Статьи

Может ли PHP быть еще быстрее? Скорость света с рамкой Blink

Люмен, Силекс, Слим и т. Д. Вы слышали о них всех. В этой статье мы рассмотрим новичка по имени Blink .

Абстрактное изображение, представляющее скорость

Blink был создан для улучшения высокопроизводительных приложений, которые потребляют много ресурсов сервера, и для достижения этой цели он использует расширение Swoole PHP. В качестве примера, мы создадим Blink на основе Notejam для нашей демонстрации. Давайте начнем.

Установка PHP-расширения Swoole

Ранее мы упоминали, что Blink использует расширение Swoole для повышения производительности.

Вы можете установить расширение Swoole из исходного кода на Github . Следуйте этой статье, если вы не знаете, как это сделать.

Альтернативный способ его установки — использование менеджера пакетов PEAR, который я и выберу. Я буду использовать образ Ubuntu 14.04 с настройкой LAMP для этой статьи, настроенной через Vaprobash — чтобы получить точную настройку, обратитесь к Vagrantfile в окончательном репо этого проекта . Мы могли бы использовать Homestead Improved для простой настройки среды, но расширение Swoole пока совместимо только с PHP 5, а Homestead Improved использует PHP 7 по умолчанию.

# update repositories sudo apt-get update # Install pear manager sudo apt-get install php - pear # PHP source to compile the extension sudo apt-get install php5 - dev # Used to install package dependencies sudo apt-get install libcurl3 - openssl - dev # Installing Swoole sudo pecl install swoole 

Текущая версия Swoole — 1.7.22-alpha и она не совместима с PHP 7 , но предпринимаются усилия, чтобы это произошло в следующей минорной версии.

После выполнения приведенных выше команд нас просят добавить строку extension=swoole.so в php.ini . Вы можете убедиться, что расширение загружено, перечислив доступную команду модулей PHP.

 php - m | grep 'swoole' 

Blink доступен на Packagist, поэтому мы можем использовать Composer для создания нового проекта.

 composer create - project -- prefer - dist blink / seed 

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

 php blink server serve 

Эта команда запустит интерактивную оболочку для журналов. Однако вы можете использовать команды php blink server start/stop/restart для управления сервером.

Мы можем убедиться, что все работает как положено, посетив порт 7788 нашего локального сервера:

Привет, мир

конфигурация

Хотя Blink не предоставляет опцию из командной строки для указания порта, мы можем использовать файл config/server.php чтобы изменить его.

 // src/config/server.php <?php return [ 'class' = > '\blink\server\SwServer' , 'bootstrap' = > require __DIR__ . '/../bootstrap.php' , 'host' = > '0.0.0.0' , 'port' = > 8080 , ] ; 

Теперь перезагрузите сервер и проверьте браузер, используя новый порт. Папка config также содержит конфигурацию для нашего приложения и сервисов.

Сборка приложения Notejam

Приложение Notejam не содержит много функций. Вы можете проверить финальное приложение на Github . Это предлагает:

  • Страница входа с опцией забытого пароля.
  • Страница регистрации.
  • Настройки учетной записи. (изменить пароль пользователя)
  • Управление заметками.
  • Управляющие колодки.

Установка шаблонного движка

Blink не предоставляет движок шаблонов по умолчанию, нам нужно выбрать и внедрить наш собственный. Я решил работать с Twig , но вы можете легко переключиться на другой движок, такой как Blade, Smarty и т. Д. Файл src/bootstrap.php отвечает за регистрацию наших маршрутов, служб и выполнение нашего приложения, и мы будем использовать его для привязки Двигатель ветки к контейнеру приложения.

 // src/bootstrap.php // ... $app = new blink \ core \ Application ( $config ) ; require __DIR__ . '/bindings.php' ; return $app ; 
 // src/bindings.php <?php /* Registering Twig */ $app - > bind ( 'twig.loader' , function ( ) { $view_paths = __DIR__ . '/views' ; $loader = new \ Twig_Loader_Filesystem ( $view_paths ) ; return $loader ; } ) ; $app - > bind ( 'twig' , function ( ) use ( $app ) { $options = [ 'debug' = > false , 'cache' = > __DIR__ . '/../cache/views/compiled' ] ; $twig = new \ Twig_Environment ( $app - > get ( 'twig.loader' ) , $options ) ;  // register Twig Extensions $twig - > addExtension ( new \ Twig_Extension_Debug ( ) ) ;  // register Twig globals $twig - > addGlobal ( 'app' , $app ) ; return $twig ; } ) ; 

