PHP: Параметры в контексте

    Проблема:

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

    Например: каждый из объектов имеет метод Data(), который возвращает данные, необходимые для отображения объекта на странице (данные для шаблонизатора). Например объект $news класса News должен возвратить 5 последних новостей в приемлемом для шаблонизатора виде, например в виде массива. В свою очередь News::Data() обращается поочередно к объектам $newsPost->Data() (к вложенным объектам) для получения данных, касающихся отдельной новости (заголовок, дата и т.д.). Однако в некоторых случаях требуется, чтобы NewsPosе::Data() возвращал не полные данные, а лишь заголовок (например на главной странице сайта), а в другом случае требуется, чтобы вернулись все данные, включая ссылки на «новости по теме».

    Применение нескольких методов Data() ( DataShort(), DataFull() ) решило бы проблему, но хотелось бы более элегантного решения.

    Управляющий скрипт (допустим, конструктор страницы) «знает» в каком виде NewsPost::Data() должен вернуть данные, однако он не может напрямую взаимодействовать с объектами класса NewsPost, потому как это «забота» класса News. Следовательно, все, что может скрипт, это «попросить» $news «попросить» $newsPost вернуть «сокращенные» данные.

    Copy Source | Copy HTML
    1. $data = $news->Data('ask newsPost: return short data');


    тогда внутри News::Data() будет что-то:

    Copy Source | Copy HTML
    1. public function Data($askWhat)
    2. {
    3.         ...
    4.         $dataNews = $this->newsPost->Data('return short data');
    5.         ...
    6. }


    возможно, NewsPost::Data() обращается еще к каким-то объектам, допустим NewsPostBody::Data() для получения собственно статьи. Тогда, получив запрос «short data» на NewsPost::Data(), нужно сделать запрос с параметром «body short» к методу NewsPostBody::Data(). Не обязательно «управляющему скрипту» знать об этом, однако нельзя блокировать возможность напрямую «дать указание» объекту класса NewsPostBody вернуть «короткий вариант» статьи. То есть что-то вроде:

    Copy Source | Copy HTML
    1. $data = $news->Data('ask NewsPostBody: return short article');


    Однако, возможна ситуация, когда нужно обращаться к методу NewsPostBody::Data() с разными параметрами, причем за один запрос. Например когда News содержит «новости сайта», где NewsPostBody::Data() должен всегда возвращать «необрезанную» версию. То есть что-то вроде:

    Copy Source | Copy HTML
    1. $data = $news->Data('ask NewsPostBody(main news only): return short article');


    или:

    Copy Source | Copy HTML
    1. $data = $news->Data('return main news short body');


    но тогда News::Data() должен сделать примерно следующее:

    image

    или еще сложнее, когда для SiteNewsBody::Data() передаются другие параметры, например 'return SEO friendly body'.

    Вырисовываются некоторые технические требования:

    • управляющая составляющая (команда)
    • адресат (кому предназначается управляющая команда)
    • указание лишь класса объекта недопустимо, дополнительно требуется указать «адресата» на уровне «бизнес логики», или «контекст», так как возможно несколько значений одного и того же параметра для одного и того же класса (возможно даже объекта) в различных контекстах.


    Context::Class::Command

    Class, принципе, тоже в нектором свмсле «контекст», так что можно использовать «вложение контекстов»:

    ContextInContextInContex::Command

    Также следует подумать о расширенном использовании управляющей составляющей Command и предусмотреть возможность отделения аргумента от собственно «команды», напрмер «body_short=250», что может означать «ограничить статью до 250 символов». Имеем:

    ContextInContext::Command[=Argument(s)];

    Copy Source | Copy HTML
    1. $data = $news->Data(new Parameters('NewsBodyInMainNews:short=250;NewsBodyInSiteNews:seo_frendly;short=500;NewsBody:keepHtml'));


    Попробуем набросать интерфейс «в живую»:

    Copy Source | Copy HTML
    1. function News::Data($params)
    2. {
    3.             $newsData = new NewsData();
    4.  
    5.             // добавляем необходимые данные с параметрами в контексте 'SiteNews'
    6.             // должен использоваться параметр short=500 и seo_freindly и keepHtml
    7.  
    8.             $params->useContext('SiteNews');
    9.             $newsData->add( $this->getSiteNewsData($params) );
    10.             $params->dontUseContext('SiteNews');
    11.  
    12.             // добавляем необходимые данные с параметрами в контексте 'MainNews'
    13.             // должны использоваться только параметры short=250 и keepHtml
    14.  
    15.             $params->useContext('MainNews');
    16.  
    17.             $newsData->add( $this->getMainNewsData($params) );
    18.  
    19.             $params->dontUseContext('MainNews');
    20.  
    21.             return $newsData;
    22.  
    23. }
    24.  
    25. ...
    26.  
    27. function NewsBody::Data($params)
    28. {
    29.             $newsBodyData = new NewsBodyData();
    30.  
    31.             $params->useContext('NewsBody');
    32.  
    33.             $bodyLimit = $params->get('short');
    34.  
    35.             $newsBodyData->add('body', $this->getBody($bodyLimit));
    36.  
    37.             $params->dontUseContext('NewsBody');
    38.  
    39.             ...
    40.  
    41.             return $newsBodyData;
    42. }
    43.  
    44.  


    Почти все готово для начала разработки класса. Подведем итог и выделим основные моменты в будущем классе:

    • Значение параметра можно задать в определенном «контексте», причем контекст может быть «вложенным» (NewsBodyInMainNewsInPage не то же самое что NewsBodyInSiteNewsInPage);
    • метод get() должен возвращать значение параметра в текущем контексте, заданным в useContext();
    • возможность «объединения» параметров (двух объектов класса Parameters с возможностью управления перегрузкой значений для одинаковых параметров)
    • возможность задавать параметры строкой, вида 'ContextInContext:param=value;param2' или вызывая метод set($parameter, $value, $context)


    image

    Можно приступать к реализации.

    PS. топик был когда-то опубликован мною. Использую это паттэрн с тех пор, не задумываясь, новый ли это «велосипед» или нет. Возможно кому-то будет полезен. Буду рад обсудить кончно.

    Комментарии 11

      0
      Даже не знаю, красиво ли, например, «ограничивать статью до 250 символов» в самом коде, а не в шаблоне дизайна.

      Еще у меня вопросик, не по конкретно вашей схеме, а вообще.
      Если у нас используется объектная модель, где есть например класс NewsItem, который в конструкторе выбирает из базы себя по id, и есть класс NewsLine, который содержит несколько NewsItem. С точки зрения ООП красиво чтоб каждый NewsItem сам себя выбрал из базы. Но тогда же будет много запросов к базе, вместо одного, которым можно было бы выбрать сразу все нужные новости.
      Вопрос в том, как обычно это реализовано — как классы взаимодействуют с базой? Просто в моем движке магазина не бывает более 10 запросов на странице, а у других я видел и сотни. Как же так? Стоит ли жертвовать скоростью ради… а ради чего, собственно у других движков столько запросов?
        0
        «ограничивать статью» наверное плохой пример, слишком простой… чаще всего это используется, если надо, например, вывести список товаров (релузьтаты поиска). В этом случае не нужно «выдавать» шаблонизатору всю карточку товара целиком (все поля) — при 100 результатах на странице может быть переполнение памяти.

        про 10 запросов… да, именно так — на странице может быть больше сотни запросов. Чаще всего разница в скорости небольшая, но если требуется, то оптимизация не больно хитрая — очень неплохо спасает memcached и тонкая настройка MySQL сервера.

        Ради чего?.. Ради качества кода. Код получается очень гибкийи понятный.
          0
          вы не с той стороны подходите к проблеме. список товаров в каталоге, в результате поиска или списке новых товаров — одинаковые или очень похожи. карточка товара — это уже НЕ СПИСОК товаров а экземпляр. Для него правильнее сделать отдельный запрос, а не пытаться получить его через регресс интерфейса получения списков. ведь очевидно что ваш подход плодит кучу лишних запросов к БД и лишает гибкости кстати.
          поэтому я обычно делаю один метод который генерирует запрос, учитывая структуру таблицы товаров и специфику вывода в шаблон, и далее через общие интерфейсы генерится нужный вид отображения… тогда можно выбирать ОДНИм запросом, который сразу вытаскивает все что нужно, для данной сущности и контекста использования
            0
            я применяю ООП, так что у меня каждая сущность занимается «своими делами». да, у меня сотня (ато и больше) запросов на странице — не вижу в этом проблемы. поэтому мне и нужноданное решение.
        +1
        Возможна другая ситуация, другой случай — признаки паттерна Стратегия.
        «Управляющий скрипт (допустим, конструктор страницы) «знает» в каком виде NewsPost::Data() должен вернуть данные, однако он не может напрямую взаимодействовать с объектами класса NewsPost, потому как это «забота» класса News. Следовательно, все, что может скрипт, это» — инстанцировать нужную стратегию.

        Имхо, Команда для приведенного случая тяжеловата — у нас простая, по разному отображаемая, коллекция.
        Еще несколько смущает «NewsPost» и «NewsPostBody» — это новость и ее текст? Стоит ли делать такую подробную иерархию? Может быть из-за этого понадобился паттерн поведения?
          0
          Пример с News — всего лишь пример. Хотел попроще, да видно перестарался.

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

          т.е. я устанавливаю параметр для «всего, что внутри News cut=true» в одном месте программы, после этого, находясь в контексте «News» у параметра cut будет значение true. Если где-то «дальше» я установлю для этого параметра другое значение в дроугом контексте, допустим «News/NewsPost cut=false» то в «News» он по-прежнему останется «true» а в NewsPost, NewsPostBody будет иметь значение false.

          Опять же, более реальный пример с результатами поиска: я не хочу врезультатх поиска полную карточку товара со всеми полями, поэтому устанавливаю «Product/Profile:fieldsetMode=short» когда Profile формирует массив с полями он проверят значение fieldsetMode и соответсвенно возвращает «сокращенный» набор.

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

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

            пример кода:

            на странице:


            $this->add( Auctions::getFeatured(), new Parameters('Auction/Vehicle/VehicleProfile:mode=short') );

            в классе VehicleProfile:

            $params->useContext('VehicleProfile');
            if( $params->get('mode') == 'short') $fieldsParam= new Parameters('Fields:Manufacturer,Model,Year');

            $data->add( $this->Data_Fields( $fieldsParam ) );

              0
              а теперь представьте,
              что нужно в зависимости от mode join-нить разные таблицы с разными атрибутами товаров например
              и в каждой из этих таблиц еще куча условий в Where

              незнаю как у вас внутри реализованы Data_Fields и контексты, но думаю что даже тут уже вы столкнетесь с ограничениями. в которые вы сами себя загоняете таким подходом.

              imho ваш подход решает достаточно частный случай. и с большой натяжкой можно считать его очень гибким и универсальным

                0
                насколько я понял, вы не используете ООП и инкапсуляцию, так что для вас, несомненно, такой подход будет «не гибким»
                  0
                  >так что для вас, такой подход будет «не гибким
                  Идея слабо зависит от реализации.
                  Если решение неуниверсально, то её не спасет ни ООП ни АОП ни чтото другое.
                  ведь это всего лишь методики программирования, они не делают алгоритм лучше.

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

                  >вы не используете ООП
                  я раньше тоже строил приложения на основе «многоэтажных» классов и интерфейсов, все это было жутко взаимозависимо, громоздко и ресурсоемко. но мне тогда казалось что это и есть true ООП)
                  Но, по настоящему стало легко и свободно кодить когда я упростил все что можно, заменил часть объектов массивами, оставшиеся классы сделал максимально независимыми и т.д.
                  Удивительно, но скорость и удобство разработки даже возросло)

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.