До сих пор мы работали с так называемой утиной типизацией: Если что-то ходит как утка и крякает как утка, то это и есть утка. Она проявляется в том, что в сигнатуре функции не указывается ожидаемый класс или интерфейс объекта. Мы просто ждём, что вызывающий код отдаст объект, имеющий необходимые методы:
<?php
function doSomething($logger)
{
$logger->info('process starting');
}
Утиная типизация автоматически вытекает из динамической типизации. В большинстве динамических языков понятия "интерфейс" даже не существует. PHP в этом смысле пошёл своим путём. Несмотря на его динамическую природу, классовая модель PHP взята из Java. И в PHP присутствуют почти все соответствующие атрибуты: интерфейсы, абстрактные классы и многое другое. Поэтому в PHP принято указывать интерфейсы ожидаемых объектов в сигнатурах функций.
<?php
function doSomething(Psr\Log\LoggerInterface $logger)
{
$logger->info('process starting');
}
Теперь эта функция работает только с объектами тех классов, которые реализуют указанный интерфейс.
Как вы помните, в сигнатуре можно указывать и класс, и интерфейс. Что правильнее? Вооружившись принципом инверсии зависимостей, мы можем однозначно сказать, что правильнее использовать интерфейсы и завязываться на абстракцию вместо конкретной реализации. Только в этом случае появится возможность подменить реализацию. Ниже пример реального кода из фреймворка Symfony2, в котором активно используется полиморфизм подтипов с инъекцией через конструктор:
<?php
class Psr18Client implements ClientInterface
{
private $client;
private $responseFactory;
private $streamFactory;
// Зависимости приходят через конструктор
public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
{
$this->client = $client ?? HttpClient::create();
$this->responseFactory = $responseFactory;
$this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
$psr17Factory = new Psr17Factory();
$this->responseFactory = $this->responseFactory ?? $psr17Factory;
$this->streamFactory = $this->streamFactory ?? $psr17Factory;
}
// another methods
}
В очередной раз подчеркну что не нужно возводить эту технику в абсолют. Если создавать интерфейсы на всё подряд, то код очень быстро распухнет и станет сложным. Вспомните, в ruby/python/js вообще нет интерфейсов и никто не умер. Попробуйте побродить по исходникам популярного микрофреймворка Lumen. Внутри него крайне тяжело найти места, где в сигнатурах методов встречаются явные указания интерфейсов
Для лучшей переносимости кода, в PHP вводятся стандартные интерфейсы для самых частых задач. Они описываются в PSR. В примере выше используется стандартный интерфейс Psr\Log\LoggerInterface
. Со временем, всё большая часть библиотек начнёт его реализовывать, а это позволит безболезненно подменять логгер без необходимости менять свой код. Кроме логгера в PSR описаны множество других интерфейсов, среди них HTTP-клиент, HTTP-запрос, HTTP-ответ, кеш и другое.
Теперь мы можем ответить на вопрос, почему этот полиморфизм называется полиморфизмом подтипов. Типом в ООП языках принято называть интерфейсы (хотя это не совсем правда) и отношение подтипов определяется отношением интерфейсов.
Интерфейсы можно расширять другими интерфейсами, для этого используется ключевое слово extends
. Пример из библиотеки DS (той, где описаны структуры данных на PHP в объектном синтаксисе):
<?php
// https://github.com/php-ds/polyfill/blob/master/src/Collection.php
interface Collection extends \Traversable, \Countable, \JsonSerializable
{
// some code
}
Теперь любой класс, реализующий интерфейс Collection, будет обязан реализовать все интерфейсы, которые расширяет Collection. Это касается каждого интерфейса в цепочке.
Статические методы
Статические методы жёстко завязаны на имя класса, поэтому полиморфизм подтипов в них невозможен. Но можно схитрить и использовать вместо класса переменную с его именем:
<?php
Foo::aStaticMethod();
$classname = 'Foo';
$classname::aStaticMethod(); // Начиная с PHP 5.3.0
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.