Blink использует контейнер Yii для внедрения зависимостей. Он имеет конструктор и метод внедрения, автоматическое разрешение для зависимостей и т. Д. Проверьте документацию для более подробной информации

Установка менеджера баз данных

Приложение Notejam требует доступа к базе данных для управления пользователями, заметками и пэдами. Для этого мы собираемся использовать пакет Eloquent, поэтому нам нужно использовать его с помощью Composer.

 composer require illuminate / database 5.1 . * composer require illuminate / console 5.1 . * composer require illuminate / filesystem 5.1 . * 

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

 // src/bindings.php // ... /* Registering Eloquent DB Manager */ use Illuminate \ Database \ Capsule \ Manager as Capsule ; $capsule = new Capsule ; $capsule - > addConnection ( require __DIR__ . '/config/database.php' ) ; $app - > bind ( 'capsule' , $capsule ) ; $capsule - > setAsGlobal ( ) ; $capsule - > bootEloquent ( ) ; 
 // src/console/MigrateCommand.php class MigrateCommand extends Command { public $name = 'migrate' ; public $description = 'Migrate Database.' ; protected function execute ( InputInterface $input , OutputInterface $output ) { $capsule = app ( 'capsule' ) ; $connectionResolver = $capsule - > getDatabaseManager ( ) ; $repository = new DatabaseMigrationRepository ( $connectionResolver , 'migrations' ) ; if ( ! $repository - > repositoryExists ( ) ) { $repository - > createRepository ( ) ; } $migrator = new Migrator ( $repository , $connectionResolver , new Filesystem ) ; return $migrator - > run ( __DIR__ . '/../database/migrations' ) ; } } 
 // src/config/app.php <?php return [  // ... 'commands' = > [ 'App\Console\MigrateCommand' , ] ] ; 

После регистрации нашей команды мы можем запустить php blink migrate для создания нашей базы данных.

 // src/database/migrations/2015_11_14_00_create_users_table.php class CreateUsersTable extends Migration { public function up ( ) { Capsule : : schema ( ) - > create ( 'users' , function ( Blueprint $table ) { $table - > increments ( 'id' ) ; $table - > string ( 'email' ) ; $table - > string ( 'password' , 60 ) ; $table - > string ( 'remember_token' , 100 ) - > nullable ( ) ; $table - > timestamps ( ) ; } ) ; } public function down ( ) { Capsule : : schema ( ) - > drop ( 'users' ) ; } } 
 // src/database/migrations/2015_11_14_00_create_notes_table.php class CreateNotesTable extends Migration { public function up ( ) { Capsule : : schema ( ) - > create ( 'notes' , function ( Blueprint $table ) { $table - > increments ( 'id' ) ; $table - > string ( 'name' , 255 ) ; $table - > text ( 'text' ) ; $table - > integer ( 'user_id' ) ; $table - > integer ( 'pad_id' ) - > nullable ( ) ; $table - > timestamps ( ) ; } ) ; } public function down ( ) { Capsule : : schema ( ) - > drop ( 'notes' ) ; } } 
 // src/database/migrations/2015_11_14_00_create_pads_table.php class CreatePadsTable extends Migration { public function up ( ) { Capsule : : schema ( ) - > create ( 'pads' , function ( Blueprint $table ) { $table - > increments ( 'id' ) ; $table - > string ( 'name' , 255 ) ; $table - > integer ( 'user_id' ) ; $table - > timestamps ( ) ; } ) ; } public function down ( ) { Capsule : : schema ( ) - > drop ( 'pads' ) ; } } 

Маршруты

Маршруты приложений зарегистрированы в http/routes.php и должны возвращать массив, содержащий наши определения маршрутов.

 // src/http/routes.php return [ [ 'GET' , '/signin' , '\\App\\Http\\Controllers\\UserController@signin' ] ] ; 

