<?php
include_once $_SERVER['DOCUMENT_ROOT'] . '/include/shared-manual.inc';
$TOC = array();
$TOC_DEPRECATED = array();
$PARENTS = array();
include_once dirname(__FILE__) ."/toc/language.oop5.inc";
$setup = array (
  'home' => 
  array (
    0 => 'index.php',
    1 => 'PHP Manual',
  ),
  'head' => 
  array (
    0 => 'UTF-8',
    1 => 'ru',
  ),
  'this' => 
  array (
    0 => 'language.oop5.variance.php',
    1 => 'Ковариантность и контравариантность',
    2 => 'Ковариантность и контравариантность',
  ),
  'up' => 
  array (
    0 => 'language.oop5.php',
    1 => 'Классы и объекты',
  ),
  'prev' => 
  array (
    0 => 'language.oop5.serialization.php',
    1 => 'Сериализация объектов',
  ),
  'next' => 
  array (
    0 => 'language.oop5.lazy-objects.php',
    1 => 'Ленивые объекты',
  ),
  'alternatives' => 
  array (
  ),
  'source' => 
  array (
    'lang' => 'ru',
    'path' => 'language/oop5/variance.xml',
  ),
  'history' => 
  array (
  ),
);
$setup["toc"] = $TOC;
$setup["toc_deprecated"] = $TOC_DEPRECATED;
$setup["parents"] = $PARENTS;
manual_setup($setup);

contributors($setup);

