Анотація index php app core. Запити до статичних файлів

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

Для початку я вибрав просту та лаконічну структуру директорій проекту:

    /
  • app - директорія webroot сервера, сюди кладемо index.php
    • public – тут зберігатимемо статичні файли програми
  • src – директорія з вихідним кодом програми
    • Blog – кожен додаток знаходиться зі своєї директорії
  • lib – директорія для зовнішніх бібліотек. Тут же розташовуватиметься і код фреймворку
  • var – директорія для файлів програми (кеш, зберігання файлів, сесій тощо)

Із директоріями визначилися. Зараз ми можемо зручно розділяти окремі компоненти додатків з різних директорій, використовувати PSR0 для автозавантаження класів тощо.
Далі потрібно вибрати ім'я фреймворку, щоб елементарно створити директорію всередині lib. Недовго думаючи, я вибрав ім'я Bun - коротке, що запам'ятовується і ніби не конфліктує.

Усередині lib створюємо директорію bun (неймспейс вендора), у ній ще одну директорію bun (неймспейс бібліотеки), а всередині неї src – тут і розмістимо код нашого фреймворку. Така примудрена структура директорій дозволить нам надалі підключати фреймворк через http://packagist.org без зайвих проблем.

Далі виділяємо namespace для базових класів фреймворку - я вибрав Core. Щоб організувати в подальшому підключення компонетів поза директорією Core, потрібно буде організувати модульну структуру фреймворку. Тому першим компонентом системи у мене з'являється модуль (Bun Core Module).

Bun\Core\Module

Модуль повинен надавати базову інформацію незалежному компоненту фреймворку. Для початку - використовувані модулем конфігурації та залежності, версія, опис модуля тощо. Кожна директорія всередині lib/bun/bun/src – буде окремим модулем і повинна містити однойменний клас (наприклад, Bun Core Core.php) – який є реалізацією модуля.

Bun\Core\Application

Додаток є реалізацією патерна Front Controller - єдина точка входу в додаток для всіх запитів. В архітектурі Bun буде передбачено, що всі запити, включаючи php cli або запити статичних файлів, повинні оброблятися всередині Application. Примірник програми буде створюватися в index.php (стартовому файлі), при ініціалізації програми буде вказуватися його виконання (production, development, testing, etc). Вже додаток далі виконуватиме роботу із завантаження конфігурації, ініціалізації контейнера сервісів, роутингу запитів через допоміжні компоненти, виклик контролерів, видачу відповіді та завершення програми.

Перше, що робимо додаток при старті, як правило - завантажує файли конфігурації, тому наступним базовим компонентом буде Config

Bun\Core\Config

Для конфігурації програми та компонентів фреймворку виділено окремий набір класів – Bun Core Config Application Config – безпосередньо сервіс для управління конфігами і клас Bun Core Config Abstract Config – абстрактний клас, що є базою класом для елементів конфігурації. Як формат конфігурації вибрано PHP-клас. Це зручно з погляду кешування, тобто. вигідніше зберігати конфіги безпосередньо в коді програми, ніж використовувати окремі файли форматів xml, json, ini та інше.

Використання класів як конфігів також зручне і для поділу конфігурацій окремих компонентів і частин програми. А також зручно для перевизначення налаштувань фреймворку за замовчуванням усередині програми, або налаштувань програми всередині конкретного середовища виконання. За задумом кожен модуль чи додаток містить у собі простір імен Config – у якому складаються класи конфігурацій. Кожен клас має свій простір імен, а параметри конфігурації зберігає як масиву в захищеному властивості.
Доступ до конфіг передбачається через dot-нотацію $config->get("name1.name2.param1") .

Після того, як ми ініціалізували конфігурацію програми, можна приступати до обробки запиту. Для роботи із запитами та відповідями веб-додатків виділено окремий набір компонетів Http



Bun Core Http