Первый элемент в массиве должен определять метод запроса. Это может быть один элемент или массив для обработки нескольких методов запроса. Второй элемент определяет URL-адрес запроса и может содержать правила для параметров.

 // src/http/routes.php return [ [ 'GET' , '/notes/{id}' , '\\App\\Http\\Controllers\\NoteController@show' ] ] ; // You can also use regex to filter parameters. return [ [ 'GET' , '/notes/{id:\d+}' , '\\App\\Http\\Controllers\\NoteController@show' ] ] ; 

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

 // src/http/routes.php return [ [ 'GET' , '/' , function ( blink\ http \ Response $response ) { return $response - > redirect ( '/signin' ) ; } ] ,  // authentication [ 'GET' , '/signin' , 'UserController@signin' ] , [ 'POST' , '/signin' , 'UserController@processSignin' ] , [ 'GET' , '/signup' , 'UserController@signup' ] , [ 'POST' , '/signup' , 'UserController@store' ] , [ 'GET' , '/logout' , 'UserController@logout' ] , [ 'GET' , '/settings' , 'UserController@settings' ] , [ 'POST' , '/settings' , 'UserController@updateSettings' ] ,  //pads [ 'GET' , '/pads' , 'PadController@index' ] , [ 'GET' , '/pads/{id:\d+}' , 'PadController@show' ] , [ 'GET' , '/pads/create' , 'PadController@create' ] , [ 'POST' , '/pads/create' , 'PadController@store' ] , [ 'GET' , '/pads/{id:\d+}/update' , 'PadController@edit' ] , [ 'POST' , '/pads/{id:\d+}/update' , 'PadController@update' ] , [ 'GET' , '/pads/{id:\d+}/delete' , 'PadController@delete' ] ,  // Notes [ 'GET' , '/notes' , 'NoteController@index' ] , [ 'GET' , '/notes/{id:\d+}' , 'NoteController@show' ] , [ 'GET' , '/notes/create' , 'NoteController@create' ] , [ 'POST' , '/notes/create' , 'NoteController@store' ] , [ 'GET' , '/notes/{id:\d+}/update' , 'NoteController@edit' ] , [ 'POST' , '/notes/{id:\d+}/update' , 'NoteController@update' ] , [ 'GET' , '/notes/{id:\d+}/delete' , 'NoteController@delete' ] , ] ; 

Контроллеры

Контроллеры хранятся в src/http/controllers , и вы можете избежать использования полностью определенного пространства имен вашего класса, если храните их там. Однако вы можете использовать другой путь и изменить его в файле src/config/app.php .

 // src/config/app.php <?php return [  // ... 'controllerNamespace' = > '\app\http\controllers' , ] ; 
 // src/http/routes.php return [ [ 'GET' , '/notes/{id}' , 'NoteController@show' ]  // without a namespace ] ; 

Зарегистрироваться

Приложение Notejam предоставляет шаблоны HTML для использования, и мы просто собираемся преобразовать их в шаблоны Twig.

 // src/views/user/signup.htm {% extends ' layouts/layout.htm ' %} 

 {% block page_title %} Sign Up {% endblock %} 

 {% block content %} < form action = " /signup " method = " POST " class = " offset-by-six " > < label for = " email " > E-mail </ label > <input type="email" id="email" name="email" value=" {{ oldInputs . email }} " /> < label for = " password " > Password </ label > < input type = " password " id = " password " name = " password " value = " " /> < label for = " password_confirmation " > Password Confirmation </ label > < input type = " password " id = " password_confirmation " name = " password_confirmation " value = " " /> < input type = " submit " value = " Sign up " /> or < a href = " /signin " > Sign in </ a > </ form > {% endblock %} 
 

Мы расширяем основной макет, который определяет общую структуру страницы, включая заголовок нашей страницы и частичные ошибки.

 // src/views/partials/errors.htm {% set session = app . get ( ' request ' ) . session %} 

 {% if session . get ( ' success ' ) or session . get ( ' errors ' ) %} < div class = " alert-area " > {% if session . get ( ' success ' ) %} < div class = " alert alert-success " > {{ session . get ( ' success ' ) }} </ div > {% endif %} 
         {% if session . get ( ' errors ' ) %} < div class = " alert alert-error " > {% for error in session . get ( ' errors ' ) %} < ul class = " errorlist " > < li > {{ error }} </ li > </ ul > {% endfor %} </ div > {% endif %} </ div > {% endif %} 
 

У нас есть доступ к экземпляру контейнера app потому что мы зарегистрировали его как глобальную переменную внутри экземпляра Twig.

Наша форма регистрации содержит адрес электронной почты, пароль и поле для подтверждения пароля. Теперь нам нужно отрендерить нашу страницу.

 // src/http/controllers/UserController.php class UserController extends Object {  // ... public function signup ( ) { return app ( ) - > twig - > render ( 'user/signup.htm' ) ; } } 

