Проксирование псевдовызовов

Вчера при обсуждении Alto Coding Style aVadim высказал мысль
Вообще, в далекой перспективе мне хотелось бы изменить синтаксис псевдовызовов методов моделей и писать так: $this->ModuleUser->getUsersByFilter(); Но это сугубо личные предпочтения

По причине выходного дня и наличия немногого свободного времени — предлагаю экспромт на эту тему. Опять же не стоит относиться к предложению как к готовому решению — это всего лишь способ — может и вообще ненужный и не к месту (

Хочу обращаться к модулям и их методам так:
$aReturn = $this->modules->topic->getTopicsByFilter($aFilter,$iPage,$iPerPage,array('user','blog'));

А не так:
$aReturn = $this->Topic_GetTopicsByFilter($aFilter,$iPage,$iPerPage,array('user','blog'));


Вообщем в голову пришла мысль не менять существующий уклад в LS, а просто добавить проксирующие свойства, которые просто передавали бы данные в псевдовызовы. Организовать альтернативную форму записи – и вот что из этого получилось.

Задача: Убрать магию из вызовов методов модулей.
Что это даст: Автокомплит, PHPDoc, повысит читабельность кода, сделает возможным использовать инспектора кода

Решение
Без хака, конечно же, не обошлось, но изменения были внесены минимальные:
1. В класс LsObject добавлено свойство $modules, а конструктор класса – инициализация этого свойства – так:
abstract class LsObject {
    public $modules;
    public function __construct() {
        $this->modules = new modules();
    }

Сам класс modules прост и незамысловат – он получает лишь модуль по его имени:
class modules {
    public function __get($sName) {
        return Engine::getInstance()->getModule(ucwords($sName).'_')[0];
    }
}

2. В классе Module от которого наследуются все модули в конструктор добавим вызов родительского конструктора:
final public function __construct(Engine $oEngine) {
        parent::__construct(); // <-- здесь
        $this->oEngine = $oEngine;
    }

3. Тоже в конструктор сущности
    public function __construct($aParam = null) {
        parent::__construct();// <-- здесь
        $this->_setData($aParam);
        $this->Init();
    }


Теперь можно обращаться к методам модуля через данное свойство, так (это пример из плагина Categories — и он работает):
$aReturn = $this->modules->topic->getTopicsByFilter($aFilter,$iPage,$iPerPage,array('user','blog'));

И теперь доступен автокомплит и phpDoc!


Плагины
Для них на самом деле рабоает такой код:

Но это не очень красиво — внесем некоторые правки в файл класса LsObject^
class modules {
    private $sPluginName = "";

    public function __construct($sPluginName="") {
        $this->sPluginName = 'Plugin' . ucwords($sPluginName) . '_';
    }

    public function __get($sName) {
        return Engine::getInstance()->getModule(
            (($this->sPluginName=='Plugin_')?'':$this->sPluginName) . ucwords($sName).'_')[0];
    }
}

class plugins {
    private $modules;

    public function __get($sName) {
        $this->modules = new modules($sName);
        return $this;
    }
}

/**
 * От этого класса наследуются все остальные
 *
 * @package engine
 * @since 1.0
 */
abstract class LsObject {

    public $modules;

    public $plugins;

    public function __construct() {
        $this->modules = new modules();
        $this->plugins = new plugins();
    }

    // Здесь родной метод __call, но я его не указал
}

Это позволяет обращаться к модулям плагинов так:

Но здесь не работает автокомплит, для этих целей создадим в директории плагина файл, единственной целью которого будет доставлять возможность автокомплита и PHPDoc — вот содержимое этого файла:
<?php
die();
class plugins {

    /**
     * Плагин функционала категории для блогов и формировщик главной
     * @var PluginCategories_ModuleCategories
     */
    public $categories;
}

class modules {
    /**
     * Модуль управления категориями блогов
     * @var PluginCategories_ModuleCategories
     */
    public $categories;
}


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


Заметьте — для свойства plugins автокомплит показывает перечень всех установленных плагинов (если они работают через проксирующие методы),а PHPDoc документацию по модулю и методам модуля :)

ЗЫ:
Да, этот подход позволяет сделать унифицированное правило именования методов — с маленькой буквы, как это обсуждалось в комментариях к статье об ACS (см. картинки к статье)

Похожие статьи

  • Рейтинг и сила — как это должно работать
    Выношу сюда стихийно возникшее обсуждение И вот еще статьи по теме: Механизм подсчета рейтинга и силы Сила и рейтинг Рейтинг юзеров и блогов Опишу реализацию, к которой я склоняюсь: 1) Модуль рейтингования...
  • Стиль кодирования
    Общая схема имени переменной выглядит следующим образом: префикс+ДополнительныйПрефикс+ИмяПеременной+Суффикс. Имена переменных содержат латинские буквы верхнего и нижнего регистров и начинаются с префикса, записаного ...
  • Alto Coding Style
    Тихой сапой начал создавать документ с громким названием «Alto Coding Style» — правда, пока только начал с системы именований. Такую работу, конечно, нельзя сделать быстро и одному, поэтому проект находиться на...

