Документация

Документация такая же простая, как и наш фреймворк.

Системные требования

Данный фреймворк для своей работы требует лишь php версии 5.3+ и больше ничего.

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


Подключение

Нет ничего проще, чем подключить PIE к вашему проекту, ведь он состоит всего из одного модуля.

require 'lib/pie.php';

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


Настройка

На самом деле, сразу после подключения модуля pie.php фреймворк полностью готов к работе. Тем не менее, как уже было сказано ранее, фреймворк является расширяемым. Все расширения подгружаются автоматически по ленивой стратегии (по мере необходимости) из директории расширений. Путь к этой директории хранится в системной переменной LIB (значение по умолчанию lib/). Также, каждое расширение может иметь свои собственные настройки или требовать некоторой инициализации. Подобные настройки могут быть сохранены в файлах конфигурации, которые также будут подгружены автоматически из директории настроек, хранящейся в переменной LIBCFG (по умолчанию lib/cfg/). Значения этих переменных могут быть изменены стандартными средствами (см. следующий раздел) или через специальные функции-обертки.

		// устанавливаем значение переменной LIB
PIE::data('LIB', 'plugins/');	// стандартным способом
PIE::lib('plugins/');		// используя метод-обертку
echo PIE::lib();		// вернет "plugins/"
echo PIE::libcfg('configs/');	// аналогично, вернет "configs/"
Кстати. Если что-то не до конца ясно - смотрите следующие разделы.

Переменные

Основным методом управления переменными в {PIE::*} является метод data. С его помощью можно устанавливать и получать значения переменных.

PIE::data('some_var', 10);		// устанавливаем значение переменной
$var1 = PIE::data('some_var');		// считываем значение переменной
$var2 = PIE::data('some_var', 20);	// аналогично обычному присвоению

Метод data также позволяет легко управляться с массивами. Причем, обращаться к элементам массива мы можем не только по имени, но и по индексу.

PIE::data('some_var', Array(
	'first'		=>	'Monday',
	'second'	=>	'Tuesday'
));
echo PIE::data('some_var.first');	// вернет 'Monday'
...
PIE::data('some_var', Array('apple', 'banana', 'orange'));
echo PIE::data('some_var.1');		// вернет 'banana'

Для проверки существования и уничтожения переменной {PIE::*} содержит методы, аналогичные стандартным isset() и unset(), соответственно.

PIE::data('some_val', 10);
if (PIE::isdata('some_val')) {
	// условие выполнится
}
PIE::undata('some_val');
if (PIE::isdata('some_val')) {
	// условие не выполнится
}

Для облегчения выполнения некоторых частых операций над переменными {PIE::*} содержит еще несколько приятных методов.

Метод Описание
PIE::add([имя], [значение]) Увеличивает переменную с заданным именем на заданное значение. Метод возвращает новое значение переменной.
PIE::sub([имя], [значение]) Уменьшает переменную с заданным именем на заданное значение. Метод возвращает новое значение переменной.
PIE::append([имя], [значение]) Дописывает заданное значение в конец переменной с заданным именем. Метод возвращает новое значение переменной.
PIE::prepend([имя], [значение]) Дописывает заданное значение в начало переменной с заданным именем. Метод возвращает новое значение переменной.
PIE::push([имя], [значение]) Добавляет новый элемент с указанным значением в конец переменной-массива с заданным именем. Если переменная ранее не являлась массивом, она превращается в массив с одним элементом. Метод возвращает новое значение переменной.
PIE::merge([имя], [массив]) Объединяет переменную-массив с заданным именем с другим массивом. Если переменная ранее не являлась массивом, она превращается в массив с одним элементом. Метод возвращает новое значение переменной.
Кстати. Напомним, что встроенными переменными LIB и LIBCFG также можно управлять с помощью этих методов.

Методы пользователя

Вся соль данного фреймворка заключается в его расширяемости. {PIE::*} позволяет динамически добавлять в себя новые методы, которые вдальнейшем используются, как его собственные. И главное, сделать это проще простого!

PIE::addMethod('foo', function(){	// добавляем метод
	echo 'executing foo()';
});
PIE::foo();				// его сразу можно использовать
...
PIE::addMethod('bar', function($name){	// можно использовать параметры
	return "Hello, $name!";		// и возвращать результат
});
echo PIE::bar("Andres");		// вернет "Hello, Andres!"
...
PIE::addMethods(Array(			// добавляем несколько методов
	'foo'		=>	function(){
				  return "foo";
				},
	'foobar'	=>	function(){
				  return PIE::foo()."bar";
				}
));
echo PIE::foobar();			// вернет "foobar"
...
if (PIE::hasMethod('foobar')) {
	// можно также проверить наличие метода
}