Страница регистрации

Мы обрабатываем отправку формы внутри нашего метода UserController@store , и поскольку нам потребуется некоторая логика проверки, мы можем использовать компонент Illuminate/Validation . Итак, давайте свяжем его с контейнером, используя наш файл src/bindings.php .

 // src/bindings.php // ... /* Registering Validator */ use Illuminate \ Container \ Container ; use Illuminate \ Filesystem \ Filesystem ; use Illuminate \ Translation \ FileLoader ; use Illuminate \ Translation \ Translator ; use Illuminate \ Validation \ Factory ; use Illuminate \ Validation \ DatabasePresenceVerifier ; $loader = new FileLoader ( new Filesystem , __DIR__ . '/lang' ) ; $translator = new Translator ( $loader , 'en' ) ; $validation = new Factory ( $translator , new Container ) ; $validation - > setPresenceVerifier ( new DatabasePresenceVerifier ( app ( 'capsule' ) - > getDatabaseManager ( ) ) ) ; $app - > bind ( 'validation' , $validation ) ; 

Метод setPresenceVerifier полезен в этом случае, потому что мы будем использовать unique правило проверки, которое проверяет наличие элемента в базе данных.

 // src/http/controllers/UserController.php class UserController extends Object {  // ... public function store ( Request $request , Hash $hash ) { $rules = [ 'email' = > 'required|email|unique:users' , 'password' = > 'required|confirmed|min:8' ] ; $validator = app ( 'validation' ) - > make ( $request - > all ( ) , $rules ) ; if ( $validator - > fails ( ) ) { $request - > session - > add ( [ 'errors' = > $validator - > errors ( ) - > all ( ) , ] ) ; return app ( 'twig' ) - > render ( 'user/signup.htm' , [ 'oldInputs' = > $request - > all ( ) ] ) ; } $user = new \ App \ Models \ User ; $user - > email = $request - > body - > get ( 'email' ) ; $user - > password = $hash - > make ( $request - > body - > get ( 'password' ) ) ; $user - > save ( ) ; $request - > session - > add ( [ 'success' = > "Welcome to Notejam, you can now log in." ] ) ; return $response - > redirect ( '/signin' ) ; } } 

Blink поддерживает внедрение метода, поэтому мы можем просто передать наши классы в качестве параметра, и они будут автоматически разрешены для нас. После построения правил проверки, если что-то не получается, мы возвращаемся со старыми данными. Объект $request->session содержит наш текущий экземпляр сеанса, и здесь мы храним наши сообщения об успехах и ошибках, которые будут отображаться частичными ошибками.

Сессии & Amp; Печенье

Использование PHP-сессий, файлов cookie и функций заголовка не будет работать, но мы можем получить к ним доступ из объекта запроса ( $request->getSession() ) Обратите внимание, что это может быть проблемой для других пакетов, которые вы можете установить в свой проект.

Войти в систему

Прежде чем войти в систему, нам нужно настроить наш компонент авторизации.

 // src/config/services.php // ... 'auth' = > [ 'class' = > 'blink\auth\Auth' , 'model' = > 'App\Models\User' , ] , // ... 

Следующим шагом является обновление модели User и заполнение методов интерфейса Authenticatable .

 // src/models/User.php class User extends Model implements Authenticatable { protected $table = 'users' ; public static function findIdentity ( $id ) { if ( is_numeric ( $id ) || ( is_array ( $id ) && isset ( $id [ 'id' ] ) ) ) { return static : : where ( 'id' , $id ) - > first ( ) ; } else if ( is_string ( $id ) || ( is_array ( $id ) && isset ( $id [ 'email' ] ) ) ) { return static : : where ( 'email' , $id ) - > first ( ) ; } else { throw new \ InvalidParamException ( "The param: id is invalid." ) ; } } public function getAuthId ( ) { return $this - > id ; } public function validatePassword ( $password ) { return ( new Hash ) - > check ( $password , $this - > password ) ; } } 

Метод findIdentity будет использоваться для аутентификации пользователей с использованием первичного ключа или электронной почты, а validatePassword проверит пароль входа в систему против текущего пользовательского экземпляра.

 // src/views/user/signin.htm {% extends ' layouts/layout.htm ' %} 

 {% block page_title %} Sign In {% endblock %} 

 {% block content %} < form action = " /signin " method = " POST " class = " offset-by-six " > < label for = " email " > E-mail </ label > <input type="email" value=" {{ oldInputs . email }} " name="email" id="email" /> < label for = " email " > Password </ label > < input type = " password " value = " " name = " password " id = " password " /> < input type = " submit " value = " Sign in " /> or < a href = " /signup " > Sign up </ a > < hr /> < p > < a class = " small-red " href = " /forgot_password " > Forgot Password? </ a > </ p > </ form > {% endblock %} 
 