Набір компонетів Http відповідатиме за абстрагування від роботи з суперглобалевими змінними $_SERVER, $_GET, $_POST тощо. За це відповідатиме сервіс Bun Core Http Request. Крім того, Http увійде клас Response - який буде стандартом результату роботи програми, тобто. запуск контролера повинен завішати отримання об'єкта Bun Core Http Response. Даний клас абстрагує нас від роботи з http-заголовками та ін. Крім того, зручно використовувати похідні класи типу AjaxResponse, DownloadResponse, ConsoleResponse і т.п.

Коли ми готові отримати інформацію про запит, можемо переходити до роутингу: наступний компонент – Router

Bun\Core\Router

Router – стандартний компонент сучасних веб-додатків на php. У Bun Framework роутері не передбачається нічого екстра ординарного, простий конфіг у вигляді масиву шаблонів url-запитів, який маппится на класи контролерів та їх екшенів. Я планую реалізувати можливість розбирати параметри з URL виду /page/view/:page_id - які будуть передаватися в екшен контролера у вигляді аргументів. Також планую зробити поділ запитів за методом (зручно, коли деякі методи можна викликати лише через POST – не потрібно робити зайвих перевірок у коді бізнес-логіки)

Від одного стандартного компонента php додатків переходимо до іншого – роутинг запитів тісно пов'язаний із реалізацією патерну MVC (Model View Controller). Тут передбачає поділ логіки програми, даних та відображення.

Bun Framework MVC

У реалізації MVC важко особливо відзначитися - тому тут теж все досить прозаїчно. Усередині Core я вироблю простір імен Controller – тут створюю базовий клас контролера, який має доступ до всіх копонентів системи, зберігає в собі об'єкт програми, що запустив його, сервіс конфігів. При ініціалізації контролер отримує параметри запуску: ім'я методу (екшену) та його аргументи.

Код відображення виділяю в окрему директорію View всередині Core. Bun Framework не передбачає певних компонентів типу View – мається на увазі, що ви просто смикаєте потрібний шаблон всередині контролера, передаючи туди дані. По-мочуванню я припускаю запит у фреймворк підтримку шаблонізатора Twig та підтримку нативних шаблонів *.phtml

Останній компонент – модель. Для неї також виділяю окремий namespace всередині Core: Model. Тут у справу буде вступати ще один базовий компонент фреймворку - ObjectMapper. Самі собою моделі – просто класи, тобто. не є ActiveRecord, але реалізують при цьому певний Bun Core Model Interface. Саме з такими класами вміє працювати ObjectMapper і зберігати їх у якесь сховище.

Тепер доведеться розповісти і про мапер об'єктів та про сховище, почнемо з першого.

Bun\Core\ObjectMapper

Мапер об'єктів – мабуть, найскладніша частина модуля Core фреймворку. Він буде сервісом, який вміє перетворювати об'єкт моделі в записи в якійсь базі даних, а також робити зворотну дію - брати запис зі сховища даних і маппіть її в об'єкт моделі. За замовчуванням модуль Core увійде сервіс, що реалізує файлове сховище об'єктів.

Bun\Core\Storage

Набір компонентів Storage (Сховище) є абстракцією та інтерфейсами, яким потрібно слідувати реалізації будь-яких сховищ у додатку. Першим таким сховищем буде Bun Core Storage FileStorage . Для своєї роботи файлове сховище буде використовувати набір допоміжних класів для роботи з файлами, а також для створення запитів на пошук записів у сховище файлів.

Описаний вище мапер об'єктів зможе працювати з будь-якою реалізацією Storage, щоб зберігати свої об'єкти. Тут слід виділити ще один важливий компонент модуля Core – Repository.

Bun\Core\Repository

Репозиторії – шар доступу об'єктом. Щоб не перевантажувати мапер об'єктів функціоналом пошуку та різних вибірок об'єктів зі сховища - ця робота передана в репозиторії. Репозиторій може працювати безпосередньо зі сховищем, вибирати від туди дані, а потім через ObjectMapper перетворювати їх на об'єкти та передавати додатку.

Відразу згадаю пов'язаний з вищеописаними компонентами - кеш

Bun\Core\Cache