Но какой в этом смысл? Почему нельзя сразу создать класс со всеми необходимыми методами? Какая польза от этого?

Прелесть в том, что данные методы могут быть выделены в отдельные модули, которые далее {PIE::*} подгрузит автоматически!

Как это работает? Все очень просто - каждый раз, когда Вы пытаетесь вызвать метод {PIE::*}, которого в нем еще нет, он пытается найти соответствующий модуль в директории расширений (помните переменную LIB?). Если такой модуль находится, {PIE::*} автоматически подключает его вместе с настройками (а LIBCFG помните?), после чего метод отправляется на выполнение.

// файл plugins/bar.php
<?
PIE::data('FOO', 10);	// можно инициализировать переменные
PIE::addMethod(Array(	// и добавить все необходимые методы
	'foo'	=>	function() {
			  return "foo";
			},
	'bar'	=>	function() {
			  return PIE::foo()."bar";
			},
));
?>
...
// файл index.php
<?
require 'pie.php';
PIE::lib('plugins/');
echo PIE::bar();		// вернет "foobar"
?>

Теперь-то Вы видите? {PIE::*} не только автоматически находит и подключает нужный метод, но и делает это не раньше, чем это может понадобиться. Другими словами, если закомментировать 18-ю строку, то файл bar.php не будет подключен и скрипт в итоге выполнится гораздо быстрее. Удобно, не правда ли?

Более того, мы можем организовать условное подключение модулей. Давайте представим более реальный пример: у нас есть набор скриптов, которые используют базу данных. Очевидно, что для соединения с БД нам нужнен не только механизм но и настройки подключения, такие как логин и пароль. Мы можем объявить эти настройки в некотором глобальном файле настроек. Но что, если у нас есть скрипты, не соединяющиеся с БД? Тогда, конечно, настройки соединяния можно описать в самом модуле работы с БД. Но ведь гораздо удобнее хранить их в каком-то одном отдельном месте. Тут нам на помощь и приходит переменная LIBCFG. Каждый раз при подключении модуля расширений {PIE::*} пытается найти такой же файл в дирктории из LIBCFG и подключить его.

// файл lib/connect.php
<?
	// набор методов для подключения к БД
?>
...
// файл lib/cfg/connect.php
<?
PIE::data('DB.SETTINGS', Array(
	'SERVER'	=>	'localhost',
	'USER'		=>	'db_user',
	'PASSWORD'	=>	'********',
	'DATABASE'	=>	'db',
	'CHARSET'	=>	'utf8'
));
?>
...
// файл index.php
<?
require 'pie.php';
PIE::connect();		// соединение установлено
?>

Отделять мух от котлет так приятно.


UTF-8

Первый модуль предназначен для работы со строками в кодировке UTF-8 без подключения библиотеки mbstring. Перед началом его использования требуется инициализация.

PIE::mbstring();

После этого можно использовать любой из методов этого модуля: strlen, strpos, substr, strtolower, strtoupper, explode, join, str_replace, strtr, quotemeta, preg_replace, preg_match, preg_match_all.

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

echo PIE::substr("hello", 1);	// вернет "ello"
if ($matches = PIE::preg_match("/love/", "I love oranges!")) {
	// в массив $matches будут записаны все совпадения
}

Автозагрузка файлов

Сколько конфигурационных файлов и модулей ядра в вашем проекте? Как часто вы пишете что-то вроде этого?

// кусочек кода LiveStreet CMS
...
require_once("LsObject.class.php");
require_once("Plugin.class.php");
require_once("Block.class.php");
require_once("Hook.class.php");
require_once("Module.class.php");
require_once("Router.class.php");
require_once("Entity.class.php");
require_once("Mapper.class.php");
require_once("ModuleORM.class.php");
require_once("EntityORM.class.php");
require_once("MapperORM.class.php");
require_once("ManyToManyRelation.class.php");
...

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

PIE::autoinclude('autoload/');		// кусочек кода этого сайта

По умолчанию данный метод подключает модули из указанного каталога и всех его подкаталогов. Но рекурсивность можно отключить.

PIE::autoinclude('autoload/', FALSE);	// нерекурсивная загрузка

Теперь все глобальные настройки, а также наборы функций можно просто поместить в каталог автозагрузки и {PIE::*} сделает все остальное за Вас.

Кстати. Стоит понимать отличие директории автозагрузки от директорий LIB и LIBCFG - в последних файлы подгружаются по ленивой стратегии (только когда это надо), в то время как autoinclude загружает все файлы сразу.
Внимание. Модуль автозагрузки требует обязательного наличия метода mbstring, описанного в предыдущем разделе.

Маршрутизация

