PHP

Создание Фрэймворка на PHP5

Всё больше и больше нам приходится создавать динамических и интерактивных сайтов. Именно поэтому множество разработчиков начинают применять в своей работе различные фрэймворки. Существует немало хороших фрэймворков, однако собственный экземпляр может быть подстроен полностью под ваши нужды и значительно увеличить скорость разработки проекта, конечно же, если вы знаете, как с ним взаимодействовать. В этой небольшой серии уроков, мы покажем вам, как создать собственный фрэймворк на PHP5! В этом уроке мы начнём с планирования и закончим реализацией структуры.

*Заметка: при запуске демо вы должны увидеть “PCA Framework version 0.1 the HTML output”, что продемонстрирует успешную работу нашего приложения на первой стадии его разработки.
Шаг 1: Немного об этой серии уроков

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

Данная серия уроков предназначена для системы, с помощью которой вы сможете создавать сайты. Ещё один плюс в том, что в последствии вам никто не будет мешать использовать данную систему в своих собственных целях и добавлять функционал.

В течении нескольких недель мы рассмотрим следующие темы:

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

Шаг 2: Шаблоны проектирования и их использование в нашем фрэймворке

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

Шаблоны проектирования - это как раз то, к чему нужно обратиться при появлении подобной задачи. Для того чтобы подобрать необходимый шаблон для нашего проекта, нужно перебрать целую кучу подобных инструментов функциональной основы. Всё это нужно для того чтобы наша система была флексибильна! Ладно, больше не томлю! В этом уроке мы рассмотрим шаблоны проектирования под названием Singleton и Registry.
Шаг 3: Файлы и папки

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

Каталог для хранения классов или файлов с функциями
Каталог для бизнес логики
Каталог для элементов дизайна

Для нас эта инфа - прекрасная отправная точка, потому как файлы, которые относятся к одной из этих 3х категорий, можно объединить в группы. Взгляните на структуру папок, которая приведена ниже. Чуть позже мы всё это детально обсудим.

Учтите, что каталоги .settings .project были созданы IDE, который я использую, и не претендуют на то, чтобы вы их создавали у себя

Базовые функции и объекты, такие как подключение к базе данных, аутентификация, обработчик шаблонов, объекты работающие с электронными сообщениями, объекты, обрабатывающие электронные сообщения, должны располагаться все вместе в каталоге objects, который в свою очередь располагается в папке PCARegistry. Это позволит нам отделить логику, относящуюся к Registry (по которому мы пробежимся вскользь) от самого объекта Registry.

Теперь нам надо определиться, в каком месте мы будем хранить файлы, отвечающие за бизнес-логику нашего фрэймворка. Пожалуй, папка controllers прекрасно подойдёт для этой цели. Каждый метод, являющийся с точки зрения приложения немаловажным (вывод бизнес списков, создание и управление данными, генератор всевозможных галерей и т.д.) будет представлять из себя отдельный контроллер. В этом уроке мы не собираемся ничего такого реализовывать, однако важно, чтобы вы понимали, для чего необходим данный каталог, таким образом, вы с лёгкостью сможете понять, как работает сам фрэймворк.

Элементы дизайна сайта и его шаблоны будут храниться в каталоге skins. Т.к. в перспективе мы хотим использовать несколько дизайнов (например, для того чтобы пользователи сами выбирали, в каком стиле они хотят видеть сайт, или различный дизайн в зависимости от времён года или в честь какого-то праздника), то каждый из этих шаблонов будет храниться в отдельном каталоге.
Шаг 4: Registry

Сердцевина нашего фрэймворка будет состоять из метода, взаимодействующего с базами данных, пользовательской авторизации и т.д. Имплементация шаблона проектирования Registry позволит нам хранить все эти методы централизованно, предоставляя лёгкий способ взаимодействия нашего фрэймворка с другими системами.

Шаблон проектирования Registry использует ссылки на объекты и в своём функционале очень похож на телефонную книгу, которая хранит и извлекает контактные данные. Этот механизм мы будем использовать, для того чтобы иметь доступ к объектам, настройкам системы, а также при необходимости передавать данные и другую информацию в любую часть приложения.

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

Ниже, в файле registry.class.php мы побыстренькому пройдёмся по тому, как это всё работает.
<?php
/**
 * Объект PCARegistry
 * Имплементация шаблонов проектирования Registry и Singleton
 *
 * @версия 0.1
*/
class PCARegistry {

    /**
     * Массив для объектов
     * @access private
     */
    private static $objects = array();

    /**
     * Массив для настроек
     * @access private
     */
    private static $settings = array();

