Статьи

Под капотом компонентной архитектуры Yii, часть 3

Добро пожаловать в последнюю статью этого трехчастного вихревого тура по классу компонентов инфраструктуры Yii. Цель этой серии — показать вам, как Yii реализует архитектуру на основе компонентов и как его класс CComponent обрабатывает детали реализации, CComponent используя магические методы PHP.

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

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

В объектной модели PHP вы можете повторно использовать функциональность, расширяя базовый класс, и новый класс наследует функции и свойства от родительского. Наследование является отличным инструментом программирования, однако в сложных системах оно может очень быстро стать ограничивающим. Что происходит, когда у вас есть два или более классов с функциональностью, относящейся к новому классу? Мы не можем эффективно использовать наш код, потому что PHP не поддерживает множественное наследование. Система поведения Yii — это способ достижения множественного наследования путем реализации миксинов .

Построение битов пользователя

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

Ага! Я знаю! Давайте добавим функциональность в поведение! Могу поспорить, что вы не ожидали этого.

Сначала мы создаем класс UserAccountBehavior . Поведения должны расширяться от базового класса CBehavior , а CBehavior от CComponent означает, что сами поведения могут также иметь поведения, события и свойства, но давайте не будем слишком увлекаться.

 <?php Class UserAccountBehavior extends CBehavior { public function getAccountInfomation() { // connect to a service to get credit card details and payment info return $paymentService->getDetails($this->owner->id); } } 

Внутри поведения $this->owner возвращает объект, к которому было прикреплено это поведение. Поскольку вы будете прикреплять это поведение только к пользовательским объектам, вы знаете, что $this->owner будет пользовательским объектом (который в Yii является экземпляром класса CWebUser ). Поэтому вы можете получить доступ к идентификатору пользователя, используя $this->owner->id .

Теперь вам нужно прикрепить поведение к объекту пользователя во время выполнения. В Yii класс CWebUser представляет зарегистрированного пользователя. Yii создает статический экземпляр CWebUser и сохраняет его в свойстве user приложения, и вы можете обращаться к нему с помощью Yii::app()->user .

 <?php $user = Yii::app()->user; $user->attachBehavior("account", new UserAccountBehavior()); echo $user->getAccountInformation(); 

Там у вас это есть, сначала вы получили пользовательский объект, используя Yii::app()->user , затем вызвали метод attachBehavior() и передали объект поведения для присоединения. Пользовательский объект теперь имеет доступ к дополнительным методам, определенным поведением. В этом примере getAccountInformation() вызывается для объекта пользователя, который, в свою очередь, вызывает метод getAccountInformation() объекта UserAccountBehavior .

Вы также можете получить доступ к UserAccountBehavior объекту UserAccountBehavior , используя $user->account , поэтому $user->account->getAccountInformation() будет аналогичен вызову $user->getAccountInformation() .

Лего блокирует использование поведения и событий

Хороший пример того, где поведение может быть наиболее полезным, — в сочетании с шаблоном активной записи. Используя поведения, вы можете определить поведение записей во время выполнения, построив его в виде серии блоков.

Например, активный класс записи, представляющий запись в блоге, может нуждаться в нескольких похожих вещах. Вы можете захотеть, чтобы оно имело «тегируемое» поведение, чтобы пользователи могли добавлять теги и искать записи, используя их. Поведение с тегами может обеспечить простые в использовании findAllByTags() , getTags() и setTags() .

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

Здесь стоит отметить, что поведения не могут переопределять методы в родительском классе, как это обычно происходит с наследованием класса, но вы можете написать свой код, чтобы события могли обрабатывать такие ситуации. Например, перед запуском метода удаления класса по умолчанию вы можете сначала проверить, было ли удаление уже обработано. Если это так, вы знаете, что некоторый код где-то (возможно, в поведении) имел дело с удалением и имеет все под контролем. Если вы вернетесь ко второй части этой серии, вы увидите, что я передавал объект CEvent при возбуждении событий; у этого объекта есть обработанное свойство, и здесь вы должны проверить, является ли оно истинным или ложным. Например:

 <?php public function beforeDelete() { $event = new CEvent; // raises and calls events attached to "onBeforeDelete" $this->onBeforeDelete($event); if ($event->handled) { return true; } else { return $this->_delete(); } } 

Теперь для поведения мягкого удаления вы можете присоединить метод onBeforeDelete (), установить свойство удаленных в базе данных и установить для свойства handled объекта события значение true.

Yii обеспечивает поведение CActiveRecordBehavior которое предоставляет и автоматически связывает методы со следующими событиями: onBeforeSave , onAfterSave , onBeforeDelete , onAfterDelete , onBeforeFind и onAfterFind . При создании поведений для активных объектов записи вы можете расширяться из этого класса. Обратите внимание, что вы также можете манипулировать beforeFind() и afterFind() чтобы изменить критерии поискового запроса, чтобы предотвратить возврат записей, помеченных как удаленные.

Другим примером может быть то, что вы хотите, чтобы эта запись представляла узлы в дереве, чтобы вы могли создать иерархию постов; Вы можете добавить поведение дерева, предоставляя доступ к дополнительным методам, таким как addChild() , getChildren() , getAncestors() и т. д. Список поведений может продолжаться вечно, но самое приятное то, что они могут храниться как отдельные файлы, такие как небольшие блоки Lego, позволяющие очень быстро создавать объекты с расширенными функциональными возможностями из существующего кода.

Магия йии

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

Системе поведения понадобятся следующие ингредиенты:

  • Метод прикрепления объекта как поведения
  • Метод хранения всех прикрепленных поведений на объекте
  • Некоторые магические методы, которые нужно отработать, если функциональность делегирована одному из прикрепленных объектов поведения
  • Метод для удаления поведения

Давайте начнем с метода для прикрепления поведения:

 <?php public function attachBehavior($name, $behavior) { $behavior->attach($this); return $this->_m[$name]=$behavior; } 

Эта функция для прикрепления поведения довольно проста; он вызывает метод attach () поведения, который определяется базовым классом CBehavior в Yii. Он получает указатель на текущий объект $this чтобы позволить поведению отслеживать, к какому объекту он прикреплен.

Внутри класса CBehavior который должен охватывать все CBehavior поведения, находится определение метода attach() :

 <?php public function attach($owner) { $this->owner = $owner; } 

Как я упоминал ранее, это позволяет вам использовать $this->owner внутри вашего объекта поведения, чтобы ссылаться на его владельца (объект, к которому он в данный момент присоединен).

Далее необходим метод хранения всех прикрепленных поведений. Это на самом деле уже было продемонстрировано в attachBehavior() с этим кодом $this->_m[$name] = $behavior . Чтобы сохранить список поведений, вы просто сохраняете их в ассоциативный массив с ключом имени поведения. Теперь вы можете прикрепить поведение к любому объекту, который подклассов CComponent .

Теперь должен быть способ, позволяющий взаимодействовать с этими поведениями и при необходимости вызывать правильные методы. Метод PHP magic __call идеально подходит для этого.

 <?php public function __call($name,$parameters) { if ($this->_m!==null) { foreach($this->_m as $object) { if (method_exists($object,$name)) { return call_user_func_array(array($object, $name), $parameters); } } throw new Exception(get_class($this) . " and its behaviors do not have a method named '" . $name . "'"); } 

При вызове неопределенного метода объекта PHP вызовет магический метод __call . Имя вызванного метода передается как аргумент $name а аргументы, которые были в вызове метода, передаются как $parameters . Теперь все, что нужно сделать коду, — это просто перебрать все присоединенные поведения (хранящиеся в $this->_m ) и проверить, содержат ли они метод с тем же именем, которое было только что вызвано. Если метод существует в поведении, то он вызывается и результаты возвращаются. Если ни в одном из поведений не найдено ни одного подходящего метода, выдается исключение с жалобой только на это.

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

Резюме

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

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