Реализация паттерна Компоновщик (Composite pattern) на php

в 13:15, , рубрики: patterns, php, метки: ,

Введение

На определенной стадии изучения серверного программирования мне захотелось написать свой простенький фреймворк. Я рассчитывал, что это поможет более глубоко понять идеологию MVC и Zend Framework в частности. Когда дело дошло до части представления и генерации html я вспомнил о паттерне Компоновщик (Composite pattern). Возможно я несколько исказил его применение, но мысль пошла оттуда.

К сути

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

К делу

Любой объект может содержать другой объект и т.д. Каждый объект обладает методом draw(), который отображает html-код своего объекта и вызывает метод draw() для всех дочерних объектов. Объекты могут быть разных типов (например: статья, список, пост, фотоальбом, шапка в конце концов). Конечно реализация метода draw() для каждого типа объектов будет своя. Хотелось бы выразить выше написанное кодом.

abstract class AbstractView {
    
    public $fillings;
    
    abstract public function draw();

    protected function insert($filling_name){
        if(isset($this->fillings[$filling_name])){
            $this->fillings[$filling_name]->draw();
        }
    } 

}

Метод insert() — метод, с помощью которого в методе draw() будут вызываться методы draw() дочерних объектов. Все дочерние объекты содержатся в ассоциативном массиве $fillings. Часто необходимо не генерировать html-код, а просто написать текст или число. Можно конечно Для таких целей наследовать объект View, метод draw() которого будет просто выводить необходимое значение, указанное, например, в конструкторе или дополнительном свойстве. Но это крайне не удобно и будет усложнять процесс формирования страницы. Поэтому создадим второй ассоциативный массив для хранения таких значений и метод для их вывода.

abstract class AbstractView {
    
    public $fillings;
    
    public $values;
    
    abstract public function draw();

    protected function insert($filling_name){
        if(isset($this->fillings[$filling_name])){
            $this->fillings[$filling_name]->draw();
        }
    }

    protected function write($value_name){
        if(isset($this->values[$value_name])){
            echo $this->values[$value_name];
        }
    }
    
}

Теперь напишем два простейших наследника для демонстрации. Один будет представлять шапку, а второй приветствие.

class LayoutView extends AbstractView {
    
    public function draw(){
        include '/layout.phtml';
    }
    
}

class IndexView extends AbstractView{
    
    public function draw(){
        include '/index.phtml';
    }
    
}

Что же такое файлы layout.phtml и index.phtml?
layout.phtml:

<!DOCTYPE html>
<html>
    <head>
        <title><?php $this->write('title')?></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <?php $this->insert('content'); ?>
    </body>
</html>

index.phtml:

<div>Hello, Habrahabr!</div>

Соединим все и с генерируем страницу:

$page = new LayoutView();
$page->values['title'] = "greating";
$page->fillings['content'] = new IndexView();
$page->draw();

Результат:

<!DOCTYPE html>
<html>
    <head>
        <title>greating</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <div>Hello, Habrahabr!</div>
    </body>
</html>

На данный момент такая реализация кажется мне удобной, но не исключено, что в скором времени я изменю свое мнение.

Как сказал Вольтер:
Я могу быть не согласным с Вашим мнением, но я готов отдать жизнь за Ваше право высказывать его.

Автор: tegoo


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js