Полиморфизм
в современном PHP

Валентин @vudaltsov

Пых / PHP Point / PHP умирает?!

Полиморфизм — возможность существования чего-либо в различных формах.

Половой полиморфизм у туркестанского термита

                
                    function array_map(callable $callback, array $array): array
                    {
                        // ...
                    }
                
            
                
                    $callable = 'date';
                
            
                
                    $callable = static fn (int $time) => new DateTimeImmutable("@$time");
                
            
                
                    $callable = [new DateTimeImmutable(), 'modify'];
                
            
                
                    $callable = [DateInterval::class, 'createFromDateString'];
                
            
                
                    $callable = new class {
                        private const string FORMAT = 'd.m.Y';

                        public function __invoke(string $time): DateTimeImmutable
                        {
                            return DateTimeImmutable::createFromFormat(self::FORMAT, $time);
                        }
                    };
                
            

Это был полиморфизм подтипов

                
                    function array_map(callable $callback, array $array): array
                    {
                        // одинаковая логика для любого callback
                    }
                
            
                
                    /**
                     * @template TKey of array-key
                     * @template TInputValue
                     * @template TOutputValue
                     * @param callable(TInputValue): TOutputValue $callback
                     * @param array<TKey, TInputValue> $array
                     * @return array<TKey, TOutputValue>
                     */
                    function array_map(callable $callback, array $array): array
                    {
                        // одинаковая логика для любого массива
                    }
                
            

Это был
параметрический полиморфизм

                
                    array_map($callback, $array, $array2);
                
            
                
                    /**
                     * @template TInputValue
                     * @template TInputValue2
                     * @template TOutputValue
                     * @param callable(TInputValue, TInputValue2): TOutputValue $callback
                     * @param array<TInputValue> $array
                     * @param array<TInputValue2> $array2
                     * @return list<TOutputValue>
                     */
                    function array_map(callable $callback, array $array, array $array2): array
                    {
                        // кастомная логика для нескольких массивов
                    }
                
            
                
                    var_dump(array_map(null, ['a', 'b', 'c'], [0, 1]));
                
            
                
                    array(3) {
                        [0] => array(2) {
                            [0] => string(1) "a"
                            [1] => int(0)
                        }
                        [1] => array(2) {
                            [0] => string(1) "b"
                            [1] => int(1)
                        }
                        [2] => array(2) {
                            [0] => string(1) "c"
                            [1] => NULL
                        }
                    }
                
            
                
                    /**
                     * @template TInputValue
                     * @template TInputValue2
                     * @param array<TInputValue> $array
                     * @param array<TInputValue2> $array2
                     * @return list<array{TInputValue, TInputValue2}>
                     */
                    function array_map(null $callback, array $array, array $array2): array
                    {
                        // zip-логика
                    }
                
            
                
                    function array_map(?callable $callback, array $array, array ...$arrays): array
                    {
                        if ($callback === null) {
                            // zip-логика
                        }

                        if (count($arrays) > 0) {
                            // логика для нескольких массивов
                        }

                        // логика для одного массива
                    }
                
            
                
                    fun <TKey, TInputValue, TOutputValue> array_map(
                        callback: (TInputValue) -> TOutputValue,
                        array: Map<TKey, TInputValue>,
                    ): Map<TKey, TOutputValue>

                    fun <TInputValue, TInputValue2, TOutputValue> array_map(
                        callback: (TInputValue, TInputValue2) -> TOutputValue,
                        array: Map<Any, TInputValue>,
                        array2: Map<Any, TInputValue2>,
                    ): List<TOutputValue>

                    fun <TInputValue, TInputValue2> array_map(
                        array: Map<Any, TInputValue>,
                        array2: Map<Any, TInputValue2>,
                    ): List<Pair<TInputValue, TInputValue2>>
                
            

Это был
ad-hoc (специальный)
полиморфизм