    /**
     * Название фрэймворка понятное для человека
     * @access private
     */
    private static $frameworkName = 'PCA Framework version 0.1';

    /**
     * Экземпляр нашего класса
     * @access private
     */
    private static $instance;

    /**
     * Закрытый конструктор (для того чтобы нельзя было вызвать напрямую)
     * @access private
     */
    private function __construct()
    {

    }

    /**
     * метод singleton для получения доступа к объекту
     * @access public
     * @return
     */
    public static function singleton()
    {
        if( !isset( self::$instance ) )
        {
            $obj = __CLASS__;
            self::$instance = new $obj;
        }

        return self::$instance;
    }

    /**
     * запрещаем клонирование объекта
     */
    public function __clone()
    {
        trigger_error( 'Cloning the registry is not permitted', E_USER_ERROR );
    }

    /**
     * Подключаем класс
     * @param String $object имя объекта
     * @param String $key ключ для массива
     * @return void
     */
    public function storeObject( $object, $key )
    {
        require_once('objects/' . $object . '.class.php');
        self::$objects[ $key ] = new $object( self::$instance );
    }

    /**
     * Получаем объект
     * @param String $key ключ для массива
     * @return object
     */
    public function getObject( $key )
    {
        if( is_object ( self::$objects[ $key ] ) )
        {
            return self::$objects[ $key ];
        }
    }

    /**
     * Передаём настройки
     * @param String $data
     * @param String $key для массива
     * @return void
     */
    public function storeSetting( $data, $key )
    {
        self::$settings[ $key ] = $data;

    }

    /**
     * Получаем настройки
     * @param String $key ключ для массива
     * @return void
     */
    public function getSetting( $key )
    {
        return self::$settings[ $key ];
    }

    /**
     * Получаем название фрэймворка
     * @return String
     */
    public function getFrameworkName()
    {
        return self::$frameworkName;
    }

}

?>

Итак, как же работает Registry?

Все объекты хранятся в массиве.

Когда в Registry передаётся новый объект, то автоматически подключается класс, где хранится функциональность данного объекта.

Доступ к данным объектам осуществляется при помощи “ключа” объекта, передаваемого в метод getObject.

Вы спросите: ну как же происходит предотвращение создания копии объекта Registry?

Наш конструктор имеет модификатор доступа private, для того чтобы предотвратить создание объекта класса напрямую.

Попытка клонировать объект приведёт к возникновению ошибки.

Если нам необходимо получить доступ к объекту в какой-то другой части нашего фрэймворка, или по какой-либо причине не можем достучаться к нему напрямую, мы можем использовать метод singleton ( PCARegistry::singleton() ) для получения экземпляра Registry.
Шаг 5: index.php

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

ЧПУ или другими словами, человеко-понятные урлы можно встретить уже практически в любом веб приложении. Единственный способ реализации такого же эффекта, заключается в том, чтобы все запросы шли через файл index.php. В будущих уроках, мы создадим специальный файл .htaccess, для того чтобы любая часть нашего приложения перенаправляла пользователя на файл index.php.

Ниже вы можете увидеть код файла index.php. Конечно же это не вся его функциональность. Это то, что нужно нам на данный момент.
<?php
// Первое, что нужно сделать - начать сессию
session_start();

// определим некоторые константы
// такие как путь к корню нашего приложения
define( "APP_PATH", dirname( __FILE__ ) ."/" );
// а эту для того, чтобы убедиться, что скрипты не будут вызываться за пределами нашего фрэймворка
define( "PCAFW", true );

/**
 * Магическая функция autoload
 * используется для того чтобы подключить нужный нам контроллер
 * @param String название класса
 */
function __autoload( $class_name )
{
    require_once('controllers/' . $class_name . '/' . $class_name . '.php' );
}

// подключаем то, что создали ранее
require_once('PCARegistry/pcaregistry.class.php');
$registry = PCARegistry::singleton();

// выведем название фрэймворка, для того чтобы убедиться, что всё работает
print $registry->getFrameworkName();

exit();

?>

Итак…что же делает этот файл на данный момент?

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

На то, как работает объект Registry в нашем фрэймворке, мы создадим dummy класс. Мы можем это продемонстрировать благодаря классу template.class.php, который хранится в каталоге PCARegsitry/objects. Для этого необходимо добавить новый код в файл index.php.

После того, как мы создали $registry, можете вписать следующее:
$registry->storeObject('template','template');

Если в классе, который мы только что упомянули реализован метод generateOutput, то мы без особых затруднений можем вызывать его в index.php:
$registry->getObject('template')->generateOutput();

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

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

MVC

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