Войти на страницу

 // src/controllers/UserController.php class UserController extends Object {  // ... public function processSignin ( Request $request , Hash $hash ) { $rules = [ 'email' = > 'required|email' , 'password' = > 'required|min:8' ] ; $validator = app ( 'validation' ) - > make ( $request - > all ( ) , $rules ) ; if ( $validator - > fails ( ) ) { $request - > session - > add ( [ 'errors' = > $validator - > errors ( ) - > all ( ) , ] ) ; return app ( 'twig' ) - > render ( 'user/signin.htm' , [ 'oldInputs' = > $request - > all ( ) ] ) ; } $user = auth ( ) - > attempt ( $request - > only ( [ 'email' , 'password' ] ) ) ; if ( ! $user ) { $request - > session - > add ( [ 'errors' = > [ 'Login error.' ] , ] ) ; return app ( 'twig' ) - > render ( 'user/signin.htm' , [ 'oldInputs' = > $request - > all ( ) ] ) ; } $cookies = $response - > getCookies ( ) - > add ( new \ blink \ http \ Cookie ( [ 'name' = > 'SESSIONID' , 'value' = > $request - > session - > id ] ) ) ; return $response - > redirect ( '/settings' ) ; } } 

Мы проверяем входные данные таким же образом и отображаем ошибки в случае сбоя. Помощник auth() разрешит экземпляр blink/auth/Auth из контейнера, и мы попытаемся войти в систему с учетными данными запроса. Если учетные данные верны, мы перенаправляем пользователя на страницу настроек. Blink не управляет нашим сеансом автоматически, поэтому нам нужно установить cookie сеанса при входе в систему и установить его по запросу при загрузке страницы. Blink позволяет нам привязывать наши сервисы к контейнеру с помощью массива конфигурации.

 // src/config/services.php return [ 'request' = > [ 'class' = > \ blink \ http \ Request : : class , 'middleware' = > [ \ App \ Http \ Middleware \ AuthMiddleware : : class ] , 'sessionKey' = > function ( \ blink \ http \ Request $request ) { $cookie = $request - > cookies - > get ( 'SESSIONID' ) ; if ( $cookie ) { return $cookie - > value ; } } ] ,  // ... ] ; 

Blink использует концепцию конфигурации Yii для инициализации атрибутов класса. Мы используем функцию закрытия для инициализации sessionKey с помощью переменной cookie, если она существует.

После успешной попытки входа в систему мы можем получить доступ к зарегистрированному пользователю из объекта запроса, используя $request->user() и $request->guest() . Проверьте документацию для более подробной информации об аутентификации.

Настройки учетной записи

Зарегистрированный пользователь имеет возможность сменить свой пароль. Он должен ввести старый пароль, новый пароль и подтверждение. Мы следуем тем же шагам, что и раньше.

 // src/http/controllers/UserController.php class UserController extends Object { public function updateSettings(Request $request, Hash $hash) { $user = $request->user(); $rules = [ 'old_password' => 'required|min:8', 'password' => 'required|confirmed|min:8' ]; $validator = app('validation')->make($request->all(), $rules); if ( $validator->fails() ) { $request->session->add([ 'errors' => $validator->errors()->all(), ]); return app('twig')->render('user/settings.htm', [ 'oldInputs' => $request->all() ]); } if( !$hash->check($request->input('old_password'), $user->password) ) { $request->session->add([ 'errors' => ['Old password incorrect.'], ]); return app('twig')->render('user/settings.htm', [ 'oldInputs' => $request->all() ]); } $user->password = $hash->make($request->input('old_password')); $user->save(); $request->session->add([ 'success' => 'settings updated successfuly.', ]); return app('twig')->render('user/settings.htm'); } } 

Метод $hash->check сравнивает значение с хешированной версией. После обновления пользовательских настроек мы возвращаемся с сообщением об успехе. Проблема здесь в том, что мы обращаемся к методу $request->user() напрямую, который возвращает текущего вошедшего в систему пользователя, но что, если пользователь еще не вошел в систему и пытается получить доступ к странице настроек?

