Хочу поделиться с Хабром простым PHP-фреймворком, выросшим из идей минимализма и нацеленным на быструю разработку простых сайтов.
Не хочу показаться зазывалой, впаривающим вам очередной фреймворк, потому все ссылки для быстрого ознакомления с проектом оставляю над катом:
<?php
$sp = [
'layout' => [
'title' => 'Статья',
],
'input' => [
INPUT_GET => [
'id' => [
FILTER_SANITIZE_NUMBER_INT,
[
'filter' => FILTER_VALIDATE_INT,
'options' => ['min_range' => 1],
'comment' => 'Идентификатор должен быть положительным, целым числом'
]
],
],
],
'pdo' => [
'queries' => [
'article' => [
'SELECT * FROM article WHERE id = :id',
'params' => [
'id' => &$_GET['id'],
],
],
],
],
];
include('../../sp.php');
$article = $article->fetch();
?>
<h1>
<?= $article->title ?>
</h1>
<div>
<?= $article->content ?>
</div>
<ul>
<li>
<a href="/articles/edit?id=<?= $article->id ?>">edit</a>
</li>
<li>
<a href="/articles/delete.php?id=<?= $article->id ?>">delete</a>
</li>
</ul>
<?php
<?php
if($_SERVER['REQUEST_METHOD'] != 'GET'){
http_response_code(404);
exit;
}
$sp = [
'input' => [
INPUT_GET => [
'id' => [
FILTER_SANITIZE_NUMBER_INT,
[
'filter' => FILTER_VALIDATE_INT,
'options' => ['min_range' => 1],
'comment' => 'Идентификатор должен быть положительным, целым числом'
]
],
],
],
'pdo' => [
'queries' => [
[
'DELETE FROM article WHERE id = :id',
'params' => [
'id' => &$_GET['id'],
],
]
],
],
];
include('../sp.php');
header('Location: /articles', 302);
Для заинтересовавшихся, под катом будет краткое описание возможностей проекта, его преимуществ и пример использования.
Краткое введение
Если вы знакомы с Jekyll, то вы уже познакомились с большей частью возможностей SimplePage, а именно:
- Конфигурация системы на уровне конкретной страницы и всего сайта
<?php
$sp = [ // Вводные страницы заменяют дефолтные конфигурации сайта
'layout' => [
'title' => 'Редактор статьи',
],
];
include('../../sp.php');
?>
<form action="/articles/create.php" method="POST">
<div>
<input type="text" name="title" placeholder="Заголовок"/>
</div>
<div>
<textarea name="content"></textarea>
</div>
<div>
<input type="submit" value="Сохранить"/>
</div>
</form>
- Роутинг на уровне веб-сервера
▾ articles/
▸ _locale/
▸ create/
▾ edit/
index.php // Страница редактирования статьи
▸ view/
create.php // Экшен создания статьи
delete.php
edit.php
index.php // Страница со списком статей
index.php // Главная страница
- Отсутствие зависимостей — фреймворк использует только возможности самого PHP. Выбор базы данных и веб-сервера остаются на совести разработчика
Преимущества и недостатки
Преимущества:
- Очень низкий порог входа — если вы умеете HTML/CSS и немного PHP, вы можете использовать этот фреймворк
- Очень прост в установке — достаточно скопировать несколько PHP-скриптов, и фреймворк готов к работе
- Очень прост в использовании — каждая страница сайта это отдельный PHP-скрипт, содержащий как логику для обработки запроса, так и HTML-шаблон для генерации ответа
Недостатки:
- Процедурный код и global — с увеличением проекта, поддержка может заметно усложниться
- Низкоуровневая инфраструктура — используются возможности самого PHP, без дополнительных слоев абстракции и интерфейсов
Плагины и модули
Для расширения функционала фреймворка и разработки прикладных решений с его помощью используются плагины и модули соответственно. Первые, как правило, представляют собой инфраструктурные и вспомогательные компоненты приложения, а вторые прикладные решения.
Для подключения плагина, достаточно указать его в конфигурации:
return [
'plugins' => [
'_plugins/autoload.php',
'_plugins/input.php',
'_plugins/middleware.php',
'_plugins/i18n.php',
'_plugins/error.php',
'_plugins/pdo.php',
'_plugins/acl.php',
'_plugins/layout.php',
'_plugins/hook.php',
],
...
];
По умолчанию фреймворк поставляется со следующими плагинами:
- Acl — разграничение прав доступа к страницам сайта
- Autoload — загрузка классов с использованием прямых ссылок или путей PSR-4
- Error — обработка ошибок и исключений
- Hook — модель событий (хуков)
- I18n — интернационализация
- Input — фильтрация ввода
- Layout — HTML-обертки
- Middleware — модель последовательной обработки ввода-вывода
- Pdo — декларативная работа с БД
Разработчик может дополнять эти плагины собственными, в том числе используя внешние зависимости и composer:
return [
'plugins' => [
'vendor/autoload.php',
'_plugins/markdown.php',
...
],
...
];
В отличии от плагинов, модули представлены набором PHP-скриптов и даже страниц, решающих прикладные задачи проекта, такие как "Панель администратора" или "Форум".
Пример простого блога
Рассмотрим пример создания блога с использованием данного фреймворка.
Для начала реализуем общую конфигурацию сайта, создав файл config.php в корне проекта:
<?php
return [
'plugins' => [
'_plugins/input.php',
'_plugins/middleware.php',
'_plugins/i18n.php',
'_plugins/error.php',
'_plugins/pdo.php',
'_plugins/layout.php',
],
'layout' => [
'title' => 'SimplePage',
'layout' => '_layout/default.html',
],
'pdo' => [
'dsn' => [
'mysql',
'dbname' => 'sp',
'charset' => 'UTF8',
],
'username' => 'root',
'password' => 'root',
'options' => [
PDO::ATTR_PERSISTENT => true
],
],
];
Далее реализуем layout, создав шаблон _layout/default.html, который будет применяться ко всем страницам сайта:
<!DOCTYPE html>
<html>
<head>
<title><?= i18n($title) ?></title>
<meta charset="utf-8" />
<link href="/_css/style.css" rel="stylesheet">
</head>
<body>
<?= $content ?>
</body>
</html>
Пришло время создать главную страницу сайта, для этого запишем в файл index.php следующее:
<?php include('sp.php') ?>
<h1>Hello world</h1>
<p>
Моя главная страница
<p>
<ul>
<li>
<a href="/articles">Статьи</a>
</li>
</ul>
Далее создадим страницу со списком статей (предполагается, что таблицы для этого модуля уже созданы в БД), для этого запишем в файл articles/index.php следующий код:
<?php
$start = isset($_GET['start'])? (int) $_GET['start'] : 0;
$offset = isset($_GET['offset'])? (int) $_GET['offset'] : 2;
$sp = [
'layout' => [
'title' => 'Articles',
],
'pdo' => [
'queries' => [
'articles' => [
'SELECT * FROM article LIMIT :start, :offset',
'params' => [
'start' => $start,
'offset' => $offset,
],
],
'articlesCount' => [
'SELECT COUNT(*) FROM article',
],
],
],
];
include('../sp.php');
$articlesCount = $articlesCount->fetchColumn();
?>
<h1>Статьи</h1>
<p>
<ul>
<?php foreach($articles as $article): ?>
<li>
<a href="/articles/view?id=<?= $article->id ?>">
<?= $article->title ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<ul>
<li>
Всего <?= i18n_plural($articlesCount, '%d article') ?>
</li>
<li>
<a href="/articles/create"><?= i18n('create') ?></a>
</li>
<li>
<a href="/articles?start=<?= $start - $offset ?>"><?= i18n('prev') ?></a>
</li>
<li>
<a href="/articles?start=<?= $start + $offset?>"><?= i18n('next') ?></a>
</li>
</ul>
</p>
Как можно заметить, в шаблоне страницы используется интернационализация. Следовательно необходимо создать файл articles/_locale/ru_RU.php для ее корректной работы:
<?php
return [
'' => [
'plural_forms' => function($n){
if($n % 10 == 1 && $n % 100 != 11){
return 0;
}
elseif($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20)){
return 1;
}
else{
return 2;
}
},
],
'%d article' => [
'%d статья',
'%d статьи',
'%d статей',
],
'Articles' => 'Статьи',
'create' => 'создать',
'prev' => 'назад',
'next' => 'вперед',
];
На последок рассмотрим экшен создания новой статьи в блоге, для этого создадим файл articles/create.php:
<?php
if($_SERVER['REQUEST_METHOD'] != 'POST'){
http_response_code(404);
exit;
}
$sp = [
'pdo' => [
'queries' => [
[
'INSERT INTO article (title, content) VALUES (:title, :content)',
'params' => [
'title' => $_POST['title'],
'content' => $_POST['content'],
],
],
],
],
];
include('../sp.php');
header('Location: /articles/view?id=' . pdo_build($sp['pdo'])->lastInsertId(), 302);