1
0
mirror of https://github.com/php/doc-ru.git synced 2026-04-27 01:08:05 +02:00
Files
archived-doc-ru/language/oop5/traits.xml
T
2024-01-04 17:56:38 +03:00

570 lines
17 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 7341710fa635b4a969bc855a1e4619cdfa6b63f1 Maintainer: mch Status: ready -->
<!-- Reviewed: no -->
<sect1 xml:id="language.oop5.traits" xmlns="http://docbook.org/ns/docbook">
<title>Трейты</title>
<para>
PHP реализует способ переиспользования кода, называемый трейтами (Traits).
</para>
<para>
Трейт — это механизм переиспользования кода в языках
с поддержкой одиночного наследования, к которым относится PHP. Задача трейта —
уменьшить ограничения одиночного наследования, разрешая разработчику легко
переиспользовать наборы методов в нескольких независимых классах,
находящихся в разных иерархиях классов. Семантика комбинации трейтов
и классов определена так, чтобы снизить уровень сложности, а также избежать
типичных проблем, свойственных множественному наследованию и примесям (Mixins).
</para>
<para>
Трейт очень похож на класс, но рассчитан только на группировку функциональности
тонко контролируемым и согласованным образом. Нельзя создать отдельный экземляр трейта.
Трейт — это дополнение к обычному наследованию и инструмент построения
горизонтальной композиции поведения, то есть работы с членами класса (трейта)
без требования наследования.
</para>
<example xml:id="language.oop5.traits.basicexample">
<title>Пример использования трейта</title>
<programlisting role="php">
<![CDATA[
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
]]>
</programlisting>
</example>
<sect2 xml:id="language.oop5.traits.precedence">
<title>Приоритет</title>
<para>
Член, унаследованный из базового класса, переопределяется членом, введённым трейтом.
Порядок приоритета следующий: члены текущего класса переопределяют методы
трейта, которые, со своей стороны, переопределяют унаследованные методы.
</para>
<example xml:id="language.oop5.traits.precedence.examples.ex1">
<title>Пример приоритета старшинства</title>
<para>
Наследуемый от базового класса метод переопределяется методом, добавленным
в класс MyHelloWorld из трейта SayWorld. Поведение методов трейта повторяет поведение методов
класса MyHelloWorld. Порядок приоритета такой: методы текущего класса
переопределяют методы трейта, которые, со своей стороны, переопределяют методы
базового класса.
</para>
<programlisting role="php">
<![CDATA[
<?php
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello World!
]]>
</screen>
</example>
<example xml:id="language.oop5.traits.precedence.examples.ex2">
<title>Пример альтернативного порядка приоритета</title>
<programlisting role="php">
<![CDATA[
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class TheWorldIsNotEnough {
use HelloWorld;
public function sayHello() {
echo 'Hello Universe!';
}
}
$o = new TheWorldIsNotEnough();
$o->sayHello();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello Universe!
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.multiple">
<title>Несколько трейтов</title>
<para>
В класс можно добавить несколько трейтов, перечислив их в директиве
<literal>use</literal> через запятую.
</para>
<example xml:id="language.oop5.traits.multiple.ex1">
<title>Пример использования нескольких трейтов</title>
<programlisting role="php">
<![CDATA[
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World';
}
}
class MyHelloWorld {
use Hello, World;
public function sayExclamationMark() {
echo '!';
}
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello World!
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.conflict">
<title>Разрешение конфликтов</title>
<para>
Если два трейта добавляют метод с одним и тем же именем, будет вызвана фатальная
ошибка, если конфликт явно не разрешён.
</para>
<para>
Для разрешения конфликтов именования между трейтами, включёнными в один и тот же
класс, вызывают оператор <literal>insteadof</literal>, чтобы точно
выбрать один из конфликтующих методов.
</para>
<para>
Так как предыдущий оператор только исключает методы, оператор
<literal>as</literal> может включить один из конфликтующих
методов под другим именем. Обратите внимание, что оператор <literal>as</literal>
не переименовывает метод, а также не влияет ни на какой другой метод.
</para>
<example xml:id="language.oop5.traits.conflict.ex1">
<title>Пример разрешения конфликтов</title>
<para>
В этом примере в класс Talker включены трейты A и B.
Поскольку в трейтах A и B есть конфликтующие методы, класс использует вариант
метода smallTalk из трейта B, а вариант метода bigTalk — из трейта A.
</para>
<para>
Класс Aliased_Talker применяет оператор <literal>as</literal>,
чтобы использовать реализацию метода bigTalk из класса B
под дополнительным псевдонимом <literal>talk</literal>.
</para>
<programlisting role="php">
<![CDATA[
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
class Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.visibility">
<title>Изменение видимости метода</title>
<para>
Применяя оператор <literal>as</literal>, можно также изменить
видимость метода в классе, в который включён трейт.
</para>
<example xml:id="language.oop5.traits.visibility.ex1">
<title>Пример изменения видимости метода</title>
<programlisting role="php">
<![CDATA[
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// Изменение видимости метода sayHello
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// Создание псевдонима метода с изменённой видимостью
// видимость sayHello не изменилась
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.composition">
<title>Трейты, состоящие из трейтов</title>
<para>
Трейты можно включать и в классы, и в другие трейты. Трейт может быть полностью или частично составлен из членов,
описанных в других трейтах, один или несколько из которых включены в определении трейта.
</para>
<example xml:id="language.oop5.traits.composition.ex1">
<title>Пример трейтов, составленных из трейтов</title>
<programlisting role="php">
<![CDATA[
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World!';
}
}
trait HelloWorld {
use Hello, World;
}
class MyHelloWorld {
use HelloWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello World!
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.abstract">
<title>Абстрактные члены трейтов</title>
<para>
Трейты поддерживают абстрактные методы, чтобы установить требования
к классу, в который будет включён трейт.
Поддерживаются общедоступные, защищённые и закрытые методы.
До PHP 8.0.0 поддерживались только общедоступные и защищённые абстрактные методы.
</para>
<caution>
<simpara>
Начиная с PHP 8.0.0 сигнатура конкретного метода должна следовать
<link linkend="language.oop.lsp">правилам совместимости сигнатур</link>.
Ранее сигнатура метода могла несовпадать.
</simpara>
</caution>
<example xml:id="language.oop5.traits.abstract.ex1">
<title>Требования трейта при помощи абстрактных методов</title>
<programlisting role="php">
<![CDATA[
<?php
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
}
class MyHelloWorld {
private $world;
use Hello;
public function getWorld() {
return $this->world;
}
public function setWorld($val) {
$this->world = $val;
}
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.static">
<title>Статические члены трейта</title>
<para>
В трейтах можно определять статические переменные, статические методы и статические свойства.
</para>
<note>
<para>
Начиная с PHP 8.1.0 прямой вызов статического метода
или прямой доступ к статическому свойству в трейте устарел.
К статическим методам и свойствам нужно обращаться только в классе, в который включён трейт.
</para>
</note>
<example xml:id="language.oop5.traits.static.ex1">
<title>Статические переменные</title>
<programlisting role="php">
<![CDATA[
<?php
trait Counter {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
class C1 {
use Counter;
}
class C2 {
use Counter;
}
$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>
]]>
</programlisting>
</example>
<example xml:id="language.oop5.traits.static.ex2">
<title>Статические методы</title>
<programlisting role="php">
<![CDATA[
<?php
trait StaticExample {
public static function doSomething() {
echo 'Что-либо делаем';
}
}
class Example {
use StaticExample;
}
Example::doSomething();
?>
]]>
</programlisting>
</example>
<example xml:id="language.oop5.traits.static.ex3">
<title>Статические свойства</title>
<programlisting role="php">
<![CDATA[
<?php
trait StaticExample {
public static $static = 'foo';
}
class Example {
use StaticExample;
}
echo Example::$static;
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.properties">
<title>Свойства</title>
<para>
Трейты также могут определять свойства.
</para>
<example xml:id="language.oop5.traits.properties.example">
<title>Определение свойств</title>
<programlisting role="php">
<![CDATA[
<?php
trait PropertiesTrait {
public $x = 1;
}
class PropertiesExample {
use PropertiesTrait;
}
$example = new PropertiesExample;
$example->x;
?>
]]>
</programlisting>
</example>
<para>
Если трейт определяет свойство, то класс не может определить свойство
c таким же именем, кроме случаев полного совпадения (та же область видимости и тип,
модификатор readonly и начальное значение), иначе будет выброшена фатальная ошибка.
</para>
<example xml:id="language.oop5.traits.properties.conflicts">
<title>Разрешение конфликтов</title>
<programlisting role="php">
<![CDATA[
<?php
trait PropertiesTrait {
public $same = true;
public $different1 = false;
public bool $different2;
public bool $different3;
}
class PropertiesExample {
use PropertiesTrait;
public $same = true;
public $different1 = true; // Фатальная ошибка
public string $different2; // Фатальная ошибка
readonly protected bool $different3; // Фатальная ошибка
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.constants">
<title>&Constants;</title>
<para>
Начиная с версии PHP 8.2.0 трейты могут также определять константы.
</para>
<example xml:id="language.oop5.traits.constants.example">
<title>Определение констант</title>
<programlisting role="php">
<![CDATA[
<?php
trait ConstantsTrait {
public const FLAG_MUTABLE = 1;
final public const FLAG_IMMUTABLE = 5;
}
class ConstantsExample {
use ConstantsTrait;
}
$example = new ConstantsExample;
echo $example::FLAG_MUTABLE; // 1
?>
]]>
</programlisting>
</example>
<para>
Если трейт определяет константу, то класс не может определить константу с таким же именем,
если только они не совместимы (одинаковая область видимости, начальное значение и модификатор final),
иначе выбрасывается фатальная ошибка.
</para>
<example xml:id="language.oop5.traits.constants.conflicts">
<title>Разрешение конфликтов</title>
<programlisting role="php">
<![CDATA[
<?php
trait ConstantsTrait {
public const FLAG_MUTABLE = 1;
final public const FLAG_IMMUTABLE = 5;
}
class ConstantsExample {
use ConstantsTrait;
public const FLAG_IMMUTABLE = 5; // Фатальная ошибка
}
?>
]]>
</programlisting>
</example>
</sect2>
</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->