Страница настроек аккаунта

Промежуточное

Для защиты наших маршрутов мы можем определить список промежуточного программного обеспечения для нашего объекта запроса. Мы можем сделать это внутри нашего конфигурационного файла services.php .

 // config/services.php return [ 'request' = > [ 'class' = > \ blink \ http \ Request : : class , 'middleware' = > [ \ App \ Http \ Middleware \ AuthMiddleware : : class ] , 'sessionKey' = > function ( \ blink \ http \ Request $request ) { $cookie = $request - > cookies - > get ( 'SESSIONID' ) ; if ( $cookie ) { return $cookie - > value ; } } ] ,  // ... ] ; 

Теперь нам нужно создать наше AuthMiddleware в AuthMiddleware src/http/middleware . Промежуточное программное обеспечение будет перехватывать все запросы, и мы должны сравнить текущий запрос с нашими защищенными маршрутами.

 // src/http/middleware/AuthMiddleware.php class AuthMiddleWare implements MiddlewareContract { public function handle ( $request ) { $guardedRoutes = [ '/\/settings/' , '/\/logout/' , '/\/notes?\/*.*/' , '/\/pads?\/*.*/' , ] ; if ( ! $request - > user ( ) ) { foreach ( $guardedRoutes as $route ) { if ( $request - > match ( $route ) ) { return response ( ) - > redirect ( '/signin' ) ; } } } if ( $request - > user ( ) && in_array ( $request - > path , [ '/signin' , '/signup' ] ) ) { return response ( ) - > redirect ( '/settings' ) ; } } } 

Метод Request@match проверяет текущий путь запроса на соответствие регулярному выражению. Он использует функцию PHP preg_match , поэтому нам нужно определить наши защищенные маршруты как регулярные выражения.
Если пользователь не вошел в систему и пытается получить доступ к одному из защищенных маршрутов, мы перенаправляем его на страницу входа. Мы также хотим запретить пользователю доступ к signin и signup когда они вошли в систему.

Вы также можете использовать методы before и after внутри контроллера для достижения того же результата.

 // src/http/controllers/UserController.php class UserController extends Object { public function before ( $action , $request ) {  // do something } public function after ( $action , $request , $response ) {  // do something } } 

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

Список заметок

Класс NoteController отвечает за обработку CRUD-операций наших заметок.

 // src/http/controllers/NoteController.php class NoteController extends Object { public function index ( Request $request , Note $noteModel ) { $notes = $noteModel - > where ( 'user_id' , $request - > user ( ) - > id ) - > with ( 'pad' ) - > get ( ) ; return app ( 'twig' ) - > render ( 'notes/index.htm' , [ 'notes' = > $notes ] ) ; } } 

Поскольку Blink поддерживает внедрение метода, мы передаем объекты Request и Note и запрашиваем заметки пользователя.

 // src/views/notes/index.htm {% extends ' layouts/layout.htm ' %} 

 {% block page_title %} My Notes {% endblock %} 

 {% block content %} 
     {% if notes | length %} < table class = " notes " > < tr > < th class = " note " > Note </ th > < th > Pad </ th > < th class = " date " > Last modified </ th > </ tr > {% for note in notes %} < tr > < td > <a href="/notes/ {{ note . id }} "> {{ note . name }} </ a > </ td > < td class = " pad " > {% if note . pad is defined %} <a href="/pads/ {{ note . pad . id }} "> {{ note . pad . name }} </ a > {% else %} No pad {% endif %} </ td > < td class = " hidden-text date " > {{ note . updated_at | date ( " F jS \\a\\tg:ia " ) }} </ td > </ tr > {% endfor %} </ table > {% else %} < p class = " empty " > Create your first note. </ p > {% endif %} < a href = " /notes/create " class = " button " > New note </ a > {% endblock %} 
 

Страница списка заметок

Посмотреть примечание