Полиморфизм
подтипов в ООП

                
                    interface I
                    {
                        public function do(string $a): iterable;
                    }

                    final class C implements I
                    {
                        public function do(
                            int|string $a,
                            SplFileInfo $b = new SplFileInfo(__FILE__),
                        ): array
                        {
                            return [];
                        }
                    }
                
            
                
                    interface Website {}

                    final class Yii implements Website {}

                    final class Symfony implements Website {}

                    interface Developer
                    {
                        public function fix(Yii|Symfony $site): void;

                        public function build(): Yii|Symfony;
                    }
                
            
                
                    interface Developer
                    {
                        public function fix(Yii|Symfony $site): void;

                        public function build(): Yii|Symfony;
                    }

                    final class Taylor
                    {
                        public function fix(Symfony|Laravel $site): void
                        {
                            // fix bugs...
                        }

                        public function build(Lamborgini $lamborgini): Symfony|Laravel
                        {
                            if (random_int(0, 1) === 1) {
                                return new Symfony();
                            } else {
                                return new Laravel();
                            }
                        }
                    }
                
            
                
                    interface Developer
                    {
                        public function fix(Yii|Symfony $site): void;

                        public function build(): Yii|Symfony;
                    }

                    final class Vasya implements Developer
                    {
                        public function fix(Yii|Symfony|Laravel $site): void
                        {
                            // fix bugs...
                        }

                        public function build(?Lamborgini $lamborgini = null): Yii
                        {
                            if ($lamborgini === null) {
                                sleep(10);
                            }

                            return new Yii();
                        }
                    }
                
            
                
                    interface I
                    {
                        public function do(string $a): iterable;
                    }

                    final class C implements I
                    {
                        public function do(
                            int|string $a,
                            SplFileInfo $b = new SplFileInfo(__FILE__),
                        ): array
                        {
                            return [];
                        }
                    }
                
            
                
                    interface Developer
                    {
                        public function fix(Yii|Symfony $site): void;

                        public function build(): Yii|Symfony;
                    }

                    final class Petya implements Developer
                    {
                        public function fix(Yii|Symfony $site): void
                        {
                            // fix bugs...
                        }

                        public function build(): Yii
                        {
                            // build...
                        }
                    }
                
            
                
                    interface Developer
                    {
                        public function fix(Yii|Symfony $site): void;

                        public function build(): Yii|Symfony;
                    }

                    final class Petya implements Developer
                    {
                        public function fix(Yii|Symfony $site): void
                        {
                            // fix bugs...
                        }

                        public function build(): Yii
                        {
                            sleep(1000);

                            return new Yii();
                        }
                    }
                
            

A behavioral notion of subtyping (Liskov, Wing, 1994)

Subtype requirement. Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.

A behavioral notion of subtyping (Liskov, Wing, 1994)

Subtype requirement. Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.