Так что же такое MVC? MVC - это шаблон проектирования (такой же как Singleton и Registry, которые мы рассмотрели в первой части). Это аббревиатура от Model View Controller. Главная цель MVC заключается в разделении бизнес логики и пользовательского интерфейса друг от друга. Теперь пришло самое время немного изменить структуру нашего проекта и создать каталог “models”. В моделях будет содержаться вся бизнес логика, а контроллеры будут взаимодействовать с ней и пользователем (допустим обрабатывать введённые данные). Метод __autoload изменять не нужно.

Обработчик для работы с БД

Многие сайты, которые написаны на PHP, также работают с различными системами базами данных, самая популярная из них - это MySQL. Если мы будет хранить всё, что связано с БД в одном месте, то (в теории) мы с лёгкостью можем сменить используемую технологию. Также мы сможем упростить некоторые задачи, такие как вставка, редактирование и удаление записей из базы.

Итак, что же будет делать наш обработчик:

Управление подключениями к базе данных
Попытка абстрагироваться от самой базы данных
Кэшировать запросы для многократного использования
Упростить основные операции взаимодействия с базой

Теперь давайте взглянем на код.
 	<?php
	 
 	class database {
 	 
 	    /**
	     * Массив для множества подключений.
 	     * Используется редко, но бывает очень необходим
 	     */
 	    private $connections = array();
 	 
 	    /**
 	     * Сообщает БД, какое подключение использовать
 	     * setActiveConnection($id) для изменения
 	     */
 	    private $activeConnection = 0;
 	 
 	    /**
 	     * Кэш для запросов
 	     */
 	    private $queryCache = array();
 	 
 	    /**
 	     * Кэш для сформированных данных
 	     */
 	    private $dataCache = array();
 	 
 	    /**
 	     * Запись последнего запроса
 	     */
 	    private $last;
 	 
 	    public function __construct()
 	    {
 	 
 	    }
 	 
 	    /**
 	     * Создание нового подключения
 	     * @param String называние хоста
 	     * @param String пользователь
 	     * @param String пароль
 	     * @param String имя базы данных
 	     * @return int id нового подключения
 	     */
 	    public function newConnection( $host, $user, $password, $database )
 	    {
 	        $this->connections[] = new mysqli( $host, $user, $password, $database );
 	        $connection_id = count( $this->connections )-1;
 	        if( mysqli_connect_errno() )
 	        {
 	            trigger_error('Error connecting to host. '.$this->connections[$connection_id]->error, E_USER_ERROR);
 	        }  
 	 
 	        return $connection_id;
 	    }
 	 
 	    /**
 	     * Закрытие активного подключения
 	     * @return void
 	     */
 	    public function closeConnection()
 	    {
 	        $this->connections[$this->activeConnection]->close();
 	    }
 	 
 	    /**
 	     * Переключение между активными подключениями
 	     * @param int новый id
 	     * @return void
 	     */
 	    public function setActiveConnection( int $new )
 	    {
 	        $this->activeConnection = $new;
 	    }
 	 
 	    /**
 	     * Запись запроса в кэш для дальнейшего использования
 	     * @param String строка запроса
 	     * @return кэш
 	     */
 	    public function cacheQuery( $queryStr )
 	    {
 	        if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
 	        {
 	            trigger_error('Error executing and caching query: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
 	            return -1;
 	        }
	        else
 	        {
 	            $this->queryCache[] = $result;
 	            return count($this->queryCache)-1;
 	        }
 	    }
 	 
 	    /**
 	     * Достать набор строк из кэша
 	     * @param int указатель на кэш
 	     * @return int количество строк
 	     */
 	    public function numRowsFromCache( $cache_id )
 	    {
 	        return $this->queryCache[$cache_id]->num_rows;
 	    }
 	 
 	    /**
 	     * Получить данные из кэша
 	     * @param int указатель на кэш
	     * @return array строка
 	     */
 	    public function resultsFromCache( $cache_id )
 	    {
 	        return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC);
 	    }
 	 
 	    /**
 	     * Записать данные в кэш
 	     * @param array данные
 	     * @return int указатель на данные в кэше
 	     */
 	    public function cacheData( $data )
 	    {
 	        $this->dataCache[] = $data;
 	        return count( $this->dataCache )-1;
 	    }
 	 
 	    /**
 	     * Достаём данные из кэша
 	     * @param int указатель
 	     * @return array данные
 	     */
 	    public function dataFromCache( $cache_id )
 	    {
 	        return $this->dataCache[$cache_id];
 	    }
 	 
 	    /**
 	     * Удалить данные из базы
	     * @param String таблица
 	     * @param String условие удаления
 	     * @param int количество строк для удаления
 	     * @return void
 	     */
 	    public function deleteRecords( $table, $condition, $limit )
	    {
 	        $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit;
 	        $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}";
 	        $this->executeQuery( $delete );
 	    }
 	 
 	    /**
 	     * Обновление записей
 	     * @param String название таблицы
 	     * @param array значения в виде field => value
 	     * @param String условие
 	     * @return bool
 	     */
 	    public function updateRecords( $table, $changes, $condition )
 	    {
 	        $update = "UPDATE " . $table . " SET ";
 	        foreach( $changes as $field => $value )
 	        {
 	            $update .= "`" . $field . "`='{$value}',";
	        }
 	 
 	        $update = substr($update, 0, -1);
	        if( $condition != '' )
	        {
 	            $update .= "WHERE " . $condition;
 	        }
 	 
 	        $this->executeQuery( $update );
 	 
 	        return true;
 	 
 	    }
 	 
 	    /**
 	     * Вставка в базу
 	     * @param String название таблицы
 	     * @param array данные в виде field => value
 	     * @return bool
 	     */
	    public function insertRecords( $table, $data )
 	    {
 	        $fields  = "";
 	        $values = "";
 	 
 	        foreach ($data as $f => $v)
 	        {
 	 
 	            $fields  .= "`$f`,";
 	            $values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',";
 	 
 	        }
 	 
 	        $fields = substr($fields, 0, -1);
 	        $values = substr($values, 0, -1);
 	 
 	        $insert = "INSERT INTO $table ({$fields}) VALUES({$values})";
 	        $this->executeQuery( $insert );
 	        return true;
 	    }
 	 
 	    /**
 	     * Выполнение запроса
 	     * @param String запрос
 	     * @return void
 	     */
 	    public function executeQuery( $queryStr )
 	    {
 	        if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
 	        {
 	            trigger_error('Error executing query: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
	        }
 	        else
 	        {
 	            $this->last = $result;
 	        }
 	 
 	    }
 	 
 	    /**
 	     * Получить строки из последнего выполненного запроса (исключая закэшированные)
 	     * @return array
 	     */
 	    public function getRows()
 	    {
 	        return $this->last->fetch_array(MYSQLI_ASSOC);
 	    }
 	 
 	    /**
 	     * Извлечение числа полученных строк прошлого запроса
 	     * @return int число строк
 	     */
 	    public function affectedRows()
 	    {
 	        return $this->$this->connections[$this->activeConnection]->affected_rows;
 	    }
 	 
 	    /**
 	     * Фильтрация данных
 	     * @param String данные на фильтрацию
 	     * @return String отфильтрованные данные
 	     */
 	    public function sanitizeData( $data )
 	    {
 	        return $this->connections[$this->activeConnection]->real_escape_string( $data );
 	    }
 	 
 	    /**
 	     * Деконструкция объекта
 	     * закрытие всех подключений
 	     */
 	    public function __deconstruct()
 	    {
 	        foreach( $this->connections as $connection )
 	        {
 	            $connection->close();
 	        }
 	    }
 	}
 	?>


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

