Группа PHP-FIG и разделяемые интерфейсы

Этот пост я написал по просьбе Эверта Пота, с которым успел пообщаться во время голландской конференции PHP в июне 2012 года. В статье подробно рассмотрены некоторые наблюдения, связанные с группой PHP-FIG и, надеюсь, после прочтения вы поймете, почему я больше не принимаю непосредственного участия в ней.

Я был одним из основателей  Framework Interoperability Group (группа, отвечающая за стандартизацию PHP), которая теперь называется PHP-FIG. Я был одним из десятка людей, которые сидели за круглым столом в 2009 году в Чикаго во время php-конференции. Мы рассуждали о том, что могли бы сделать для улучшения совместной работы по нашим проектам. Также шли разговоры о том, чтобы упростить пользователям задачу по выбору одного из наших проектов в целях создания новых решений для наших приложений.

Первый «стандарт», который получился благодаря нашей затее, назывался PSR-0. Он основывался на стандартном наименовании классов, использующем соотношение 1:1 между пространством имени и / или префиксом вендора и иерархией каталогов, а также именем класса и именем файла, в котором он хранится. По сей день есть и те, кто видит в этом большой шаг вперед для развития сотрудничества, так и те, которые считают это проблемой.

А знаете, что было потом? Ничего. Достаточно долго мы просто бездействовали. Но чуть больше года назад произошел новый подъем, организованный людьми, желающими сделать больше. Пол Джонс взял на себя ответственность и решился возглавить следующие двастандарта, которые сосредоточены вокруг стиля программирования. Опять же, как и в случае со стандартом PSR-0, среди нас были те, кто чувствовал, что это огромный шаг вперед, и те, кто ненавидел выбранное направление.

А самое интересное началось, как только наша тема получила новое развитие. Казалось, каждый хотел высказаться и привнести что-то новое. В неделю мы получали от десятков людей отзывы с просьбой дать им возможность участвовать в нашем мероприятии. Вслед за старой заявкой поступала новая. Вне зависимости от того, что кто-то любит или не любит существующий стандарт, люди хотят видеть, что их предложения не останутся не услышанными.

Мы обсуждали предложения по разделению интерфейсов, затем кэширование и теперь логгирование (хотя последняя тема была первой на голосовании).

Разделяемые интерфейсы

Идея вокруг разделяемых интерфейсов довольно проста: если мы можем прийти к обоюдному соглашению по базовому интерфейсу для общих задач приложения, библиотек и фреймворков, мы можем контролировать тип в этом общем интерфейсе, позволяя разработчикам осуществить свой выбор или даже стандарт, эталонную реализацию. Цель состоит в том, чтобы предотвратить NIH-синдром (Not Invented Here, «Изобретено не здесь»), а также упростить повторное использование компонентов между двумя библиотеками. Например, если у вас есть фреймворк А, имеющий кэшированную библиотеку, и вы используете ORM B, то вы сможете передать тот же кэшированный объект в ORM, который вы используете во фреймворке.

И это великое достижение.

Проблемы

Во-первых, я согласен, что NIH — это проблема.

Во-вторых, я думаю, что есть место для нескольких вариантов реализации любого компонента. Часто встречаются различные подходы: можно сосредоточиться на производительности или на применении нескольких адаптеров для предоставления различных возможностей и т.д. Иногда наличие различных бэкграундов может демонстрировать различные проблемные зоны, которые нужно решать. Таким образом, хорошо иметь в запасе несколько реализаций, поскольку разработчики могут сравнить результаты работы каждой из них, и выбрать оптимальную для условий текущего проекта.

Последний пункт требует внести некоторые оговорки по поводу общих интерфейсов.

Что делать, если конкретный подход требует отступления от разделяемого интерфейса для реализации своих целей? Кроме того, для сохранения наибольшей совместимости между проектами разделяемые интерфейсы приобретают тенденцию к некоторой обобщенности. А при конкретной реализации разработчикам приходится делать тонну ручной проверки типов и других параметров, что приводит к трудностям тестирования, поддержки, ументирования и понимания.