?>
<div id="language.oop5.variance" class="sect1">
 <h2 class="title">Ковариантность и контравариантность</h2>

 <p class="para">
  В PHP 7.2.0 путём снятия ограничений типа для параметров в дочернем методе
  добавили частичную контравариантность. Начиная с PHP 7.4.0 добавили полную поддержку
  ковариантности и контравариантности.
 </p>

 <p class="para">
  Ковариантность разрешает дочернему методу возвращать более конкретный тип,
  чем тип значения возврата родительского метода. Контравариантность
  разрешает определить тип параметра в дочернем методе менее конкретным, чем в родительском.
 </p>

 <p class="para">
  Объявление типа считают более конкретным в следующем случае:
  <ul class="itemizedlist">
   <li class="listitem">
    <span class="simpara">
     Удалили тип <a href="language.types.type-system.php#language.types.type-system.composite.union" class="link">из объединения типов</a>
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Добавили тип
     <a href="language.types.type-system.php#language.types.type-system.composite.intersection" class="link">к пересечению типов</a>
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Тип класса изменили на тип дочернего класса
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Тип <span class="type"><a href="language.types.iterable.php" class="type iterable">iterable</a></span> изменили на тип <span class="type"><a href="language.types.array.php" class="type array">array</a></span> или <span class="classname"><a href="class.traversable.php" class="classname">Traversable</a></span>
    </span>
   </li>
  </ul>

  Класс типа считают менее конкретным, если верно обратное.
 </p>

 <div class="sect2" id="language.oop5.variance.covariance">
  <h3 class="title">Ковариантность</h3>

  <p class="para">
   Создадим простой абстрактный родительский класс <var class="varname">Animal</var>,
   чтобы проиллюстрировать работу ковариантности.
   Класс <var class="varname">Animal</var> расширяется дочерними классами
   <var class="varname">Cat</var> и <var class="varname">Dog</var>.
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="annotation-interactive phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">abstract class </span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />    protected </span><span style="color: #0000BB">string $name</span><span style="color: #007700">;<br /><br />    public function </span><span style="color: #0000BB">__construct</span><span style="color: #007700">(</span><span style="color: #0000BB">string $name</span><span style="color: #007700">)<br />    {<br />        </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name </span><span style="color: #007700">= </span><span style="color: #0000BB">$name</span><span style="color: #007700">;<br />    }<br /><br />    abstract public function </span><span style="color: #0000BB">speak</span><span style="color: #007700">();<br />}<br /><br />class </span><span style="color: #0000BB">Dog </span><span style="color: #007700">extends </span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />    public function </span><span style="color: #0000BB">speak</span><span style="color: #007700">()<br />    {<br />        echo </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name </span><span style="color: #007700">. </span><span style="color: #DD0000">" лает"</span><span style="color: #007700">;<br />    }<br />}<br /><br />class </span><span style="color: #0000BB">Cat </span><span style="color: #007700">extends </span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />    public function </span><span style="color: #0000BB">speak</span><span style="color: #007700">()<br />    {<br />        echo </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name </span><span style="color: #007700">. </span><span style="color: #DD0000">" мяукает"</span><span style="color: #007700">;<br />    }<br />}<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

  </div>

  <p class="para">
   Обратите внимание, пример не содержит методов, которые возвращают значения. Добавим ряд фабрик,
   которые возвращают новый объект с типом класса <var class="varname">Animal</var>,
   <var class="varname">Cat</var> или <var class="varname">Dog</var>.
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="annotation-interactive phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">interface </span><span style="color: #0000BB">AnimalShelter<br /></span><span style="color: #007700">{<br />    public function </span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #0000BB">string $name</span><span style="color: #007700">): </span><span style="color: #0000BB">Animal</span><span style="color: #007700">;<br />}<br /><br />class </span><span style="color: #0000BB">CatShelter </span><span style="color: #007700">implements </span><span style="color: #0000BB">AnimalShelter<br /></span><span style="color: #007700">{<br />    public function </span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #0000BB">string $name</span><span style="color: #007700">): </span><span style="color: #0000BB">Cat </span><span style="color: #FF8000">// Возвращаем тип Cat вместо типа Animal<br />    </span><span style="color: #007700">{<br />        return new </span><span style="color: #0000BB">Cat</span><span style="color: #007700">(</span><span style="color: #0000BB">$name</span><span style="color: #007700">);<br />    }<br />}<br /><br />class </span><span style="color: #0000BB">DogShelter </span><span style="color: #007700">implements </span><span style="color: #0000BB">AnimalShelter<br /></span><span style="color: #007700">{<br />    public function </span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #0000BB">string $name</span><span style="color: #007700">): </span><span style="color: #0000BB">Dog </span><span style="color: #FF8000">// Возвращаем тип Dog вместо типа Animal<br />    </span><span style="color: #007700">{<br />        return new </span><span style="color: #0000BB">Dog</span><span style="color: #007700">(</span><span style="color: #0000BB">$name</span><span style="color: #007700">);<br />    }<br />}<br /><br /></span><span style="color: #0000BB">$kitty </span><span style="color: #007700">= (new </span><span style="color: #0000BB">CatShelter</span><span style="color: #007700">())-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Рыжик"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$kitty</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">speak</span><span style="color: #007700">();<br />echo </span><span style="color: #DD0000">"\n"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$doggy </span><span style="color: #007700">= (new </span><span style="color: #0000BB">DogShelter</span><span style="color: #007700">())-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Бобик"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$doggy</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">speak</span><span style="color: #007700">();<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

   
<p class="para">
 Результат выполнения приведённого примера:
</p>

   <div class="example-contents screen">
<div class="annotation-interactive cdata"><pre>
Рыжик мяукает
Бобик лает
</pre></div>
   </div>
  </div>
 </div>

 <div class="sect2" id="language.oop5.variance.contravariance">
  <h3 class="title">Контравариантность</h3>

  <p class="para">
   В продолжение предыдущего примера с классами <var class="varname">Animal</var>,
   <var class="varname">Cat</var> и <var class="varname">Dog</var>
   введём новые классы — <var class="varname">Food</var> и <var class="varname">AnimalFood</var>,
   и добавим в абстрактный класс <var class="varname">Animal</var> новый метод
   <var class="varname">eat(AnimalFood $food)</var>.
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="annotation-interactive phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">class </span><span style="color: #0000BB">Food </span><span style="color: #007700">{}<br /><br />class </span><span style="color: #0000BB">AnimalFood </span><span style="color: #007700">extends </span><span style="color: #0000BB">Food </span><span style="color: #007700">{}<br /><br />abstract class </span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />    protected </span><span style="color: #0000BB">string $name</span><span style="color: #007700">;<br /><br />    public function </span><span style="color: #0000BB">__construct</span><span style="color: #007700">(</span><span style="color: #0000BB">string $name</span><span style="color: #007700">)<br />    {<br />        </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name </span><span style="color: #007700">= </span><span style="color: #0000BB">$name</span><span style="color: #007700">;<br />    }<br /><br />    public function </span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">AnimalFood $food</span><span style="color: #007700">)<br />    {<br />        echo </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name </span><span style="color: #007700">. </span><span style="color: #DD0000">" ест " </span><span style="color: #007700">. </span><span style="color: #0000BB">get_class</span><span style="color: #007700">(</span><span style="color: #0000BB">$food</span><span style="color: #007700">);<br />    }<br />}<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

  </div>

  <p class="para">
   Переопределим метод <var class="varname">eat</var> в классе <var class="varname">Dog</var> так,
   чтобы метод принимал любой объект с типом <var class="varname">Food</var>,
   чтобы увидеть суть контравариантности. Класс <var class="varname">Cat</var> оставим
   без изменений.
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="annotation-interactive phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">class </span><span style="color: #0000BB">Dog </span><span style="color: #007700">extends </span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />    public function </span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">Food $food</span><span style="color: #007700">)<br />    {<br />        echo </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name </span><span style="color: #007700">. </span><span style="color: #DD0000">" ест " </span><span style="color: #007700">. </span><span style="color: #0000BB">get_class</span><span style="color: #007700">(</span><span style="color: #0000BB">$food</span><span style="color: #007700">);<br />    }<br />}<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

  </div>

  <p class="para">
   Следующий пример покажет поведение контравариантности.
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="annotation-interactive phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br />$kitty </span><span style="color: #007700">= (new </span><span style="color: #0000BB">CatShelter</span><span style="color: #007700">())-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Рыжик"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$catFood </span><span style="color: #007700">= new </span><span style="color: #0000BB">AnimalFood</span><span style="color: #007700">();<br /></span><span style="color: #0000BB">$kitty</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">$catFood</span><span style="color: #007700">);<br />echo </span><span style="color: #DD0000">"\n"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$doggy </span><span style="color: #007700">= (new </span><span style="color: #0000BB">DogShelter</span><span style="color: #007700">())-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Бобик"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$banana </span><span style="color: #007700">= new </span><span style="color: #0000BB">Food</span><span style="color: #007700">();<br /></span><span style="color: #0000BB">$doggy</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">$banana</span><span style="color: #007700">);<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

   
<p class="para">
 Результат выполнения приведённого примера:
</p>

   <div class="example-contents screen">
<div class="annotation-interactive cdata"><pre>
Рыжик ест AnimalFood
Бобик ест Food
</pre></div>
   </div>

   <p class="para">
    Но что произойдёт, если кошка <var class="varname">$kitty</var> попробует
    методом <span class="methodname"><strong>eat()</strong></span> съесть банан <var class="varname">$banana</var>?
   </p>

   <div class="example-contents">
<div class="annotation-interactive phpcode"><code><span style="color: #000000">$kitty-&gt;eat($banana);</span></code></div>
   </div>

   
<p class="para">
 Результат выполнения приведённого примера:
</p>

   <div class="example-contents screen">
<div class="annotation-interactive cdata"><pre>
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given
</pre></div>
   </div>
  </div>
 </div>
 <div class="sect2">
  <h3 class="title">Вариативность свойств</h3>
  <p class="simpara">
   По умолчанию свойства не удовлетворяют ни правилам ковариантности, ни правилам контравариантности,
   следовательно, свойства — инвариантны. Поэтому тип свойства вообще нельзя изменить в дочернем классе.
   Причина этого состоит в том, что операции «чтения» должны быть ковариантными,
   а операции «записи» — контравариантными.
   Единственный доступный для свойства способ удовлетворить обоим требованиям — оставаться инвариантным.
  </p>
  <p class="simpara">
   Начиная с PHP 8.4.0, в котором добавили абстрактные свойства в интерфейсе или абстрактном классе
   <a href="language.oop5.property-hooks.php#language.oop5.property-hooks.virtual" class="link">и виртуальные свойства</a>,
   разрешается объявить свойство, доступное только для операций чтения или записи.
   Итогом нововведений стало то, что абстрактным свойствам или виртуальным свойствам,
   для которых требуется только операция &quot;get&quot;, доступна ковариантность.
   Аналогично, абстрактному свойству или виртуальному свойству,
   для которого требуется только операция &quot;set&quot;, доступна контравариантность.
  </p>
  <p class="simpara">
   Однако как только для свойства объявили как операцию get, так и операцию set,
   свойство теряет ковариантность или контравариантность для расширения,
   и снова становится инвариантным.
  </p>
  <div class="example" id="example-1">
   <p><strong>Пример #1 Пример вариативности типа свойства</strong></p>
   <div class="example-contents">
<div class="annotation-interactive phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">class </span><span style="color: #0000BB">Animal </span><span style="color: #007700">{}<br />class </span><span style="color: #0000BB">Dog </span><span style="color: #007700">extends </span><span style="color: #0000BB">Animal </span><span style="color: #007700">{}<br />class </span><span style="color: #0000BB">Poodle </span><span style="color: #007700">extends </span><span style="color: #0000BB">Dog </span><span style="color: #007700">{}<br /><br />interface </span><span style="color: #0000BB">PetOwner<br /></span><span style="color: #007700">{<br />    </span><span style="color: #FF8000">// Требуется только операция get, поэтому свойству доступна ковариантность<br />    </span><span style="color: #007700">public </span><span style="color: #0000BB">Animal $pet </span><span style="color: #007700">{<br />        </span><span style="color: #0000BB">get</span><span style="color: #007700">;<br />    }<br />}<br /><br />class </span><span style="color: #0000BB">DogOwner </span><span style="color: #007700">implements </span><span style="color: #0000BB">PetOwner<br /></span><span style="color: #007700">{<br />    </span><span style="color: #FF8000">// Свойству возможно указать более ограниченный тип, поскольку со стороны операции get<br />    // по-прежнему возвращается Animal. Однако, поскольку это внутреннее свойство текущего класса,<br />    // потомкам класса больше нельзя изменять тип свойства<br />    </span><span style="color: #007700">public </span><span style="color: #0000BB">Dog $pet</span><span style="color: #007700">;<br />}<br /><br />class </span><span style="color: #0000BB">PoodleOwner </span><span style="color: #007700">extends </span><span style="color: #0000BB">DogOwner<br /></span><span style="color: #007700">{<br />    </span><span style="color: #FF8000">// Изменение свойства НЕДОПУСТИМО, поскольку для свойства DogOwner::$pet<br />    // определили поведение операций get и set, и дочерние классы обязаны соблюдать<br />    // контракт родительского класса при переопределении свойства<br />    </span><span style="color: #007700">public </span><span style="color: #0000BB">Poodle $pet</span><span style="color: #007700">;<br />}<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

  </div>
 </div>
</div><?php manual_footer($setup); ?>