Методы для удаления, обновления и вставки новых записей позволят нам более простым способом решать некоторые задачи (к тому же, их функционал всегда можно расширить). Все что нам нужно - это только передать название таблицы, массив с данными, число для лимита и условие. Запросы также можно кэшировать, чтобы использовать их в дальнейшем. Рассмотрим пример использования.
 	// вставка записи
 	$registry->getObject('db')->insertRecords( 'testTable', array('name'=>'Michael' ) );
 	// обновление записи
 	$registry->getObject('db')->updateRecords( 'testTable', array('name'=>'MichaelP' ), 'ID=2' );
 	// удаление записи
 	$registry->getObject('db')->deleteRecords( 'testTable', "name='MichaelP'", 5 );

Также мы без особых затруднений можем работать с несколькими подключениями (к примеру следующий фрагмент кода позволяет нам удалить данные из двух разных баз).
 	// создаём второе подключение (представим, что одно у нас уже есть)
 	$newConnection = $registry->getObject('db')->newConnection('localhost', 'root', 'password', 'secondDB');
 	// удаляем из первой базы
 	$registry->getObject('db')->deleteRecords( 'testTable', "name='MichaelP'", 5 );
 	// меняем подключение
 	$registry->getObject('db')->setActiveConnection( $newConnection );
 	// удаляем из второй базы
 	$registry->getObject('db')->deleteRecords( 'testTable', "name='MichaelP'", 5 );
 	// снова меняем подключение
 	$registry->getObject('db')->setActiveConnection( 0 );