Cache - міститиме набір абстракцій для реалізації взаємодії з різними кеш-сховищами, типу Redis, Memacached і т.п. Крім того, модуль Core буде включений компонент FileCacheDriver, який реалізує кешування даних у файлах.

Тут переходимо до важливого і, як я вважаю, визначального архітектури, компоненту фреймворку. Гнучкість і замінність компонентів досягається, коли ваш код не прив'язаний жорстко до випереджених сервісів, а може між ними швидко перемикатися. Крім того, наступний компонент робить величезну роботу з грамотної організації коду фреймворку та додатку.

Bun\Core\Container

Container – реалізує один із моїх улюблених патернів у програмуванні – Dependency Injection. Використання залежностей добре у всьому – частини додатка є слабко залежними від конкретних компонентів, окремі класи легко тестувати, замінюючи їх залежність. Зручно реалізовувати альтернативні варіанти реалізації в декількох сервісах, а потім легко перемикатися між ними навіть без змін коду програми. Плюс до всього ваші класи, що використовують впровадження залежностей, наочно показують свої залежності – ви навіть можете побудувати граф залежностей компонентів вашої програми.

Container ініціалізує та зберігає налаштовані сервіси в ході рантайму програми, коли до цих сервісів звертається додаток. Всередині одного класу ви отримуєте по суті контроль над усіма частинами програми.

На цьому базові компоненти фреймворку в модулі Core поки що закінчені. У ході реалізації в Core модуль можливо буде включено реалізації управління подіями, FormBuilder. Серед допоміжних комопнентів слід зазначити типізацію винятків. Базовий клас винятків Bun Core Exception Exception - передбачений для того, щоб всі інші типізовані винятки всередині програми і фреймворку успадковувалися від нього. Це забезпечує централізоване перехоплення винятків на рівні програми та перешкоджає виникненню не перехоплених винятків та авварійному падінню програми.

У наступних частинах розповіді про розробку Bun Framework я почну розповідати про компонент Application, а також опишу суті автозавантаження файлів за стандартом PSR0. Після цього, перейду до опису сервісу конфігурації програми та набору компонентів Http

З патернів мене влаштовувало mvc, registry. Для запитів я написав невеликий прошарок абстракції, для роутингу — свою функцію парсингу запиту.
Структура веб-додатку буде такою

Папка application

Вхідний файл index.php включає bootstrap.php. Той, у свою чергу, підключає ядро, конфіг-файл, деякі бібліотеки і запускає маршрутизатор.

Use Core\Route; require_once "lib/registry.php"; require_once "config.php"; require_once "lib/datebase.php"; require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; $router = новий Route(); $router->start(); // запускаємо маршрутизатор

Реєстр влаштований просто:

Namespace Lib; class Lib_Registry ( static private $data = array(); static public function set($key, $value) ( ​​self::$data[$key] = $value; ) self::$data[$key]) self::$data[$key] : null; ) static public function remove($key) ( if (isset(self::$data[$key]))) (self::$data[$key]); ) ) )

Тут гетери та сеттери для збереження глобальних значень.

Use Lib\Lib_Registry; define("PATH_SITE", $_SERVER["DOCUMENT_ROOT"]); define("HOST", "localhost"); define("USER", "root"); define("PASSWORD", "mypass"); define("NAME_BD", "articles"); define ("DS", DIRECTORY_SEPARATOR); $mysqli = new mysqli(HOST, USER, PASSWORD,NAME_BD)or die("Неможливо встановити з'єднання з базою даних".$mysqli->connect_errno()); Lib_Registry::set("mysqli", $mysqli); $mysqli->query("SET names "utf8""); //база встановлюємо кодування даних у базі

Запит виду http://domen.ru/articles/index перетворюється на Контроллер - Екшн. Назва контролера та екшна задається в стилі Zend framework, camel case - Controller_Name, function action_name (). Файл контролера, моделі, в'юхи повинен збігатися з назвою контролера в нижньому регістрі без префікса Controller або Model_

Клас моделі задається так само - Model_Name , файл завірюхи ми вже з'ясували - на ім'я екшна або явно в методі generate(name )