Одной из важных возможностей для любого фреймворка является маршрутизация. В {PIE::*} она реализуется в два этапа: настройка маршрутов и обработка запросов. Для добавления нового маршрута необходимо использовать метод route. Данный метод принимает в качестве параметров регулярное выражение для проверки маршрута и, собственно, обработчик маршрута.

PIE::route('/\/home/', function() {
	// обработка маршрута "/home"
});
Внимание. Чуть не забыл самое главное: чтобы вся эта красота работала, необходима настройка перенаправления apache. Например, файл .htaccess следующего вида вполне подойдет:
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L,QSA]

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

function foo() {
	...
}
PIE::route('/\/foo/', 'foo');			// обработчик-функция
PIE::route('/\/bar/', 'routes/foo.php');	// подключение модуля
PIE::route('/.*/', function(){			// анонимная функция
	...
});
Внимание. После запуска маршруты обрабатываются в порядке создания, по этому в предыдущем примере третий обработчик (/.*/) получит управление, если один из предыдущих двух не закончит работу скрипта функцией exit.

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

PIE::route('/\/foo/', 'foo');
PIE::route('/\/foo/', 'routes/foo.php');
PIE::route('/\/bar/', 'bar');
PIE::route('/\/bar/', 'routes/bar.php');
PIE::route('/\/bar/', function() {
		...
});

Можно заменить на:

PIE::route('/\/foo/', 'foo; routes/foo.php');	// в строковом виде
PIE::route('/\/bar/', Array(			// в виде массива
	'bar',
	'routes/bar.php',			// эти два можно объединить
	function() {
		...
	}
));

Также, {PIE::*} позволяет назначить нескольким маршрутам один и тот же обработчик (если такая ситуация может возникнуть). И вместо:

PIE::route('/\/foo/', 'foobar');
PIE::route('/\/bar/', 'foobar');

Мы получаем:

PIE::route(Array('/\/foo/', '/\/bar/'), 'foobar');

Результат обраобтки регулярного выражения внутри обработчика можно получить, обратившись к переменной PARAMS. Нулевой элемент этой переменной-массива содержит совпадение с самим регулярным выражением, а каждый следующий - с каждым подвыражением.

PIE::route('/^\/([a-z]+)\/([0-9]+)\/?$/', function(){
	print_r(PIE::data('PARAMS'));exit;
/*
	при переходе по адресу /articles/123 мы получим:
	Array
	(
	    [0] => /articles/123
	    [1] => articles
	    [2] => 123
	)
*/
});

Функции могут получать результаты через входные параметры, и вместо:

PIE::route('/^\/([a-z]+)\/([0-9]+)\/?$/', function(){
	$param1 = PIE::data('PARAMS.1');
	$param2 = PIE::data('PARAMS.2');
	...
});

Можно смело писать:

PIE::route('/^\/([a-z]+)\/([0-9]+)\/?$/', function($url, $param1, $param2){
	...
});

После того, как все маршруты были настроены, необходимо запустить маршрутизатор.

PIE::run();

Еще одной полезной возможностью маршрутизатора {PIE::*} является перемаршрутизация. Она позволяет выполнить скрипт так, как если бы пользователь обратился к определенному заданному адресу. Например, ее можно использовать для проверки маршрутов.

PIE::route('/\/foo/', 'pages/foo.php');	// настройка маршрутов
PIE::route('/\/bar/', 'pages/bar.php');
PIE::route('/\/articles\/([0-9+)\/?/', 'pages/articles.php');

PIE::reroute('/foo');			// выполнится первый маршрут

По сути, функция run представляет собой обертку над reroute.

PIE::reroute($_SERVER['REDIRECT_URL']);

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

PIE::route('/\/foo/', 'pages/foo.php');
PIE::route('/\/bar/', 'pages/bar.php');
PIE::route('/\/articles\/([0-9+)\/?/', 'pages/articles.php');
// если ни один из маршрутов не отработал, показываем ошибку 404
PIE::route('/.*/', 'pages/404.php');

PIE::run();

Или, с использованием перемаршрутизации, установка страниц по умолчанию.

...
PIE::route('/.*/', function(){
	PIE::reroute('/home');
});
Внимание. Модуль маршрутизации требует обязательного наличия метода mbstring, описанного в соответствующем разделе.

Шаблоны

Всем известно, что логику необходимо отделять от представления. И {PIE::*} с этим согласен, по этому у него есть простенький и быстрый template. Для его использования сначала следует настроить шаблонизатор, указав каталог, в котором будут расположены файлы шаблонов.

PIE::template('templates/');

Шаблоны в {PIE::*}, это файлы с расширением *.tpl, в которых могут содержаться особые теги формата {{имя}}. Вдальнейшем эти теги будут заменены на значения.

Для примера рассмотрим простой шаблон simple.tpl.

<b>{{key}}:</b> {{value}}<br>

