Slim framework создаем блог. Пишем простую энциклопедию на Slim Framework

Эта публикация будет интересна небольшим компаниям по разработкам сайтов с использованием CMS. Небольшая предыстория: мы — это небольшой отдел, который занимается разработкой веб-сайтов. Очень долгое время мы разрабатывали сайты только на системах управления (OpenCart, WordPress, MODX и самописная), которые по своей сути были простые и однотипные, но с лета прошлого года пришёл тренд на разработку сайтов, которые не просто были в интернете лицом компании, но одним из инструментов бизнеса.

Так, например, был заказ для транспортной компании, которая осуществляет рейсовые маршруты и хотела бы, чтобы всё управление (бронирование и оплата билета, в том числе через кассира) происходила через сайт.

Как вы можете догадываться, большой пользы от CMS тут ждать не приходится. Даже наоборот — любой алгоритм, которому она следовала, нам мешал. Через несколько дней на первом же таком сайте были одни костыли и практически вся обработка шла исключительно на javascript, а CMS использовалась как прослойка для записи и выборки из базы данных.

Терпеть такое мы долго не смогли и, посовещавшись, решили, что для таких проектов мы будем использовать miсroframework — и сразу же положили глаз на Slim.

Как гласит официальный сайт:

Slim — это микро фреймворк, который поможет вам быстро писать простые, но мощные веб-приложения.

И это действительно так, вы можете скачать его вручную архивом или воспользоваться Composer. После этого создайте index.php со следующим содержанием:

//Подключаем фреймворк require "Slim/Slim.php"; //Регистрируем автозагрузку \Slim\Slim::registerAutoloader(); $app = new \Slim\Slim(); $app->get("/hello/:name", function ($name) { echo "Привет, $name"; }); $app->run();
Это простое приложение которое выводит привет {{username}} при переходе на url.
Но как же мы были разочарованы, что на этом большинство статей в интернете заканчивается и на данном этапе многое остается для нас непонятным.

Сам Slim не задает чёткой системы распределений файлов на диске, поэтому мы не нашли лучшего способа, чем иклюдить каждый роутинг. На следующий день, поняв, что что-то идёт не так, было решено создать отдельную папку и класть туда файлы по группам. В файл index.php добавилось следующее

Foreach (glob("Routes/*.php") as $file) include($file);
Со временем каждый такой файл стал слишком большим, но посмотрев официальную документацию , ничего не обнаружили. Решение было найдено на github, где было сказано, что надо делать:

$app->get("post/:type/:id", "\Controller\Post:onePost");
Где \Controller\Post — класс, который, как я понимаю, добавлен в соответствии с RSP-0. А onePost — это его метод.
Таким образом наша файловая структура обновилась ещё одной директории Controller, в которую мы записывали действия, которые могли использовать сразу в нескольких вариантах роутинга. Я считаю хорошим примером данный роутинг, который заключён в группу, стиль, который почему-то не описан в той же документации.

$app->group("/", function () use ($app) { $app->get("post/:type/:id", "\Controller\Post:onePost") ->conditions(array("type" => "{3,}", "id" => "{1,}")) ->setParams(array("app" => $app, "type" => $type = NULL, "id" => $id = 0)) ->name("onePost"); });
Где conditions — валидация по регулярному выражению, setParams — передача параметров методу onePost, а name — имя для возможного редиректа на данную страницу (Например $app->redirectTo("onePost");)

Не лишним будет проводить проверку на доступность не в самом контроллере, а на уровне роутига, благодаря «Middleware». Например, для панели администратора сразу же можно поставить на целую группу такую как:

$app->group("/admin", $authenticateForRole($app, "admin"), function () use ($app) { ... });
Где вторым аргументом является присвоенная анонимная функция переменной. В моём случае она выглядит таким образом:

$authenticateForRole = function ($app, $role = "admin") { return function () use ($app, $role) { \Controller\Autch::chekUserRole($app, $role); }; };
К сожалению, а для некоторых, может, и к счастью, в slim нет встроенной работы с базой данных, но этот вопрос достаточно просто решается, когда мы создаём объект App. Мы можем передавать некоторые аргументы, которые будут им использоваться.

Например:

$app = new \Slim\Slim(array("templates.path" => "Views", "debug" => TRUE, "mode" => "development", "BDbase" => "localhost", "BDuser" => "username", "BDpassword" => "password", "BDname" => "basename"));
После этого мы сможем спокойно воспользоваться Dependency Injection для работы с базой данных, обращаясь к параметрам, переданным при создании с помощью метода $app-config("Имя");

$app->database = new mysqli($app->config("BDbase"), $app->config("BDuser"), $app->config("BDpassword"), $app->config("BDname")); if ($app->database->connect_errno) { echo "Не удалось подключиться к MySQL: (" . $app->database->connect_errno . ") " . $app->database->connect_error; die(); } $app->database->query("SET NAMES "utf8""); $app->database->query("SET CHARACTER SET "utf8""); $app->database->query("SET SESSION collation_connection = "utf8_general_ci"");
Все остальные возможности Slim достаточно хорошо описаны в документации. В заключении хочу сказать, что для нашей команды, которая никогда раньше не работала с различными framrwork"ами, это был хороший опыт, который мы уже используем в проектах. Возвращаться на какую-либо CMS желания не возникает.

  • Tutorial

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

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

Подобную энциклопедию можно очень легко создать средствами других фреймворков и CMS, но я попробую использовать микрофреймворк Slim. Почему? Всё очень просто: настоящая заметка рассчитана на начинающего программиста, который хочет начать изучение Slim Framework.

Приступим к написанию кода. Единственная задача энциклопедии - выводить содержимое в красивом шаблоне. Этот аскетичный функционал похож на API справочной системы. Начнём с реализации модели и всего, что с ней связано. Уникальное имя страницы будет единственным аргументом, который понадобится упомянутому в интерфейсе методу:
interface IPortal { public function getContentByAlias($alias); }
Далее необходимо реализовать класс, содержащий метод «getContentByAlias». Так как заметка рассчитана на начинающий уровень читателя, то я специально напомню о необходимости наложить индекс на столбец «alias». При большом количестве материалов в энциклопедии, индекс (по нему будет поиск) позволит повысить скорость работы сервера базы данных. Таких вопросов как кэширование (например, memcached или средствами nginx) мы касаться не будем, так как это отдельная и очень большая тема. А вот про базовые требования безопасности есть смысл упомянуть: переложите ответственность за экранирование на драйвер, например, таким образом:
class Portal implements IPortal { private $_pdo; private $_sqlGetContentByAlias = "SELECT `title`, `content` FROM `pages` WHERE `alias` = ? LIMIT 1;"; /** * Constructor */ public function __construct($_pdo) { $this->_pdo = $_pdo; } /** * Get content by alias */ public function getContentByAlias($alias) { $stm = $this->_pdo->prepare($this->_sqlGetContentByAlias); $stm->bindParam(1, $alias, PDO::PARAM_STR); $stm->execute(); return $stm->fetch(); } }
Обратите внимание, что в запросе я достаточно явно указал подсистемам MySQL, что мне нужна только одна запись («LIMIT 1»), и получил данные с использованием «fetch», т.е. только одну запись. Я использую PDO, а соединение передаю через конструктор класса. Так как Slim Framework не содержит встроенных возможностей для работы с базами данных (в отличие от моих любимых Laravel и Yii 2.0), то нам придётся написать ещё один небольшой класс. Как вы успели заметить, нам понадобится Singleton, который позволит заполучить столь нужное соединение с базой данных. По моему субъективному мнению, подходящий способ реализации выглядит следующим образом:
class Connect { private static $_instance; private $_pdo; private $_pdoUrl = "mysql:host=localhost;dbname=kalinin;charset=utf8"; private $_pdoUser = "root"; private $_pdoPassword = ""; private $_pdoPrm = ; /** * Constructor */ private function __construct() { $this->_pdo = new PDO($this->_pdoUrl, $this->_pdoUser, $this->_pdoPassword, $this->_pdoPrm); } /** * Singleton */ private function __clone() {} private function __wakeup() {} public static function getInstance() { if (self::$_instance === null) { self::$_instance = new self; } return self::$_instance; } /** * Get connection */ public function getConnection() { return $this->_pdo; } }
Вот и настало время реализации самой фабрики. С большой вероятностью такая электронная энциклопедия не будет развиваться (в смысле нового функционала), однако, вероятность вовсе не нулевая, следовательно, нужно предусмотреть на самом начальном этапе проектирования возможность дорабатывать и развивать проект. Реализация фабрики получилась такой:
require "./app/models/iportal.php"; require "./app/models/portal.php"; require "./db/connect.php"; class Factory { public static function create($type) { $connection = Connect::getInstance()->getConnection(); switch ($type) { case "simple": return new Portal($connection); break; } } }
Я ничего не забыл? Ах, да. Где же сам Slim? Первым делом нам нужно подключить файлы, необходимые для нашего простого приложения - очень аскетичной электронной энциклопедии. На этом этапе мы укажем в настройках режим «debug»:
require "Slim/Slim.php"; require "./app/models/factory.php"; \Slim\Slim::registerAutoloader(); $app = new \Slim\Slim(); $app->config("debug", true); $app->config(["templates.path" => "./app/views/"]); $conf["url"] = "http://127.0.0.1:8098/";
Помните, что мы должны показывать содержимое в шаблоне в зависимости от алиаса страницы? Посредством второго аргумента метода «render» осуществляется передача данных в представление (View в паттерне MVC). А ещё у нас было требование перенаправлять пользователя на главную страницу в том случае, если нет искомой записи в базе данных. Метод «redirect» решает эту задачу. С использованием «use» мы передаём в область видимости анонимной функции (замыкание или лямбда-функция появилась в PHP начиная с версии 5.3) требуемые массивы и другие переменные. Попробуем:
$app->get("/kalinin/:alias", function ($alias) use ($app, $conf) { $model = Factory::create("simple"); $content = $model->getContentByAlias($alias); if(empty($content)) { $app->redirect($conf["url"], 301); } $app->render("page.php", $content); });
В тех случаях, когда представление является статическим контентом достаточно использовать такую реализацию:
$app->get("/", function () use ($app) { $app->render("main.php"); });
Давайте подумаем о печальном. Ошибки могут случиться, а значит, мы обязательно должны продумать поведение электронной энциклопедии в подобных ситуациях. Как говорили древние мудрецы: «не единым XDEBUG-ом жив программист», что значит: «в реальных проектах есть смысл подробно логировать некоторые ошибки». Согласно рекомендациям поисковых систем необходимо выдавать 404-ый код ошибки при неудачной попытке найти страницу, однако, в данном примере, в случае любых ошибок я просто перенаправлю пользователя на главную страницу. Для работы с ошибками используйте следующие два замыкания (для примера у меня просто перенаправление с 301-ым кодом ответа):
$app->notFound(function () use ($app, $conf) { $app->redirect($conf["url"], 301); }); $app->error(function (\Exception $e) use ($app, $conf) { $app->redirect($conf["url"], 301); });
Всё готово, осталось только вызвать метод «run»:
$app->run();
Просто? Хорошая документация поможет вам понять все возможности Slim Framework. Разумеется, я описал только один из возможных подходов. Необходимость применения того или иного фреймворка определяется программистом самостоятельно (или группой разработчиков на совещании).