В качестве примера рассмотрим следующую запись:

public function log($message, array $context = null);

Что делать, если ваша библиотека поддерживает идею приоритетов? Куда отправится даная выше информация, будет ли она различаться другими библиотеками, будет ли одна библиотека использовать ключ для совершенно другой цели? А как насчет логгирования объектов — запись не говорит, что вы не можете, но как узнать, будет ли поддерживать данную функцию конкретная реализация, если я передам ее? Почему должна быть переменная $context-массивом? Почему не любым Traversable- или ArrayAccess-объектом?

Кроме того, если данный проект имеет собственную реализацию компонента и специализированные функции, зачем контролировать тип на разделяемом интерфейсе, который не выполняет эти функции? Это нелогично, так как проект затем нужно будет проверять на дополнительные интерфейсы для специализированных возможностей и т.д. Все это увеличивает затраты на поддержку.

Таким образом, основная проблема, связанная с идеей создания разделяемого интерфейса, заключается в том, что я чувствую — здесь есть над чем подумать. Всегда найдется место для новых мыслей и идей в той или иной предметной области, и поэтому мышление не должно быть ограничено существующим стандартом. С другой стороны, я считаю, что нельзя ограничивать себя стандартизированным интерфейсом.

Разделение

Помните, первое, о чем я подумал: NIH — это скорее проблема, чем подмога. Как согласовать это с твердой позицией в отношении разделяемых интерфейсов?

Сделать это просто — с помощью мостов и / или адаптеров.

Давайте вернемся к примеру, где у нас был фреймворк А, кэшированная библиотека и ORM B.

Давайте предположим, что ORM B определяет интерфейс для кэширования, и, скажем, это выглядит следующим образом:

interface CacheInterface
{
    public function set($key, $data);
    public function has($key);
    public function get($key);
}

Кроме того, мы предполагаем, что ожидаемые значения параметров и возвращаемых типов документируются.
Что мы, как пользователь фреймворка А и ORM B, можем сделать, так это построить реализацию CacheInterface, которая принимает экземпляр кэша от фреймворка А и прокси различных методов интерфейса для этого экземпляра:

Предполагается, что ваш код хорошо отделен, и вы используете какой-нибудь контейнер Инверсии управления, вы, скорее всего, можете создать фабрику для вашего ORM, которая будет захватывать классы с кэшем инъекций и передавать их в ORM-экземпляр.

class FrameworkACache implements CacheInterface
{
    protected $cache;
    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }
    public function set($key, $data)
    {
        $item = new CacheItem($key, $data);
        $this->cache->setItem($item);
    }
    public function has($key)
    {
        return $this->cache->exists($key);
    }
    public function get($key)
    {
        $item = $this->cache->getItem($key);
        return $item->getData();
    }
}

Да, это требует немного больше работы, но зато можно не сомневаться в конечном результате: при разделенном кэшировании между фреймворком и ORM отпадает необходимость в разделении интерфейсов, и не нужно жертвовать функциями в фреймворке или ORM.

Разделение — это хорошо, но разработка решений — лучше

Я думаю, что основную идею группы PHP-FIG можно сформулировать следующим образом: давайте все начнем думать о том, как мы можем облегчить работу друг с другом. С течением времени мои мысли о том, как достичь этой цели, существенно изменились, и сводятся к следующим пунктам:

  • Используйте соглашения при именовании, которые позволят уменьшить количество коллизий (например, используйте в проекте префикс вендора / пространства имен);
  • Используйте семантические версии;
  • Держите пакеты установки отдельно;
  • Используйте простой способ автозагрузки;
  • Предоставление интерфейса для всего, что поможет извлечь выгоду из альтернативных реализаций;
  • Не пишите код, который имеет побочные эффекты в глобальное пространство имен (в том числе изменение параметров PHP или Суперглобальные переменные).

Следуя этим принципам, вы сможете без проблем взаимодействовать друг с другом, и в то же время содействовать инновационным и дифференцируемым решениям общих проблем.