Вариантность дженериков

                
                    interface Website {}

                    final class Yii implements Website {}

                    final class Symfony implements Website {}
                
            
                
                    /**
                     * @template TWebsite of Website
                     */
                    final class Portfolio
                    {
                        /**
                         * @var list<TWebsite>
                         */
                        private array $websites = [];

                        /**
                         * @param TWebsite $website
                         */
                        public function add(Website $website): void
                        {
                            $this->websites[] = $website;
                        }

                        /**
                         * @return list<TWebsite>
                         */
                        public function all(): array
                        {
                            return $this->websites;
                        }
                    }
                
            
                
                    /** @template TWebsite of Website */
                    final class Portfolio
                    {
                        /** @param TWebsite $website */
                        public function add(Website $website): void { /** ... */ }

                        /** @return list<TWebsite> */
                        public function all(): array { /** ... */ }
                    }

                    /**
                     * @param Portfolio<Website> $portfolio
                     */
                    function display(Portfolio $portfolio): void
                    {
                        var_dump($portfolio->all());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    display($valentinsPortfolio);
                
            
                
                    /** @template TWebsite of Website */
                    final class Portfolio
                    {
                        /** @param TWebsite $website */
                        public function add(Website $website): void { /** ... */ }

                        /** @return list<TWebsite> */
                        public function all(): array { /** ... */ }
                    }

                    /**
                     * @param Portfolio<Website> $portfolio
                     */
                    function addYii(Portfolio $portfolio): void
                    {
                        $portfolio->add(new Yii());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    addYii($valentinsPortfolio);
                
            
                
                    /**
                     * @param Portfolio<Website> $portfolio
                     */
                    function addYii(Portfolio $portfolio): void
                    {
                        $portfolio->add(new Yii());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    addYii($valentinsPortfolio);
                
            
                
                    /**
                     * @template TWebsite of Website
                     */
                    final class Portfolio
                    {
                        /** @param TWebsite $website */
                        public function add(Website $website): void { /** ... */ }

                        /** @return list<TWebsite> */
                        public function all(): array { /** ... */ }
                    }
                
            
                
                    @template (invariant) TWebsite:
                        Portfolio<Symfony> ⊆ Portfolio<Symfony>
                        Portfolio<Website> ⊆ Portfolio<Website>
                        Portfolio<Symfony> ⊄ Portfolio<Website>
                
            
                
                    /**
                     * @param Portfolio<Website> $portfolio
                     */
                    function addYii(Portfolio $portfolio): void
                    {
                        $portfolio->add(new Yii());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    addYii($valentinsPortfolio);
                
            
                
                    /**
                     * @param Portfolio<Website> $portfolio
                     */
                    function display(Portfolio $portfolio): void
                    {
                        var_dump($portfolio->all());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    display($valentinsPortfolio);
                
            
                
                    /**
                     * @param Portfolio<Website> $portfolio
                     */
                    function display(Portfolio $portfolio): void
                    {
                        var_dump($portfolio->all());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    display($valentinsPortfolio);
                
            
                
                    @template (covariant) TWebsite:
                        Portfolio<Symfony> ⊆ Portfolio<Symfony>
                        Portfolio<Website> ⊆ Portfolio<Website>
                        Portfolio<Symfony> ⊆ Portfolio<Website>
                
            
                
                    /**
                     * @template-covariant TWebsite of Website
                     */
                    final class Portfolio
                    {
                        /** @param TWebsite $website */
                        public function add(Website $website): void { /** ... */ }

                        /** @return list<TWebsite> */
                        public function all(): array { /** ... */ }
                    }
                
            
                
                    /**
                     * @template-covariant TWebsite of Website
                     */
                    interface PortfolioRead
                    {
                        /** @return list<TWebsite> */
                        public function all(): array;
                    }

                    /**
                     * @template TWebsite of Website
                     * @implements PortfolioRead<TWebsite>
                     */
                    final class Portfolio implements PortfolioRead
                    {
                        /** @param TWebsite $website */
                        public function add(Website $website): void { /** ... */ }

                        public function all(): array { /** ... */ }
                    }
                
            
                
                    /**
                     * @param PortfolioRead<Website> $portfolio
                     */
                    function display(PortfolioRead $portfolio): void
                    {
                        var_dump($portfolio->all());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    display($valentinsPortfolio);
                
            
                
                    /** @template TWebsite of Website */
                    final class Portfolio
                    {
                        // ...
                    }

                    /**
                     * @param Portfolio<covariant Website> $portfolio
                     */
                    function display(Portfolio $portfolio): void
                    {
                        var_dump($portfolio->all());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    display($valentinsPortfolio);
                
            
                
                    /**
                     * @param Portfolio<covariant Website> $portfolio
                     */
                    function addYii(Portfolio $portfolio): void
                    {
                        $portfolio->add(new Yii());
                    }

                    /**
                     * @var Portfolio<Symfony> $valentinsPortfolio
                     */
                    $valentinsPortfolio = new Portfolio();

                    addYii($valentinsPortfolio);
                
            

Полезные материалы

Спасибо за внимание!

QR code

@vudaltsov / Пых / PHP Point / PHP умирает?!