Slim – один из популярнейших открытых микрофреймворков, доступных для PHP. Он очень эффективный, быстрый и простой в использовании. Данный фреймворк идеально подходит для разработки малых и средних веб-приложений, а также позволяет достаточно быстро разрабатывать крупные масштабируемые PHP-приложения.

Slim обладает следующими (достаточно предсказуемыми как для фреймворка) функциями:

  • простота в использовании, мощный и гибкий маршрутизатор;
  • просмотр визуализации шаблонов;
  • безопасные файлы cookie;
  • HTTP-кэширование;
  • простота обработки ошибок и отладки;
  • простота настройки.

Требования

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

Если разрабатываемому приложению не нужна СУБД MySQL, можете не устанавливать ее. Как минимум, понадобятся веб-сервер Apache (с модулем Mod_Rewrite) и PHP 5.3+.

Быстрая установка требований

1: Установка Apache

apt-get update
apt-get install apache2

2: Установка PHP

apt-get install php5 libapache2-mod-php5 php5-mcrypt

3: Активация mod_rewrite

4: Редактирование конфигурационного файла Apache

В конфигурационном файле Apache нужно изменить AllowOverride None на AllowOverride All. В зависимости от настроек сервера этот файл может находиться в:

  • /etc/apache2/apache2.conf
  • /etc/apache2/sites-enabled/000-default
  • /etc/apache2/sites-available/default

В конфигурационном файле найдите раздел, который выглядит следующим образом:



AllowOverride None
Require all granted

Отредактируйте его следующим образом и сохраните изменения:


Options Indexes FollowSymLinks
AllowOverride All
Require all granted

5: Перезапуск Apache

service apache2 restart

Установка Slim

Процесс установки фреймворка Slim состоит из трех этапов:

  • распаковка zip-файлов;
  • копирование Slim Framework в общедоступное место.
  • 1: Загрузка Slim

    Скачать Slim Framework можно при помощи команды:

    wget https://github.com/codeguy/Slim/zipball/master

    Данная команда извлечет архив фреймворка и сохранит его в текущем каталоге под именем master.

    2: Распаковка zip-файла

    Содержимое zip-файла можно извлечь с помощью следующей команды:

    unzip master -d ./

    Примечание : если появилось сообщение о том, что инструмент unzip не установлен, его можно установить с помощью команды apt-get install unzip, а затем снова выполнить предыдущую команду.

    Данная команда извлечет файлы в папку с именем вроде codeguy-Slim-3a2ac72, которая содержит папку по имени Slim, папку фреймворка.

    3: Копирование Slim Framework в общедоступное место

    Теперь нужно скопировать папку codeguy-Slim-3a2ac72/Slim в общедоступное место системы (например, в /usr/local/Slim), откуда фреймворк Slim будет доступен для всех проектов на этом сервере, которым он необходим. Это позволит избежать дублирования и предотвратит любые проблемы обслуживания, которые могут возникнуть из-за дублирования программы.

    Скопируйте папку с помощью следующей команды:

    cp -r ./codeguy-Slim-3a2ac72/Slim /usr/local/Slim

    Примечание : имя извлеченной папки (в данном случае codeguy-Slim-3a2ac72) может несколько отличаться в случае загрузки другой версии фреймворка. Убедитесь, что в команде указано верное имя папки.

    Как только это будет сделано, любой из проектов, использующих Slim Framework, сможет получить доступ к нему.

    Важно! Многие руководства советуют установить фреймворк в общую папку/document root (например, в /var/www/Slim). Имейте в виду: установка файлов инфраструктуры за пределами общей папки/document root (как это сделано в данном руководстве) позволяет немного обезопасить приложение, поскольку файлы фреймворка не будут доступны в браузере.

    Разработка проекта Slim

    Как правило, проект Slim состоит из трех основных каталогов:

    1. Каталог фреймворка Slim.

    Он содержит файлы фреймворка (каталог, который был скопирован в предыдущем разделе).

    1. Каталог проекта.

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

    Данный каталог может находиться в любой точке сервера; единственное условие: в идеале он не должен располагаться в доступном из сети месте. Его можно разместить в /usr/local или домашней папке. К примеру, если проект находится в папке HelloSlim, то такую папку можно поместить в /usr/local/HelloSlim или ~/HelloSlim.

    Файлы в этой папке можно расположить следующим образом:

    HelloSlim
    |- Routes
    | |- route1.php
    | |- route2.php
    |- Models
    | |- model1.php
    | |- model2.php
    |- Views
    | |- footer.php
    | |- header.php
    | |- sidebar.php
    | |- view1.php
    | |- view2.php
    |- Class
    | |- class1.php
    | |- class2.php
    |- routes.php //contains "include" statements for all routes in the "Routes" folder
    |- includes.php //contains "include" statements for all models/classes in the "Models/Class" folders

    Такую структуру папок можно создать при помощи следующих команд:

    mkdir /usr/local/HelloSlim
    mkdir /usr/local/HelloSlim/Routes
    mkdir /usr/local/HelloSlim/Models
    mkdir /usr/local/HelloSlim/Views
    mkdir /usr/local/HelloSlim/Class

    Примечание : необязательно использовать именно такую структуру папок; ее можно изменять согласно потребностям проекта или разработчика.

    1. Document root, или общая папка.

    Это папка, доступная в сети (как правило, расположена в /var/www). В ней содержится всего два файла, связанные со Slim:

    • index.php
    • .htaccess

    Данная папка также содержит все скрипты, графические файлы и стили проекта. Во избежание «засорения» папки можно создать в ней отдельные каталоги scripts, styles и images для хранения соответствующих файлов.

    Вот пример структуры папки document root:

    Document Root (eg. /var/www/)
    |- scripts
    | |- jquery.min.js
    | |- custom.js
    |- styles
    | |- style.css
    | |- bootstrap.min.css
    |- images
    | |- logo.png
    | |- banner.jpg
    |- .htaccess
    |- index.php

    Содержимое файла

    Предположим, проект имеет структуру, заданную выше; в таком случае нужно заполнить файлы.htaccess и index.php (в document root, root-каталоге сайта) следующим образом:

    .htaccess

    RewriteEngine On


    index.php



    require "$projectDir/includes.php"; //include the file which contains all the project related includes
    $app = new \Slim\Slim(array(
    "templates.path" => "/usr/local/HelloSlim/Views"


    $app->run(); //load the application

    Предполагая, что проект организован в соответствии со структурой папок, определенной в предыдущем разделе, файлы routes.php и includes.php (в каталоге проекта) должны иметь следующее содержание:

    require "/usr/local/HelloSlim/Routes/route1.php";
    require "/usr/local/HelloSlim/Routes/route2.php";

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

    includes.php

    require "/usr/local/HelloSlim/Class/class1.php";
    require "/usr/local/HelloSlim/Class/class2.php";
    require "/usr/local/HelloSlim/Models/model1.php";
    require "/usr/local/HelloSlim/Models/model2.php";

    Образец приложения Slim

    Теперь можно приступить к созданию простого приложения, которое:

    • обрабатывает статические маршруты (GET & POST);
    • обрабатывает динамические маршруты;
    • использует виды.

    Примечание : при создании этого образца приложения предполагается, что фреймворк Slim был развернут согласно инструкциям руководства.

    Для начала нужно наметить требования этого примера приложения:

    Данному проекту требуются следующие файлы в папке приложения (/usr/local/HelloSlim/):

    HelloSlim
    |- Routes
    | |- getRoutes.php
    | |- postRoutes.php
    |- Views
    | |- footer.php
    | |- header.php
    | |- hello.php
    | |- greet.php
    |- routes.php

    Ниже показано, как будет работать общая папка.

    Вот пример структуры document root:

    Document Root (eg. /var/www/)
    |- .htaccess
    |- index.php

    Теперь нужно заполнить данные файлы следующим образом:

    1. /var/www/.htaccess

    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php

    2. /var/www/index.php

    require "/usr/local/Slim/Slim.php"; //include the framework in the project
    \Slim\Slim::registerAutoloader(); //register the autoloader
    $projectDir = "/usr/local/HelloSlim"; //define the directory containing the project files
    $app = new \Slim\Slim(array(
    "templates.path" => "/usr/local/HelloSlim/Views"
    )); //instantiate a new Framework Object and define the path to the folder that holds the views for this project
    require "$projectDir/routes.php"; //include the file which contains all the routes/route inclusions
    $app->run(); //load the application

    3. /usr/local/HelloSlim/Routes/getRoutes.php

    $app->get("/", function(){
    echo "This is a simple starting page";
    });
    //The following handles any request to the /hello route
    $app->get("/hello", function() use ($app){
    // the following statement invokes and displays the hello.php View
    $app->render("hello.php");
    });
    //The following handles any dynamic requests to the /hello/NAME routes (like /hello/world)
    $app->get("/hello/:name", function($name) use ($app){
    // the following statement invokes and displays the hello.php View. It also passes the $name variable in an array so that the view can use it.
    $app->render("hello.php", array("name" => $name));
    });

    4. /usr/local/HelloSlim/Routes/postRoutes.php

    //The following handles the POST requests sent to the /greet route
    $app->post("/greet", function() use ($app){
    //The following statement checks if "name" has been POSTed. If it has, it assigns the value to the $name variable. If it hasn"t been set, it assigns a blank string.
    $name = (null !== $app->request->post("name"))?$app->request->post("name"):"";
    //The following statement checks if "greeting" has been POSTed. If it has, it assigns the value to the $greeting variable. If it hasn"t been set, it assigns a blank string.
    $greeting = (null !== $app->request->post("greeting"))?$app->request->post("greeting"):"";
    // the following statement invokes and displays the "greet.php" View. It also passes the $name & $greeting variables in an array so that the view can use them.
    $app->render("greet.php", array(
    "name" => $name,
    "greeting" => $greeting
    ));
    });

    5. /usr/local/HelloSlim/Views/footer.php

    Copyright notice...

    6. /usr/local/HelloSlim/Views/header.php




    Sample Slim Application

    7. /usr/local/HelloSlim/Views/hello.php

    ***

    ***

    Hello