Так як у перспективі у нас створення адмінки створимо папки client і admin. До речі наш маршрутизатор враховуватиме вкладені папки, тобто. можна буде створити підпапки в контролерах (напр. /about/contacts/contacts.php ) та звернутися до нього на його шляху /about/contacts/
Отже, ми запустили маршрутизатор

/** * */ public function start() ( // catch AJAX request if ($this->getIsAjaxRequest()) ( ) session_start(); $this->dispatch(); ) /** * */ public function dispatch()( // диспетчер отримує файл, що збігається з назвою контролера, екшн і аргументи $this->getDirections($file, $controller, $action, $args); /* ************ * include Controller - Model */ if (is_readable($file) == false) ( die ("File $file 404 Not Found"); ) // підключили контролер include ($file); $model = str_replace("controller" , "model", $file);// Model additional if(is_readable($model))( // підключаємо модель include($model); ) /* ****** отримуємо клас ** */ $controller = $controller; // створюємо екземпляр $controller = new $class($this->controller_path_folder); if (is_callable(array( $controller, $action)) == false) ( die ("Action $action 404 Not Found"); ) // викликаємо екшн $controller->$action($args);

Диспетчер викликає метод getDirections(), тобто. одержати директиви запиту. За умовчанням дефолтний контролер - articles, екшн - index.

/** * @param $file * @param $controller * @param $action * @param $args */ private function getDirections(&$file, &$controller, &$action, &$args) ( $route = ( empty($_SERVER["REQUEST_URI"])) ? "" : $_SERVER["REQUEST_URI"]; unset($_SERVER["REQUEST_URI"]); $route = trim($route, "/\\"); $ controller_path = $this->path; if (empty($route)) ( /* ******************* Default directions ******** */ $controller = "articles"; $action = "action_index"; $controller_path = $this->controller_path_folder = "application/controllers/$this->namespace/"; ) else ( $parts = explode("/", $route); /* ************** namespace ********** */ if($parts = = "admin") ( $this->namespace = "admin"; array_shift($parts); ) /* ***************** folders & subfolders ***** ** */ $fullpath = $this->controller_path_folder = $controller_path . $this->namespace; foreach ($parts as $part) ($ Parts); continue; ) if (is_file($fullpath . ".php")) ( array_shift($parts); $file = "$fullpath.php"; break; ) ) /* ************* ** Controller, Action, Params ******** */ if(!isset($part)) $part = "articles"; $controller = $part; if(!$file) $file = $fullpath."/$part.php"; $action = array_shift($parts); if(!$action) $action = "action_index"; else $action = "action_$action"; $args = $parts; )

У наступному уроці розглянемо створення базових контролера, моделей та в'юх, напишемо запити.

Файли 1-го уроку тут

https://github.com/vaajnur/create_php_application
Гілка майстер буде 1-м уроків, далі для кожного уроку однойменна гілка - lesson1, lesson2, 3.

Останнє оновлення: 29.11.2017

У Angular є своя система маршрутизації, що дозволяє зіставляти маршрути з компонентами. Але в ASP.NET Core також є своя система маршрутизації, яка використовується для обробки запитів. З якими проблемами ми можемо мати справу? Наприклад, візьмемо проект із минулої теми, де вже використовувалась маршрутизація. Якщо всередині програми Angular ми переходитимемо за посиланнями, то у нас буде все нормально.

Однак якщо ми безпосередньо в адресному рядку браузера введемо потрібну нам адресу і відправимо запит, то такий запит не буде належним чином оброблений:

Незважаючи на те, що йде звернення на ту ж адресу, але в другому випадку ми отримаємо помилку 404.

Коли ми переходимо до посилання або програмним чином усередині програми Angular, то застосовується відповідний API HTML5 (Pushstate API), за допомогою якого змінюється URL у браузері без надсилання HTTP-запиту. Але коли ми вручну вводимо адресу ресурсу в адресному рядку браузера, то браузер надсилає новий запит до ASP.NET Core.

