Download the PHP package pinahq/framework without Composer
On this page you can find all versions of the php package pinahq/framework. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download pinahq/framework
More information about pinahq/framework
Files in pinahq/framework
Package framework
Short Description The Pina2 Framework.
License MIT
Homepage http://pinahq.com
Informations about the package framework
Pina Framework
Введение
Pina Framework - PHP фреймворк для разработки RESTfull, stateless приложений, со встроенной поддержкой очередей (gearman) и асинхронной обработкой событий. На уровне работы с БД Pina поддерживает автоматическую генерацию структуры таблиц и триггеров на основании классов модели (как альтернативу миграциям).
Наша стратегия
Придерживаться стандартов PSR, схожего с Laravel именования методов и классов там, где это принципиально не влияет на производительность и идеологию фреймворка.
Наша идеология
Способствовать разработке производительных асинхронных RESTfull и stateless приложений на PHP.
Основывается на нескольких принципиальных позициях:
- Структура БД и триггеры - должны храниться в кодовой базе проекта вместе с другой бизнес логикой и генерироваться автоматически.
- Основа роутинга - однозначное отображение RESTfull ресурсов “коллекция/элемент/коллекция…” на контроллеры, позволяющий отказаться от больших таблиц роутинга.
- Модульность с привязкой к пространствам имен (namespace).
- Широкое применение очередей и фоновых процессов для затратных операций (пока на базе gearman).
Pina Framework - это не вещь в себе, он вырос на нескольких реальных проектах со сложной бизнес-логикой. И, надеюсь, вместе с вами мы сможем развить его до нового качественного уровня.
Установка
Pina не гарантирует в будущем поддержку версий PHP младше PHP7, но в данный момент корректно работает на PHP 5.6+.
Требует PHP-расширений:
- mysqli
- mbstring
- xml
Для начала работы нужно скачать bootstrap версию приложения (скоро будет на github и в composer), обновить зависимости через composer, прописать корректные настройки адреса сайта и БД, и запустить php pinacli.php system.update для обновления БД.
Настройки
В папке config расположены конфигурационные файлы. Каждый из них представляет собой PHP-файл, возвращающий ассоциативный массив. Для обращения к настройкам используется класс \Pina\Config.
Получить все настройки из файла config/app.php:
Получить пользователя для подключения к БД,
Структура папок:
Папка app
Здесь в папке default содержится ваше приложение, разбитое на подпапки Layout, Modules, Skin. Layout содержит шаблоны со структурой страниц, Modules содержит контроллеры, шаблоны и классы модулей, а Skin - шаблоны общих визуальных элементов.
Почему все хранится в папке default? Pina поддерживает перегрузку шаблонов для темы оформления сайта. И темы хранятся в папках на одном уровне с default. Таким образом у вас всегда в default оригинальные шаблоны, а папке шаблона - перегруженные.
Папка bootstrap
Содержит несколько стандартных файлов для запуска фреймворка
Папка config
Содержит настроечный файлы
Папка public
Здесь храним файл index.php, который обрабатывает все HTTP запросы, так же здесь храним статику (css, js, картинки).
Папка tests
Папка для юнит-тестов.
Папка var
Папка, где хранятся скомпилированные версии шаблонов, временные файлы, логи.
Обработка ошибок и логирование
...
Модульность
Приложение разбивается на модули. Каждый модуль - это определенное пространство имен (namespace) внутри Pina\Modules. Например Pina\Modules\Catalog.
Обычно код модуля расположен в папке внутри app/default/Modules (так как автозагрузка по стандарту PSR-4 настроена именно таким образом, что не мешает вам подгрузить новый модуль через composer).
Ключевым элементом модуля является класс Module внутри пространства имен модуля. Именно его система подгружает при инициализации модуля. И именно он указывает на точное местоположение модуля.
Класс модуля реализует интерфейс Pina\ModuleInterface. Поэтому он должен определить как минимум четыре метода:
- getPath() - возвращает путь к каталогу модуля
- getNamespace() - возвращает название пространства имен модуля
- getTitle() - возвращает имя модуля
- boot() - запускается каждый раз при старте приложения.
Так же класс модуля может реализовать методы под разные режимы запуска приложения. Например, метод будет вызываться каждый раз, когда запускается обработка http-запроса. А метод каждый раз, когда запускается обработка команды из командной строки. Эти методы должны вернуть настройки роутинга для своего типа приложения.
Так же обычно в классе модуля дополнительно объявляется функции в пространстве имен модуля. Например, функция локализации
Пример, объявления класса модуля:
Здесь в методе региструруется обработчик для события 'image.resize', в методе регистрируются обработчики на коллекции images и cp/images, а в методе регистрируется пространство команд image.
Кроме классов, относящихся к пространству имен модуля, в модуле предусмотрено несколько папок:
- Папка http с контроллерами и представлениями для обработки HTTP-запросов.
- Папка helpers для собственных smarty-функций модуля.
- Папка events для обработчиков событий
- Папка cli для контроллеров комманд командной строки.
- Папка lang для хранения файлов локализации приложения.
Слой HTTP
Роутинг
Чтобы повысить эффективность роутинга и избавиться от традиционных таблиц роутинга, связывающих маски адресов с контроллерами, в Pina существует ряд ограничений на структуру системных URL-адресов. Впрочем, если вас не устроит эта система, то фреймворк дает вам возможность зарегистрировать свой диспетчер и обрабатывать произвольные адреса самостоятельно, что очень полезно для CMS систем. Но об этом в другом разделе.
Вернемся к системному роутингу, который должен покрыть нужды большей части вашего приложения.
Pina требует от вас организовывать структуру URL в соответствии с REST-методологией. То есть структурировать URL-адреса по принципу “Коллекция/Элемент”. Таким образом список книг имел бы адрес /books, а книга с ID=5 имела бы адрес /books/5. Книги пользователя alex имели бы адрес /users/alex/books. Система интуитивно понятна, но имеет важное ограничение: Вы не можете задать коллекцию внутри коллекции минуя элемент. То есть адрес /users/books будет трактоваться как пользователь books внутри коллекции users.
Это ограничение дает возможность однозначно отображать множество коллекций на контроллеры, отбросив части с элементами. Например, GET-запрос к ресурсу /users/alex/books будет обрабатываться группой контроллеров в папке проекта /users/books
Обратите внимание, что за один структурный элемент ресурса отвечает целая группа контроллеров. Ниже объясню, почему мы пришли к такому положению дел.
HTTP протокол поддерживает разные методы запросов, но обычно используются GET, POST, PUT, DELETE, так как их очень удобно сопоставлять с типовыми CRUD-операциями,
HTTP-метод | CRUD-операция |
---|---|
POST | CREATE (создание) |
GET | READ (чтение) |
PUT | UPDATE (обновление) |
DELETE | DELETE (удаление) |
Если трактовать HTTP-методы, как CRUD операции, то сопоставив их с ресурсами в терминах “Коллекция/Элемент” получим следующие трактовки
HTTP-метод | Объект | Пример | Трактовка |
---|---|---|---|
GET | Коллекция | GET /users | Получить список пользователей |
GET | Элемент | GET /users/alex | Получить данные пользователя alex |
POST | Коллекция | POST /users | Добавить пользователя в коллекцию |
POST | Элемент | POST /users/alex | Добавить пользователя в коллекцию с определенным идентификатором |
PUT | Коллекция | PUT /users | Обновить информацию о пользователях |
PUT | Элемент | PUT /users/alex | Обновить данные о пользователе alex |
DELETE | Коллекция | DELETE /users | Удалить пользователей |
DELETE | Элемент | DELETE /users/alex | Удалить пользователя alex |
Таким образом над одной коллекцией можно задать разнообразный набор действий. Обычно в других фреймворках эти действия задаются методами одного класса-контроллера. Но мы заметили, что эти методы практически не связаны друг с другом, так как реализуют принципиально разную логику и разделили их на несколько типовых контроллеров для каждого типа действия. Эти контроллеры мы проименовали так, как обычно именуют методы класса-контроллера и положили в папку, соответствующей обрабатываемой коллекции.
Пример запроса | Обработчик |
---|---|
GET /users | /users/index.php |
GET /users/alex | /users/show.php |
GET /users/create | /users/create.php |
POST /users | /users/store.php |
POST /users/alex | /users/store.php |
PUT /users | /users/update.php |
PUT /users/alex | /users/update.php |
DELETE /users | /users/destroy.php |
DELETE /users/alex | /users/destroy.php |
Обратите внимание, что в целом контроллер определяется HTTP-методом.
Но для GET-запросов есть исключение (особый метод create). Таким образом, для GET-запросов система отличает:
- коллекцию (index.php),
- элемент (show.php),
- и ресурс для ввода информации для создания нового элемента (create.php).
Как это будет работать для вложенных коллекций:
Пример запроса | Обработчик |
---|---|
GET /users/alex/books | /users/books/index.php |
GET /users/alex/books/5 | /users/books/show.php |
GET /users/alex/books/create | /users/books/create.php |
POST /users/alex/books | /users/books/store.php |
POST /users/alex/books/5 | /users/books/store.php |
PUT /users/alex/books | /users/books/update.php |
PUT /users/alex/books/5 | /users/books/update.php |
DELETE /users/alex/books | /users/books/destroy.php |
DELETE /users/alex/books/5 | /users/books/destroy.php |
Контроллеры хранятся в папке http в корне модуля. Так как модулей у нас может много, то надо определять, какой модуль отвечает за какую коллекцию. Для этого модулю достаточно подтвердить факт владения коллекцией в методе http класса модуля:
Метод http модуля запускается каждый раз при старте приложения в начале обработки HTTP-запроса, а в качестве результата работы передает список коллекций (контроллеров), которые он обрабатывает.
Таким образом модуль пользователей может владеть коллекцией пользователей и по умолчанию владеть всеми вложенными коллекциями, но какой-то другой модуль (например, модуль книг) может объявить право на коллекцию книг пользователя:
Такой подход делает роутинг простым и быстрым.
Контроллеры
Итак, мы разобрались, где должен лежать файл контроллера, чтобы обрабатывать определенные URL-адреса. Теперь поговорим о том, как работает такой контроллер.
Основная идея контроллера Pina состоит в том, что для одного и того же контроллера можно было бы использовать несколько разных представлений. Поэтому контроллер просто принимает на вход параметры, а на выход отправляет результаты вычислений. О том, как именно они будут интерпретированы эти результаты и как именно будут они отображены, в общем случае контроллер не должен беспокоиться.
Для реализации этих целей контроллер работает с классом \Pina\Request и Pina\Response.
Получить параметры можно так:
Результаты контроллер возвращает через конструкцию return:
В таком случае контроллер передаст дальше данные, которые будут инерпретироваться в зависимости от выбранного представления. Но контроллер может так-же вернуть данные в каком-то определенном формате. Например, в JSON:
Предположим, наш контроллер на основе параметра ‘post_id’ должен получить из базы данных запись в блоге и вернуть её в качестве результата.
Усложним задачу. Теперь если запись не найдена, надо возвращать страницу 404:
Если не передан обязательный параметр post_id, возвращать код ошибки 400 bad request:
В общем случае контроллер должен вернуть либо данные, либо экземпляр класса, реализующего интерфейс \Pina\ResponseInterface.
Для доступа к параметрам запроса в классе \Pina\Request есть набор методов:
- Получить все параметры в виде массива:
- Получить конкретный параметр:
- Получить конкретный параметр и в случае его отсутствия использовать значение по умолчанию:
- Получить некоторые параметры: или
- Получить все параметры кроме некоторых: или
- Получить некоторые параметры, если они присутсвиуют: или
Вы могли обратить внимание, что интерфейс этих методов очень похож на то, что используется в Laravel. И это так и есть.
Валидация
…
Вложенные запросы.
Контроллер может обрабатывать, как и внешний HTTP-запрос, так и внутренний вызов из представления или другого контроллера. Это позволяет использовать контроллеры для обработки отдельных блоков HTML-страницы, подключая и отключая их прямо из шаблонизатора.
Представление (View)
У контроллера может быть несколько разных типов представлений. В данный момент на уровне автоматического определения поддерживаются два основных: JSON-представление и HTML-представление. Но и вы можете возвращать из контроллера любое представление, используя конструкцию return с конкретным экземпляром интерфейса \Pina\ResponseInterface.
Чтобы получить JSON-представление достаточно обратиться к ресурсу, используя заголовок Accept: application/json или Accept: text/json. В этом случае данные, переданные как результаты запроса, упакуются в json-объект. В целом проектировать контроллер нужно таким образом, чтобы результаты его работы коррелировали с сутью запроса к нему. Думать о его результатах отдельно от его представления, даже если основным представлением будет HTML, а не JSON.
Для построения HTML-представления используется шаблонизатор Smarty. Шаблон лежит в той же папке, что и контроллер, называется также (за исключением расширения: tpl, а не php). У одного контроллера может быть несколько HTML-представлений. Выбор представления управляется параметром display.
Например:
Метод | Ресурс | Контроллер | Шаблон |
---|---|---|---|
GET | books/5 | books/show.php | books/show.tpl |
GET | books/5?display=edit | books/show.php | books/show.edit.tpl |
GET | books/create | books/create.php | books/create.tpl |
GET | books/create?display=copy | books/create.php | books/create.copy.tpl |
POST | books | books/store.php | books/store.tpl |
PUT | books/5 | books/update.php | books/update.tpl |
DELETE | books/5 | books/delete.php | books/delete.tpl |
Обычно POST, PUT и DELETE запросы реализуют без HTML-представлений, просто перенаправляя клиента на нужный адрес. В этом случае контроллер должен вернуть экземпляр класса Response с указанием адреса перехода:
Шаблоны
Smarty - мощный шаблонизатор, с основными его функциями вы можете познакомиться на официальном сайте Smarty, я же изложу основные приемы и smarty-функции, которые мы используем для строительства приложения на основе Pina.
Родительский шаблон (Layout)
В папке app/default/Layout хранятся родительские шаблоны, в один которых будут вписаны результаты отрисовки запроса. По умолчанию используется шаблон main.tpl, но вы можете в конкретном отображении поменять родительский шаблон через smarty-функцию:
В таком случае будет использован родительский шаблон single.tpl
В родительский шаблон результат отрисовки будет передан в качестве переменной {$content}.
Обычно простой вставки результатов отрисовки запроса в какое-то одно место родительского шаблона не достаточно. Например, нам кроме центральной части надо заполнять заголовок и тег title.
Родительский шаблон мог бы выглядеть так:
main.tpl
Чтобы отдельно прокинуть контент в область для title, в дочернем шаблоне надо использовать smarty-функцию {content}
show.tpl
Использование других контроллеров в отображении
Из представления вы можете обращаться к другим контроллерам через smarty-функцию {module}.
Например:
В этом случае выполнится обработчик GET-запроса "books/:book_id", где заместо :book_id подставится значение из переменной $book.book_id. Результат его отрисовки в HTML и подставится в место вызова.
В эту smarty-функцию можно добавить дополнительные параметры, в том числе параметр display для выбора конкретного отображения.
Хорошей стратегией будет делать простые контроллеры и по необходимости компоновать их между собой в шаблонах.
Pina позволяет вам использовать отображение другого контроллера без вызова самого контроллера, если у вас уже есть подготовленные данные. Для этого можно использовать smarty-функцию {view}
В этом случае переменная book будет проброшена в шаблон, как если бы она была вычислена контроллером.
Ссылки
Для построения ссылок между страницами используется smarty-функция {link}
Этот пример сформирует ссылку на ресурс books/:book_id, подставив заместо :book_id значение $book.book_id. Параметры, которые нельзя подставить в шаблон ссылки, добавляются обычными GET-параметрами.
Превратится в ссылку /books/5?display=edit
Контекст ссылок
В шаблон ссылки подставляются значения из параметров ссылки. А что если какого-то элемента для подстановки в параметре нет? В этом случае подставится значение из контекста. Контекст может быть глобальным и локальным.
Глобальный контекст настраивается с помощью инструкции (обычно вызываемой в методе http() класса модуля):
Теперь во всех ссылках {link get="panel/:language/books"}, часть :language будет заменена на 'ru', если в параметрах не указано другое значение. Глобальный контекст действует только на замены.
Для настройки локального контектса существует блоковая смарти функция {link_context}, она добавляет ко всем вложенным в неё ссылкам свои параметры, даже если там нет таких замен.
Каждая ссылка внутри вызова {link_context} получит параметр author_id и sort.
Свои smarty-функции
Собственные smarty-функции модуля можно положить в папку helpers модуля.
Локализация
В php используется функция
В шаблонах используется блоковая smarty-функция {t} или модификатор |t
База данных
Настройка подключения к БД
За настройки реквизитов подключения к БД отвечает файл config/db.php. Например:
Класс DB
Класс DB подключается к базе данных и выполняет базовые запросы. Методы класса на вход обычно получают уже готовый запрос, и управляют в основном форматом результата.
Получить несколько записей из таблицы с помощью метода table:
Получить одну строку с помощью метода row:
column
Получить колонку:
value
Получить значение определенной ячейки
Выполнить запрос, который не возвращает результата:
Получить ID записи после её вставки в таблицу
Конструктор запросов.
Получить все записи из таблицы:
Получить одну запись из таблицы (первую в выборке)
Получить значение конкретной ячейки
Получить колонку из таблицы
Выборка с определенным полями
Метод select() в отличии от laravel добавляет указанные поля в выборку, а не заменяет их, то есть можно использовать несколько select с разными полями
Выборка по произвольному условию
whereBy проверяет соответствует ли поле значению или одному из значений массива.
Order by, Group by, having
Методы orderBy, groupBy, having просто добавляют соответствующие выражения “как есть”.
Limit
Соединения таблиц
Агрегатные функции
Проверка существования
Вставка данных
Получение идентификатора после вставки
Вставка нескольких записей одним запросом
Замена данных в таблице
Замена данных в таблице и получения идентификатора, если добавлен новый элемент
Обновление данных
Удаление данных
Модели и шлюзы таблиц
Модели, которые непосредственно связаны с одной таблицей в БД, обычно наследуются от класса TableDataGateway. Этот базовый класс дает модели инструментарий для описания и обработки мета-информации таблицы, а так же для гибкой выборки данных из неё. Так как TableDataGateway сам унаследован от класса контруктора запросов SQL, то модель получает все методы конструктора запросов, проинициализированного таблицей моделей. Что позволяет использовать всю мощь конструктора запросов, скрывая обращение к конкретным таблицам обращением к классам моделей. Пример для соединения таблиц на таких моделях можно было бы переписать следующим образом:
Модель часто определяет методы, расширяющие конструктор запросов и ориентированные на структуру своей таблицы. Это позволяет записывать сложные запросы простым путем и не дублировать часто используемые конструкции.
Как и в конструкторе запросов для простоты интерфейс многих методов определен с оглядкой на Laravel. Например, получить запись по первичному ключу.
Получить все данные из таблицы
Получить идентификатор (первичный ключ) записи
Выборка по идентификатору (первичному ключу)
Получить активные записи (у которых поле enabled = ‘Y’)
Удалить пользователя по его идентификатору
Обновить данные пользователя по его идентификатору
Генерация структуры таблицы
Pina поддерживает автоматическую генерацию структуры таблицы на основе описания полей и индексов в классе моделей.
В поле static::$table указываем имя таблицы в БД.
В массиве static::$fields указываем список полей, ключи массива - названия полей, а значения - описание поля в терминах MySQL.
В массиве static::$indexes указываем индексы таблицы. В качестве ключа массива используем тип ключа и имя ключа, а в качестве значений - набор полей. Если полей несколько, то надо использовать массивы.
Например:
All versions of framework with dependencies
psr/container Version ^1.0
smarty/smarty Version 2.6.*
monolog/monolog Version 1.23.*
phpmailer/phpmailer Version 5.2.*
league/climate Version 3.2.*
league/csv Version ^8.1
pinahq/sql-parser Version 0.3