Как и что можно добавить в этот класс?

Полную абстракцию
Применить наследование. Создать для каждой технологии баз данных свой отдельный интерфейс.
Хранить ID подключения на ряду с закэшированным запросом
Улучшить фильтрацию данных (согласно каким-то критериям)

Менеджер шаблонов

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

Для того чтобы не усложнять нашу задачу, мы организуем работу так, чтобы менеджер шаблонов мог управлять объектом страницы.
 	<?php
 	 
 	// устранение доступа напрямую
 	if ( ! defined( 'PCAFW' ) )
 	{
 	    echo 'This file can only be called via the main index.php file, and not directly';
 	    exit();
 	}
 	 
 	/**
 	 * Класс менеджера шаблонов
 	 */
 	class template {
 	 
 	    private $page;
 	 
 	    public function __construct()
 	    {
 	        include( APP_PATH . '/PCARegistry/objects/page.class.php');
 	        $this->page = new Page();
 	 
 	    }
 	 
 	    /**
 	     * Добавляем шаблон на страницу
 	     * @param String $tag тег где будет вставлен шаблон {hello}
 	     * @param String $bit путь или имя к файлу с шаблоном
 	     * @return void
 	     */
 	    public function addTemplateBit( $tag, $bit )
 	    {
 	        if( strpos( $bit, 'skins/' ) === false )
 	        {
 	            $bit = 'skins/' . PCARegistry::getSetting('skin') . '/templates/' . $bit;
 	        }
 	        $this->page->addTemplateBit( $tag, $bit );
 	    }
 	 
 	    /**
 	     * Вставка шаблонов на страницу
 	     * Происходит обновление контента страницы
 	     * @return void
 	     */
 	    private function replaceBits()
 	    {
 	        $bits = $this->page->getBits();
 	        foreach( $bits as $tag => $template )
 	        {
 	            $templateContent = file_get_contents( $bit );
 	            $newContent = str_replace( '{' . $tag . '}', $templateContent, $this->page->getContent() );
 	            $this->page->setContent( $newContent );
 	        }
 	    }
 	 
 	    /**
 	     * Замена тегов на контент
 	     * @return void
 	     */
 	    private function replaceTags()
 	    {
 	        $tags = $this->page->getTags();
 	 
 	        foreach( $tags as $tag => $data )
 	        {
 	            if( is_array( $data ) )
 	            {
 	 
 	                if( $data[0] == 'SQL' )
 	                {
 	                    $this->replaceDBTags( $tag, $data[1] );
 	                }
 	                elseif( $data[0] == 'DATA' )
 	                {
 	                    $this->replaceDataTags( $tag, $data[1] );
 	                }
 	            }
 	            else
 	            {
 	                $newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() );
 	                $this->page->setContent( $newContent );
 	            }
 	        }
 	    }
 	 
 	    /**
 	     * Замена контента на странице данными из БД
 	     * @param String $tag тег области замены
 	     * @param int $cacheId ID закэшированного запроса
 	     * @return void
 	     */
 	    private function replaceDBTags( $tag, $cacheId )
 	    {
 	        $block = '';
 	        $blockOld = $this->page->getBlock( $tag );
 	 
 	        while ($tags = PCARegistry::getObject('db')->resultsFromCache( $cacheId ) )
	        {
 	            $blockNew = $blockOld;
 	            foreach ($tags as $ntag => $data)
 	            {
 	                $blockNew = str_replace("{" . $ntag . "}", $data, $blockNew);
 	            }
 	            $block .= $blockNew;
	        }
 	        $pageContent = $this->page->getContent();
 	        $newContent = str_replace( '<!-- START ' . $tag . ' -->' . $blockOld . '<!-- END ' . $tag . ' -->', $block, $pageContent );
 	        $this->page->setContent( $newContent );
 	    }
 	 
 	    /**
 	     * Замена контента страницы данными из кэша
 	     * @param String $tag тег области замены
 	     * @param int $cacheId ID закэшированных данных
 	     * @return void
 	     */
 	    private function replaceDataTags( $tag, $cacheId )
 	    {
 	        $block = $this->page->getBlock( $tag );
 	        $blockOld = $block;
 	        while ($tags = PCARegistry::getObject('db')->dataFromCache( $cacheId ) )
 	        {
 	            foreach ($tags as $tag => $data)
 	            {
 	                $blockNew = $blockOld;
 	                $blockNew = str_replace("{" . $tag . "}", $data, $blockNew);
 	            }
 	            $block .= $blockNew;
 	        }
 	        $pageContent = $this->page->getContent();
 	        $newContent = str_replace( $blockOld, $block, $pageContent );
 	        $this->page->setContent( $newContent );
 	    }
 	 
 	    /**
 	     * Получить объект страницы
 	     * @return Object
 	     */
 	    public function getPage()
 	    {
 	        return $this->page;
 	    }
 	 
 	    /**
 	     * Отобразить содержание страницы в зависимости от числа шаблонов
 	     * @return void
 	     */
 	    public function buildFromTemplates()
 	    {
 	        $bits = func_get_args();
 	        $content = "";
 	        foreach( $bits as $bit )
 	        {
 	 
 	            if( strpos( $bit, 'skins/' ) === false )
 	            {
 	                $bit = 'skins/' . PCARegistry::getSetting('skin') . '/templates/' . $bit;
 	            }
 	            if( file_exists( $bit ) == true )
 	            {
 	                $content .= file_get_contents( $bit );
	            }
 	 
 	        }
 	        $this->page->setContent( $content );
 	    }
	 
 	    /**
 	     * Сконвертировать массив с данными в теги
 	     * @param array данные
 	     * @param string префикс
 	     * @return void
 	     */
 	    public function dataToTags( $data, $prefix )
 	    {
 	        foreach( $data as $key => $content )
 	        {
 	            $this->page->addTag( $key.$prefix, $content);
 	        }
 	    }
 	 
 	    public function parseTitle()
 	    {
 	        $newContent = str_replace('<title>', '<title>'. $page->getTitle(), $this->page->getContent() );
 	        $this->page->setContent( $newContent );
 	    }
	 
 	    /**
 	     * Формируем страницу
 	     * @return void
 	     */
 	    public function parseOutput()
 	    {
 	        $this->replaceBits();
 	        $this->replaceTags();
 	        $this->parseTitle();
	    }
	 
 	}
 	?>