Зарегистрированный пользователь может только просматривать свои заметки и управлять ими. И поскольку нам нужно часто проверять это условие, мы собираемся поместить его в отдельный метод.

 // src/http/controllers/NoteController.php class NoteController extends Object { protected function noteExists ( $noteId ) { $request = request ( ) ; $note = Note : : with ( 'pad' ) - > where ( 'user_id' , $request - > user ( ) - > id ) - > where ( 'id' , $noteId ) - > first ( ) ; if ( ! $note ) { $request - > session - > add ( [ 'errors' = > [ "Note not found." ] , ] ) ; return false ; } return $note ; }  // ... } 
 // src/http/controllers/NoteController.php class NoteController extends Object { public function show ( $id , Request $request , Response $response , Note $noteModel ) { $note = $this - > noteExists ( $id ) ; if ( ! $note ) { return $response - > redirect ( '/notes' ) ; } return app ( ) - > twig - > render ( 'notes/view.htm' , [ 'note' = > $note ] ) ; } } 
 // src/views/notes/view.htm {% extends ' layouts/layout.htm ' %} 

 {% block page_title %} {{ note . name }} {% endblock %} 

 {% block content %} < p class = " hidden-text " > Last edited at {{ note . updated_at | date ( " F jS \\a\\tg:ia " ) }} </ p > < div class = " note " > < p > {{ note . text }} </ p > </ div > <a href="/notes/ {{ note . id }} /update" class="button">Edit </ a > <a href="/notes/ {{ note . id }} /delete" class="delete-note">Delete it </ a > {% endblock %} 
 

Просмотр страницы заметки

Новая заметка

Новый вид заметки имеет имя, текст и ввод с клавиатуры.

 // src/views/notes/create.htm {% extends ' layouts/layout.htm ' %} 

 {% block page_title %} New Note {% endblock %} 

 {% block content %} < form action = " /notes/create " method = " POST " > < label for = " name " > Name </ label > <input type="text" id="name" name="name" value=" {{ oldInputs . name }} "/> < label for = " text " > Text </ label > <textarea id="text" name="text" value=" {{ oldInputs . text }} "> </ textarea > < label for = " pad " > Pad </ label > < select id = " pad " name = " pad " > {% for pad in pads %} <option value=" {{ pad . id }} "> {{ pad . name }} </ option > {% endfor %} </ select > < input type = " submit " value = " Create " /> </ form > {% endblock %} 
 
 // src/http/controllers/NoteController.php class NoteController extends Object { public function create ( Request $request , Pad $padModel ) { $pads = $padModel - > where ( 'user_id' , $request - > user ( ) - > id ) - > get ( ) ; return app ( 'twig' ) - > render ( 'notes/create.htm' , [ 'pads' = > $pads ] ) ; } } 

Новая страница заметки

Поскольку мы уже установили наши отношения модели, мы также можем сделать что-то вроде $request->user()->pads . Метод store обрабатывает отправку формы.

 // src/http/controllers/NoteController.php class NoteController extends Object { public function store ( Request $request , Response $response ) { $rules = [ 'name' = > 'required' , 'text' = > 'required' , 'pad' = > 'required|exists:pads,id' ] ; $validator = app ( 'validation' ) - > make ( $request - > all ( ) , $rules ) ; if ( $validator - > fails ( ) ) { $request - > session - > add ( [ 'errors' = > $validator - > errors ( ) - > all ( ) , ] ) ; return app ( 'twig' ) - > render ( 'notes/create.htm' , [ 'oldInputs' = > $request - > all ( ) ] ) ; } $note = new Note ; $note - > name = $request - > input ( 'name' ) ; $note - > text = $request - > input ( 'text' ) ; $note - > user_id = $request - > user ( ) - > id ; $note - > pad_id = $request - > input ( 'pad' ) ; $note - > save ( ) ; $request - > session - > add ( [ 'success' = > 'Note saved successfully.' , ] ) ; return $response - > redirect ( "/notes/{$note->id}/update" ) ; } } 

Мы следуем тому же процессу проверки данных и возврата ошибок в случае сообщений. Чтобы избежать вставки повторяющихся задач для обновления, удаления и просмотра заметок, вы можете проверить конечный результат на Github . Хранилище также содержит этапы установки.

Финальные заметки

Хотя Swoole и Blink пытаются экономить ресурсы сервера, поддерживая ресурсы приложения в течение всего срока его службы, объекты запросов и ответов всегда обновляются при каждом запросе. Blink предоставляет интерфейс ShouldBeRefreshed , который можно реализовать, чтобы указать, что экземпляр класса должен обновляться при каждом запросе.

Вывод

Blink является новичком и все еще находится на ранней стадии разработки. Вы можете сотрудничать, написав документацию для различных компонентов и отправляя запросы на извлечение новых функций. Если у вас есть какие-либо вопросы или комментарии, вы можете опубликовать их ниже и сообщить нам, что вы думаете об этой новой структуре!