5 комментариев

+3
Любопытное решение. И добавлю еще пару плюсов, которые проистекают от избавления от «магии»:

1) По идее должна увеличиться общая скорость отработки кода, т.к. вызов через «магию» — это все ж довольно ресурсоемкая операция, а таких вызовов в коде немерянно.

2) Появляется возможность передачи параметров по ссылке, чего иногда мне лично не хватало. Например:
class ModuleTopic {
    public function Blabla(&$iCount) {
        $iCount = 1;
        return true;
    }
}
Вот такой вызов ничего не вернет в $iCount:
$iCount = 0;
$this->Topic_Blabla($iCount); // $iCount останется 0
А в Вашей реализации, похоже, сработает как надо:
$iCount = 0;
$this->modules->topic->Blabla($iCount); // $iCount вернет 1
0
Да, вернет. Каждый элемент цепочки является фактическим объектом, а не фантомом. Этот плюс не увидел.

Хотелось бы указать, что минусы тоже есть, например маппер (и вообще все наследники LsObject) получают возможность обращаться к модулям — ломается архитектура, но это все объигрывается…
0
Так и сейчас любой наследник LsObject может в своих методах обращаться к модулям:
$this->Topic_GetTopicsByFilter();
Этот вызов в текущей реализации отработает в любом компоненте — сущность, маппер и т.д. Или речь о чем-то другом?

Но дополню: если и внедрять подобную реализацию, то я бы предложил чуть-чуть иной подход, а именно вместо:
$this->modules->topic->getTopicsByFilter($aFilter,$iPage,$iPerPage,array('user','blog'));
реализовать что-нибудь типа:
E::$Modules->topic->getTopicsByFilter($aFilter,$iPage,$iPerPage,array('user','blog'));
Возможно, не так красиво, но есть важное преимущество: мы явно указываем, что не к свойству текущего объекта обращаемся, а именно к модулю. И если даже какой-то новичок программер, не въехав в нюансы, создаст экшен в котором по ему ведомой логике объявит свойство $modules — он напрочь сломает вашу реализацию, а в моем варианте все будет работать.
0
В фреймворке Yii используется подобный подход. Там обращение к модулям идет так:
Yii::app()->module->doSomething();

Так получается очень естественно и более понятно. Полностью согласен. попробую реализовать так.

Про наследников — это я с LS спутал. Там LsObject не содержит __call метода.
+2
Если спроецировать на наши реалии, то, возможен такой вариант:
E::modules()->topic->doSomething();
Но надо смотреть, удастся ли сделать так, чтоб подружить с автокомплитом и PHPDoc, ибо это несомненно большой плюс для разработчиков, использующих умные IDE
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.