Данный класс создаёт объект страницы в зависимости от шаблона. Объект страницы содержит контент и информацию, которая необходима для создания HTML страницы. Затем мы используем метод buildFromTemplate(‘templatefile.tpl.php’, ‘templatefile2.tpl.php’) для получения начального содержания нашей страницы. Данный метод принимает целый набор названий шаблонов и производит последовательную обработку.

Управляет содержанием страницы, помогая объекту страницы вести учёт данных, которые будут заменены, а также применение новых шаблонов, которые добавляются при помощи (addTemplateBit(‘userbar’,'usertoolsbar.tpl.php’)).

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

Объект страницы управляется менеджером шаблонов и предназначен для всей информации о странице. Разделение этих объектов упрощает их использование, что опять же открывает нам возможность расширять функциональность данных классов.
 	<?php
 	 
 	/**
 	 * Это объект страницы
 	 * Предназначен для добавления какой-то новой функциональности
 	 * К примеру, добавления пароля, специальных файлов css/js, и т.д.
 	 */
 	class page {
 	 
 	    private $css = array();
 	    private $js = array();
 	    private $bodyTag = '';
 	    private $bodyTagInsert = '';
 	 
 	    // будующая функциональность
 	    private $authorised = true;
 	    private $password = '';
 	 
 	    // элементы страницы
 	    private $title = '';
 	    private $tags = array();
 	    private $postParseTags = array();
 	    private $bits = array();
 	    private $content = "";
 	 
 	    /**
 	     * Конструктор...
 	     */
 	    function __construct() { }
 	 
 	    public function getTitle()
 	    {
 	        return $this->title;
 	    }
 	 
 	    public function setPassword( $password )
 	    {
 	        $this->password = $password;
 	    }
	 
	    public function setTitle( $title )
 	    {
 	        $this->title = $title;
 	    }
 	 
 	    public function setContent( $content )
 	    {
 	        $this->content = $content;
 	    }
 	 
	    public function addTag( $key, $data )
 	    {
 	        $this->tags[$key] = $data;
 	    }
 	 
 	    public function getTags()
 	    {
 	        return $this->tags;
 	    }
 	 
 	    public function addPPTag( $key, $data )
 	    {
 	        $this->postParseTags[$key] = $data;
 	    }
 	 
 	    /**
 	     * Получить все теги после первого анализа
 	     * @return array
 	     */
 	    public function getPPTags()
	    {
 	        return $this->postParseTags;
 	    }
 	 
 	    /**
 	     * Добавления шаблона, но не содержания
 	     * @param String куда добавить шаблон
 	     * @param String имя файла с шаблоном
 	     * @return void
 	     */
 	    public function addTemplateBit( $tag, $bit )
 	    {
 	        $this->bits[ $tag ] = $bit;
 	    }
 	 
 	    /**
 	     * Получает шаблоны присутствующие на странице
 	     * @return array массив тегов
 	     */
 	    public function getBits()
 	    {
 	        return $this->bits;
 	    }
 	 
 	    /**
 	     *Получить фрагмент содержимого страницы
 	     * @param String блок ( <!-- START tag --> block <!-- END tag --> )
 	     * @return String контент блока
 	     */
 	    public function getBlock( $tag )
 	    {
 	        preg_match ('#<!-- START '. $tag . ' -->(.+?)<!-- END '. $tag . ' -->#si', $this->content, $tor);
 	 
 	        $tor = str_replace ('<!-- START '. $tag . ' -->', "", $tor[0]);
 	        $tor = str_replace ('<!-- END '  . $tag . ' -->', "", $tor);
 	 
 	        return $tor;
 	    }
 	 
 	    public function getContent()
 	    {
 	        return $this->content;
 	    }
 	 
 	}
 	?>

Загрузка объектов

Теперь самое время сообщить нашему фрэймворку о новой функциональности, которую мы только что написали. Я создал специальный метод для объекта PCARegistry с именем loadCoreObjects, который загружает объекты. Это необходимо для того, чтобы мы могли иметь доступ к объектам из файла index.php.
 	public function storeCoreObjects()
 	{
 	    $this->storeObject('database', 'db' );
	    $this->storeObject('template', 'template' );
 	}

Какие-то данные

Для того чтобы продемонстрировать работу нашего фрэймворка, нам необходимо создать базу данных и таблицу. Сейчас нам подойдёт самая простая структура: ID, имя, email.

Честно говоря, нам нужно всего несколько строк из таблицы!
Шаблон на скорую руку

Для того чтобы продемонстрировать работу фрэймворка в полной мере, создадим небольшой шаблон.
 	<html>
 	<head>
 	    <title> Powered by PCA Framework</title>
 	</head>
 	<body>
 	<h1>Our Members</h1>
 	<p>Below is a list of our members:</p>
 	<ul>
 	<!-- START members -->
 	<li>{name} {email}</li>
 	<!-- END members -->
 	</ul>
 	</body>
 	</html>

START и END - это блок (он будет выбираться при помощи метода getBlock()). Как раз в этом месте менеджер шаблона будет вставлять данные из базы.
Используем фрэймворк

Теперь соберём всё вместе в нашем index.php:
 	require_once('PCARegistry/pcaregistry.class.php');
 	$registry = PCARegistry::singleton();
	 
 	$registry->storeCoreObjects();
 	 
 	$registry->getObject('db')->newConnection('localhost', 'root', '', 'pcaframework');
 	 
 	$registry->storeSetting('default', 'skin');
 	 
 	// подключаем шаблон
 	$registry->getObject('template')->buildFromTemplates('main.tpl.php');
 	 
 	// кэшируем запрос
 	$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM members');
 	 
 	// добавляем тег
 	$registry->getObject('template')->getPage()->addTag('members', array('SQL', $cache) );
 	 
 	// назначаем заголовок страницы
 	$registry->getObject('template')->getPage()->setTitle('Our members');
 	 
 	// парсим и выводим
 	$registry->getObject('template')->parseOutput();
 	print $registry->getObject('template')->getPage()->getContent();

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

Итак, у нас есть наши файлы с кодом, рассортированных в каталоги и несколько объектов, доступ к которым получаем из центра управления. Одним из этих объектов является обработчик шаблонов, который позволяет нам без особых сил генерировать HTML код. Также мы можем с его помощью выводить изображения, css и другие шаблоны, которые формируют дизайн нашего приложения.
Шаг 1: Что нам необходимо для дизайна

Дизайн фронт энда не должен быть слишком уж сложным. Будет круто, если структура HTML шаблона будет пригодна для всех фичей, которые поддерживаются нашим фрэймворком. Минимум, который нам нужен - это:

Основной блок, где будет располагаться инфа. Назовём его #content
Также нам понадобится несколько колонок, которые будут храниться в #content
Наверняка нам понадобится таблица.
Нумерованные и ненумерованные списки (и другие виды списков).
Изображение. Я привык использовать технику присвоения специального стиля для фоток в HTML коде; к примеру, вот так: <img class=”photo” src=”images/photo.jpg” alt=”Photograph” />.
Формы для получения данных.
<head>

А теперь настало время создать простую структуру нашей XHTML страницы. Начнём мы вот с чего:
 	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 	<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 	<head>
 	<title>{pagetitle}</title>
 	<meta name="description" content="{metadesc}" />
 	<meta name="keywords" content="{metakey}" />
 	<style type="text/css" title="Default page style" media="screen"><!--@import "skins/fmwk/style.css";--></style>
 	<link rel="icon" href="favicon.ico" type="image/x-icon" />
 	<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
 	</head>
 	<body>

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

lang. В этом же фрагменте кода мы подключаем наши основные таблицы стилей. В добавок, вписываем теги мета для ключевых слов и описания, которые будут меняться в зависимости от страницы. Заменитель {pagetitle} будет использоваться для того, чтобы вставлять название страницы, которую будет просматривать пользователь.
<body>

Теперь мы можем перейти непосредственно к телу нашей XHTML страницы, для того чтобы дополнить структуру дизайна нашего фрэймворка. Сейчас мы ничего не будем заполнять, а просто создадим несколько блоков, которые часто используются разработчиками. Добавим блоки header, content, column и footer.
 	<div id="wrapper">
 	<div id="header">
 	 
 	</div>
 	<div id="content">
 	 
 	</div><!--/content-->
 	<div id="column">
 	 
 	</div><!--/column-->
 	 
 	<div id="footer">
 	 
 	</div><!--/footer-->
 	 
 	</div><!--/wrapper-->
	</body>
 	</html>

Шаг 2: основной контент

Как я и обещал, теперь мы перейдём к описанию основного контента, который будет состоять из тегов, которые так или иначе часто встречаются на веб страницах:
 	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 	<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 	<head>
 	<title>{pagetitle}</title>
 	<meta name="description" content="{metadesc}" />
 	<meta name="keywords" content="{metakey}" />
 	<style type="text/css" title="Default page style" media="screen"><!--@import "skins/fmwk/style.css";--></style>
 	<link rel="icon" href="favicon.ico" type="image/x-icon" />
 	<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
 	</head>
 	<body>
 	<div id="wrapper">
 	<div id="header">
 	<h2><a href="#" title="Website name">Website name</a></h2>
	</div>
 	<div id="content">
 	<h1>{pagetitle}</h1>
 	<img class="photo" src="photo.jpg" alt="Photo test" />
 	<p>
 	Lorem ipsum dolor sit amet, <strong>consectetuer adipiscing elit</strong>. Quisque urna augue, fringilla quis, pulvinar non, feugiat in, pede. Curabitur vitae pede. Cras vehicula varius tellus. Sed consequat, enim tristique euismod volutpat, <em>tellus magna aliquet risus</em>, id aliquet eros metus at purus.
 	</p>
 	<h2>Secondary heading</h2>
 	<p>
 	Aliquam dictum, nibh eget <a href="#" title="Test link">ullamcorper condimentum</a>, magna turpis placerat pede, tempor facilisis tortor urna commodo turpis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras luctus cursus velit. Nullam imperdiet turpis.
 	</p>
 	<h3>Tertiary heading</h3>
 	<table>
 	<tr>
 	<th>Heading</th>
 	<td>Data</td>
 	</tr>
 	<tr>
 	<th>Heading</th>
 	<td>Data</td>
 	</tr>
 	</table>
 	<p>
 	<img src="image.jpg" alt="Generic image" />
 	Cras a eros eget lorem fermentum malesuada. Phasellus condimentum libero vel lacus. Donec lectus nisl, adipiscing malesuada, sodales tincidunt, sagittis vitae, lacus. Proin nec pede. Maecenas adipiscing adipiscing risus.
 	</p>
 	</div><!--/content-->
 	<div id="column">
 	 
 	<ul>
 	<li>List item</li>
 	<li>List item</li>
 	<li>List item</li>
 	</ul>
 	 
 	<ol>
 	<li>List item</li>
 	<li>List item</li>
 	<li>List item</li>
	<li>List item</li>
 	</ol>
 	 
 	</div><!--/column-->
 	 
 	<div id="footer">
 	<p>
 	© Website name, 2008.
 	</p>
 	</div><!--/footer-->
 	 
 	</div><!--/wrapper-->
 	</body>
 	</html>

Теперь наш контент готов к тому, чтобы мы его слегка оформили.
Шаг 3: стиль

В первую очередь, нам необходимо сбросить все начальные значения HTML элементов, такие как внешние и внутренние отступы:
 	body, * {
 	margin: 0;
 	padding 0;
 	}

Теперь оформим ещё несколько элементов:
 	body {
 	background: #FFF;
 	color: #000;
 	font-family: "helvetica", "arial", "verdana", sans-serif;
 	font-size: 62.5%;
Добавлено: 18 Августа 2013 12:42:22 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...