У прикладі вище надсилався запит "product/1". Але ми не маємо дії контролера, який би зіставлявся з подібним запитом. Тому, природно, ми отримаємо помилку. І нам необхідно додати додатковий код на стороні сервера, щоб подібні запити також оброблялися додатком Angular.

Що ми можемо зробити в цьому випадку? Все залежить від того, як формується веб-сторінка. Або це безпосередньо статична веб-сторінка, яка безпосередньо надсилається користувачеві. Або це уявлення (файл з розширенням cshtml), у якому визначено код завантаження програми Angulr.

Запити до статичних файлів

У минулій темі програма Angular завантажувалася на статичну веб-сторінку index.html, яка розміщувалася в проекті в папці wwwroot:

Angular in ASP.NET Core

І в цьому випадку нам треба, щоб якщо запит не призначався до статичного файлу, не призначався до жодної з дій контролера, то такий у відповідь на такий запит посилався файл index.html. І в даному випадку на стороні сервера ми можемо використати декілька підходів.

Метод Run

Найпростіший спосіб - додати до кінця конвеєра обробки запиту middleware, який би відправляв файл index.html. Для цього змінимо метод Configure у класі Startup:

Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions)) ( HotModuleReplace.) seStaticFiles( );app.UseMvc(); // обробка маршрутів, які не зіставлені з ресурсами раніше app.Run(async(context) => ( context.Response.ContentType = "text/html"; await context.Response.SendFileAsync(Path) .Combine(env.WebRootPath, "index.html")); ));

Таким чином, для всіх запитів, які не зіставлені з ресурсами у додатку, буде надсилатись файл wwwroot/index.html.

Мінусом такого підходу є те, що нам складно буде задіяти на стороні сервера обробку помилок 404. Оскільки якщо у нс немає контролерів і дій, що відповідають запиту, запит все одно буде оброблятися, щоправда, на стороні клієнта в Angular.

Надсилання файлу методом контролера

Подібний підхід надає відправлення файлу методом контролера. Наприклад, у проекті в папці Controllers є наступний контролер HomeController:

Using Microsoft.AspNetCore.Mvc; using System.IO; using Microsoft.AspNetCore.Hosting; namespace HelloAngularApp.Controllers ( public class HomeController: Controller ( IHostingEnvironment env; public HomeController(IHostingEnvironment env) ( this.env = env; ) public IActionResult Index() ( return new PhysicalFileResult(Path. "), "text/html"); ) ) )

У цьому випадку метод Index надсилає файл index.html.

У цьому випадку змінимо метод Configure класу Startup наступним чином:

Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions)) (HotModuleReplace.) UseStaticFiles (); app.UseMvc(routes =>

У цьому випадку для інфраструктури MVC визначено два маршрути. Перший маршрут зіставляється зі звичайними контролерами та його діями. Другий маршрут визначено за допомогою методу MapSpaFallbackRoute() і зіставляється з дією Index контролера Home, який у даному випадку відправляє користувачеві файл index.html.

Запити до дій контролера

Ми необов'язково можемо визначати завантаження Angular на статичній html-сторінці. Це може бути уявлення. Наприклад, визначимо в проекті каталог Views/Home і до нього помістимо новий файл Index.cshtml :

Angular in ASP.NET Core

В даному випадку представлення містить тільки код html, хоча при необхідності також можна визначити в ньому інструкції Razor, створити для нього майстер-сторінку і т.д.

А метод Index контролера HomeController буде використовувати це уявлення:

Using Microsoft.AspNetCore.Mvc; namespace HelloAngularApp.Controllers ( public class HomeController: Controller ( public IActionResult Index() ( return View(); ) ) )

У методі Configure класу Startup визначимо наступний код:

Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions ( HotModuleReplace.)) метод тепер не потрібний app.UseStaticFiles(); routes.MapSpaFallbackRoute("angular-fallback", new (controller = "Home", action = "Index")); ));

Тут знову ж таки за допомогою способу routes.MapSpaFallbackRoute всі інші запити, які не зіставлені з іншими ресурсами, будуть оброблятися шляхом Index контролера HomeController.