Mixin – один из самых используемых паттернов PHP

returnt

19.06.2016

design patterns - пример паттерн дизайн

Хорошим тоном кода PHP считают такую его реализацию, когда создание экземпляра класса делегируется самому классу либо специально созданному (обученному) для этих целей отдельному классу — паттерну проектирования Mixin в PHP.

Паттерн Mixin в ООП на PHP

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

Выход есть — на помощь приходит Mixin pattern!

Немного о том, как работает Mixin в PHP «на пальцах». Допустим, мы имеем класс А, в котором есть метод (а) и есть класс Б. Как получить доступ к методу (а), не создавая экземпляр класса Б в текущем классе? Ответ прост: делегировать функцию создания специальному классу, который в ответ вернёт экземпляр нужного нам класса.

Прежде чем приступить к практическому примеру, хотелось ещё отметить, что миксин паттерн решает не только одну из проблем ООП, но и позволяет реализовать так называемое множественное наследование, поскольку мы можем дёрнуть любой доступный класс и использовать его в текущем контексте, что очень полезно на практике!

Давайте же посмотрим пример реализации данного паттерна:

Класс А

class A extends Mixin
{
}

Обратите внимание — класс А наследует класс миксин. Давайте его реализуем:

Класс Mixin

<?php 
class Mixin { 
     private $mixed = array(); 

     public function __get($name) { 
        foreach ($this->mixed as $object) {
            if (property_exists($object, $name)) {
                return $object->$name;
            }
        }

        throw new Exception("Property $name is not defined.");
    }

    public function __set($name, $value)
    {
        foreach ($this->mixed as $object) {
            if (property_exists($object, $name)) {
                return $object->$name = $value;
            }
        }

        throw new Exception("Property $name is not defined.");
    }

    public function __isset($name)
    {
        foreach ($this->mixed as $object) {
            if (property_exists($object, $name) && isset($this->$name)) {
                return true;
            }
        }

        return false;
    }

    public function __unset($name)
    {
        foreach ($this->mixed as $object) {
            if (property_exists($object, $name)) {
                $object->$name = null;
            }
        }
    }

    public function __call($name, $parameters)
    {
        foreach ($this->mixed as $object) {
            if (method_exists($object, $name)) {
                return call_user_func_array(array($object, $name), $parameters);
            }
        }

        throw new Exception("Method $name is not defined.");
    }

    public function mix($class, $parameters)
    {
        if (empty($parameters)) {
            return $this->mixed[$class] = new $class();
        } else {
            return $this->mixed[$class] = new $class($parameters);
        }
    }

Что ж, рассмотрим по пунктам данный «чудо» класс.

  1. Публичный class Mixin
  2. Приватное свойство $mixed = array(); — данное свойство предназначено для хранения всех экземпляров классов, к которым мы обращались/вызывали
  3. Публичный метод public function __get($name) – также является «магическим» методом. Он будет вызван в том случае, если мы попытаемся получить какие-либо данные из недопустимых/не реализованных в текущем классе свойствах. Метод в своей реализации пробежится циклом по нашему свойству $mixed (а, как мы помним, это массив) и проверит существование свойства у каждого элемента массива, а каждый элемент массива — это не что иное, как экземпляр класса, независимо от того, есть ли у него такое свойство (property_exists) или нет. Если есть, то он вернёт значение найденного свойства
  4. Публичный метод public function __set($name, $value) – будет вызван в том случае, если мы попытаемся записывать данные в недоступные/ не реализованные свойства аналогично предыдущему методу, только лишь с одним отличием — он установит значение найденному свойству
  5. Ещё один публичный метод public function __isset($name) – отработает так же, как и предыдущие методы, но с двумя отличиями. Кроме проверки property_exists, он ещё проверит isset вызываемого свойства и вернёт истину. Не забываем о том, что данный метод будет вызван в случае использования isset() или empty() на недоступных свойствах
  6. public function __unset($name) – данный «магический» метод будет вызван в случае попытки вызвать unset у недопустимого/не реализованного свойства.
  7. И, наконец, чудеснейшей метод public function __call($name, $parameters), который будет вызван в случаен вызова, как Вы уже догадались, не реализованного метода. Суть его работы такова: он также пройдёт циклом по массиву экземпляров классов, попытается найти в них вызываемый метод с помощью функции method_exists и далее с помощью функции call_user_func_array(array($object, $name), $parameters) вызовет и вернёт найденный метод экземпляра класса с набором переданных параметров
  8. Последний метод, но не менее важный, – одноименный миксин public function mix($class), которому мы передаём имя класса. Он и создаст параметры экземпляра класса, и сохранит его в массив mixed благодаря реализации вышеперечисленных «магических» методов.

Класс Б

class B
{
    public $foo = "barn";

    function test()
    {
        echo "Success!\n";
    }
}

Имейте в виду, что класс Mixin ничего не знает о классе Б – он условно функциональный. Как видим, класс Б обладает одним публичным свойством и одним публичным методом, которые возвращают строку с выводом.

Теперь давайте протестируем наш функционал:

$a = new A();
$a->mix('Б', '');
$a->test();
echo $a->foo;

В данном примере с помощью миксин паттерна проектирования PHP мы создаём экземпляр класса (А), а у него вызываем метод mix, который реализован в классе-родителе (Mixin) по параметрам метода mix, пробрасываем строку с именем класса, который хотим инициализировать, и параметрами конструктора, если таковые имеются (в нашем примере их нет, поэтому передаём пустую строку). А далее мы легко можем вызвать у класса А метод класса Б (так же, как если бы класс А наследовался от класса Б) и метод test.

Для полной уверенности давайте протестируем возможность вызова свойства класса Б у экземпляра класса А foo. Результат обработки данного кода выглядит так:

 

Пример Mixin в PHP – миксин паттерн дизайн

Результат обработки кода

 

Подведём итог

Какие проблемы решает паттерн миксин?

  1. Множественное наследование
  2. Делегирование инициализации экземпляра класса

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