Теперь мы очень просто можем оформить с его помощью некоторые данные.

$data = Array(
	'key'	=>	'name',
	'value'	=>	'Andres'
);
$template = PIE::get('simple');		// считываем шаблон
echo PIE::parse($template, $data);	// выведет <b>name:</b> Andres<br>

Шаблонизатор также умеет обрабатывать наборы данных. Например, для массива:

$data = Array(
	Array(
		'key'	=>	'name',
		'value'	=>	'Andres'
	),
	Array(
		'key'	=>	'family',
		'value'	=>	'Kovalev'
	)
);
$template = PIE::get('simple');		// считываем шаблон

Вместо:

foreach($data as $value)
  echo PIE::parse($template, $value);
/*
	выведет:
		<b>name:</b> Andres<br>
		<b>family:</b> Kovalev<br>
*/

Мы можем писать:

echo PIE::parses($template, $data);

Последний метод шаблонизатора - это display. Он является оберткой над parse. Его задача вывести обработанный шаблон и завершить выполнение сценария (exit). Он также принимает дополнительный входной параметр $content_type, который будет автоматически подставлен в соответствующий заголовок ответа сервера.

// так, например, может выглядеть сценарий вывода статических страниц
// для маршрута PIE::route('/\/asserts\/([a-z]+)\.(css|js)/', 'static.php');
$type = PIE::data('PARAMS.2');
$file = PIE::data('PARAMS.1');
if (PIE::exists($template = "$type/$file"))
  PIE::display(PIE::get($template), NULL, $type);
PIE::reroute(PIE::data('home'));

Для самых распространенных типов (html, css и js) содержимого можно передавать сокращение (как это сделано в примере). По умолчанию передается тип html

Внимание. Модуль шаблонизатора требует обязательного наличия метода mbstring, описанного в соответствующем разделе.

Базы данных

{PIE::*} содержит несколько простых методов для работы с БД. Все эти методы являются лишь оберткой над стандартным классом Mysqli.

Для установки соединения с БД используется метод connect. Настройки соединения могут быть переданы в качестве параметра, либо через переменную DB.SETTINGS.

$mysql_config = Array(
	'SERVER'	=>	'server',
	'USER'		=>	'user',
	'PASSWORD'	=>	'********',
	'DATABASE'	=>	'db',
	'CHARSET'	=>	'utf8'
);
// передача настроек через системную переменную
PIE::data('DB.SETTINGS', $mysql_config);
PIE::connect();
// передача настроек через параметр
PIE::connect($mysql_config);

При передаче настроек через параматр они все равно записываются в системную переменную DB.SETTINGS.

Для разрыва соединения предназначен метод disconnect.

PIE::disconnect();

После того, как соединение установлено, можно смело выполнять запросы. Для этого есть два метода: query и select. Оба они принимают три параметра: параметризованный запрос, строку типов параметров и массив параметров (по аналогии с методом mysqli_stmt->bind_param).

Метод query возвращает объект класса pie_mysqli_stmt. Последний наследует mysqli_stmt, дополняя его методами fetch и fetch_all. Как следует из названия, метод fetch возвращает одну строку результата, а fetch_all - массив строк. Каждая строка результата представляет собой ассоциативный массив значений полей результата.

Метод select является оберткой для метода query->fetch_all и, соответственно, предназначен только для выполнения выборки.

$query = "SELECT *
	    FROM `message`
	    LIMIT ?, ?";
$from = 1;
$length = 10;

// выборка с помощью fetch()
$q = PIE::query($query, "ii", Array($from, $length));
$rows = Array();
while ($rows[] = $q->fetch());
$q->close();

// выборка с помощью fetch_all()
$q = PIE::query($query, "ii", Array($from, $length));
$rows = $q->fetch_all();
$q->close();

// выборка с помощью select()
$rows = PIE::select($query, "ii", Array($from, $length));

В случае ошибки запроса метод select возвращает значение FALSE, а само содержание ошибки записывается в системную переменную DB.ERRORS.

В случае метода query этот же массив ошибок доступен из pie_mysqli_stmt->error_list.

// проверка ошибки в select()
if (!PIE::select("BAD QUERY"))
  print_r(PIE::data('DB.ERRORS'));

// проверка ошибки в query()
$q = PIE::query("BAD QUERY");
if ($q->errno)
  print_r($q->error_list);

/*
	// в обоих случаях на экране будет
	Array
	(
	    [0] => Array
	        (
	            [errno] => 1064
	            [sqlstate] => 42000
	            [error] => You have an error in your SQL syntax; check
				the manual that corresponds to your MySQL
				server version for the right syntax to use
				near 'BAD QUERY' at line 1
	        )
	)
*/
}

Остальные детали можно почитать в документации mysqli_stmt.