Статьи

Быстрая разработка корпоративных приложений с Zend Expressive

Если вы когда-либо делали быстрый старт Zend Framework, вы, вероятно, никогда не работали в Zend Framework. Быстрый старт исторически был далеко не быстрым, и легко потерять интерес и перейти к следующему.

Zend Expressive значительно улучшает этот опыт с помощью команды composer create-project запускаемой мастером. Тем не менее, это может быть сложно настроить, потому что есть так много вариантов, чтобы сделать заранее. Этот учебник проведет вас через мои рекомендуемые настройки для быстрой разработки, которая будет
выход на уровне предприятия, надежное приложение.

Логотип Zend Framework

Это руководство не о настройке вашей среды, поэтому я предполагаю, что у вас хорошая рабочая среда, например, Homestead Improved .

Если вы не знакомы с Vagrant или изолированными виртуальными средами, у нас есть удивительная книга, которая поможет вам ознакомиться с концепциями, доступными в нашем магазине здесь .

Настройка проекта

Запустите ваш проект, выполнив следующую команду в папке, где вы храните свои проекты ( Code на Homestead Improved ):

 composer create-project zendframework/zend-expressive-skeleton expressive 

Вам будет предложено принять несколько решений по пути. Используйте эти ответы:

  • Какой тип установки вы бы хотели?
    • модульная
  • Какой контейнер вы хотите использовать для внедрения зависимостей?
    • Zend ServiceManager
  • Какой роутер вы хотите использовать?
    • Zend Router
  • Какой шаблонизатор вы хотите использовать?
    • прут
  • Какой обработчик ошибок вы хотите использовать во время разработки?
    • Возгласы
  • Пожалуйста, выберите файл конфигурации, который вы хотите внедрить в Zend \ Validator \ ConfigProvider?
    • Config / config.php
  • Помните эту опцию для других пакетов того же типа?
    • Y

Затем выполните эти команды:

 cd expressive && git init && git config color.ui true && git add . && git commit -m "Initial commit" && chmod -R +w data; 

Это инициализирует хранилище во вновь созданной папке и делает папку data доступной для записи.

Затем запустите php-сервер для тестирования с

 composer serve 

… И перейдите по адресу http: // localhost: 8080 или просто посетите IP-адрес виртуальной машины или виртуальный хост, если вы используете Homestead Improved .

Zend Expressive Главная страница

Понимание Выразительный

Структура папок Expressive выглядит следующим образом:

 bin/ config/ data/ cache/ public/ index.php src/ App test/ AppTest vendor/ 

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

Expressive поставляется с несколькими удобными командами:

  • ./vendor/bin/expressive — Создание, регистрация и ./vendor/bin/expressive регистрации модулей. Создать класс промежуточного программного обеспечения и т. Д.
  • composer serve — псевдоним для запуска сервера php-fpm
  • composer cs-check — выполнить проверку стандартов кодирования для вашего кода.
  • composer cs-fix — Выполните проверку стандартов кодирования в вашем коде и по возможности исправьте проблемы.
  • composer test — запуск тестов PHPUnit для вашего кода.
  • composer check — псевдоним для запуска cs-check , затем test.

Expressive также поставляется с обработчиком ошибок Whoops. Чтобы проверить это, откройте src/App/src/Action/HomePageAction.php и введите echo $badVar в echo $badVar process() , а затем обновите страницу. Вы увидите обработчик ошибок Whoops.

Обработчик ошибок

Необходимые улучшения

Отражение на основе абстрактной фабрики

Zend Expressive использует Zend ServiceManager для внедрения зависимостей. В настройках по умолчанию вам нужно добавить конфигурацию и, возможно, создать фабричный класс для каждого написанного вами класса. Это кажется обременительным после выполнения этого примерно в два раза.

Чтобы избежать этого, мы включим абстрактную фабрику на основе отражений, предоставляемую Zend Expressive.

Добавьте это в config/autoload/dependencies.global.php в массиве dependencies :

 'abstract_factories' => [ \Zend\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory::class ], 

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

Если вы беспокоитесь о скорости; В производстве у нас может быть процесс, который генерирует фабрики для ваших классов, которые обрабатывались фабрикой отражений с vendor/bin/generate-factory-for-class .

доктрина

Zend Expressive не предоставляет инструментов для работы с базами данных или ORM. Я выбрал Doctrine в качестве своего ORM после долгих исследований и создания нескольких собственных ORM. Это просто работает.

Установите Doctrine и Symfony Yaml через Composer:

 composer require dasprid/container-interop-doctrine symfony/yaml 

Создайте файл config/cli-config.php со следующим содержимым:

 <?php use Doctrine\ORM\Tools\Console\ConsoleRunner; chdir(dirname(__DIR__)); require 'vendor/autoload.php'; /** * Self-called anonymous function that creates its own scope and keep the global namespace clean. */ return call_user_func(function () { /** @var \Interop\Container\ContainerInterface \$container */ $container = require 'config/container.php'; $entityManager = $container->get(\Doctrine\ORM\EntityManager::class); return ConsoleRunner::createHelperSet($entityManager); }); 

Замените содержимое config/autoload/dependencies.global.php следующим текстом:

 <?php use Zend\Expressive\Application; use Zend\Expressive\Container; use Zend\Expressive\Delegate; use Zend\Expressive\Helper; use Zend\Expressive\Middleware; return [ // Provides application-wide services. // We recommend using fully-qualified class names whenever possible as // service names. 'dependencies' => [ 'abstract_factories' => [ \Zend\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory::class ], // Use 'aliases' to alias a service name to another service. The // key is the alias name, the value is the service to which it points. 'aliases' => [ 'Zend\Expressive\Delegate\DefaultDelegate' => Delegate\NotFoundDelegate::class, ], // Use 'invokables' for constructor-less services, or services that do // not require arguments to the constructor. Map a service name to the // class name. 'invokables' => [ // Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class, \Doctrine\DBAL\Logging\DebugStack::class => \Doctrine\DBAL\Logging\DebugStack::class, Helper\ServerUrlHelper::class => Helper\ServerUrlHelper::class, Middleware\ImplicitHeadMiddleware::class => Middleware\ImplicitHeadMiddleware::class, Middleware\ImplicitOptionsMiddleware::class => Middleware\ImplicitOptionsMiddleware::class, ], // Use 'factories' for services provided by callbacks/factory classes. 'factories' => [ Application::class => Container\ApplicationFactory::class, Delegate\NotFoundDelegate::class => Container\NotFoundDelegateFactory::class, \Doctrine\ORM\EntityManager::class => \ContainerInteropDoctrine\EntityManagerFactory::class, Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class, Helper\UrlHelper::class => Helper\UrlHelperFactory::class, Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class, Zend\Stratigility\Middleware\ErrorHandler::class => Container\ErrorHandlerFactory::class, Middleware\ErrorResponseGenerator::class => Container\ErrorResponseGeneratorFactory::class, Middleware\NotFoundHandler::class => Container\NotFoundHandlerFactory::class, ], ], ]; 

Создайте этот файл для настройки драйвера Doctrine config/autoload/doctrine.global.php .

 <?php return [ 'doctrine' => [ 'driver' => [ 'orm_default' => [ 'class' => \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain::class, 'drivers' => [], ], ], ], ]; 

Создайте этот файл для своих учетных данных базы данных config/autoload/doctrine.local.php .

 <?php return [ 'doctrine' => [ 'connection' => [ 'orm_default' => [ 'params' => [ 'url' => 'mysql://root:password1@localhost/expressive', ], ], ], ], ]; 

Проверьте, запустив ./vendor/bin/doctrine . Вы должны увидеть подсказку.

Глоток

Gulp — мой текущий инструмент выбора для внешнего интерфейса. Существует множество инструментов для построения внешнего интерфейса. Посмотрите, если хотите, но вы можете потеряться в море блестящих новых библиотек JavaScript. Я не хочу вдаваться в подробности, поскольку это больше учебник по PHP, чем JS, но я хочу показать, как следует настроить gulp для работы с Zend Expressive.

Создайте файл package.json со следующим содержимым:

 { "name": "expressive", "version": "1.0.0", "description": "", "main": "index.js", "devDependencies": { "del": "^3.0.0", "gulp": "github:gulpjs/gulp#4.0", "gulp-cached": "^1.1.1", "gulp-imagemin": "^3.3.0", "gulp-minify-css": "^1.2.4", "gulp-rename": "^1.2.2", "gulp-sass": "^3.1.0", "gulp-uglify": "^2.1.2", "gulp-usemin": "^0.3.28" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } 

Запустите npm install . Вы также можете запустить npm update , если вы читаете это руководство через некоторое время после его написания.

Затем создайте gulpfile.js со следующим содержимым:

 'use strict'; var cache = require('gulp-cached'); var del = require('del'); var gulp = require('gulp'); var imagemin = require('gulp-imagemin'); var minifyCss = require('gulp-minify-css'); var path = require('path'); var rename = require('gulp-rename'); var sass = require('gulp-sass'); var uglify = require('gulp-uglify'); // CSS Processing gulp.task('clean-css', function() { return del('public/css', { force: true }); }); gulp.task('compile-sass', function() { return gulp.src('src/*/public/sass/**/*.scss', { base: './' }) .pipe(cache('compile-sass')) .pipe(sass().on('error', sass.logError)) .pipe(rename(function (path) { path.dirname = path.dirname.replace(/^src\/([^\/]+\/)public\/sass/, '$1'); })) .pipe(gulp.dest('public/css/')); }); gulp.task('copy-css', function() { return gulp.src('src/*/public/css/**/*.css', { base: './' }) .pipe(cache('copy-css')) .pipe(rename(function (path) { path.dirname = path.dirname.replace(/^src\/([^\/]+\/)public\/css/, '$1'); })) .pipe(gulp.dest('public/css/')); }); gulp.task('minify-css', function() { return gulp.src(['public/css/**/*.css', '!public/css/**/*.min.css'], { base: './' }) .pipe(cache('minify-css')) .pipe(minifyCss()) .pipe(rename(function (path) { path.dirname = path.dirname.replace(/^public\/css/, ''); })) .pipe(rename({ extname: '.min.css' })) .pipe(gulp.dest('public/css')) ; }); gulp.task('process-css', gulp.series(['compile-sass', 'copy-css'], 'minify-css')); // JS Processing gulp.task('clean-js', function() { return del('public/js', { force: true }); }); gulp.task('copy-js', function() { return gulp.src('src/*/public/js/**/*.js', { base: './' }) .pipe(cache('copy-js')) .pipe(rename(function (path) { path.dirname = path.dirname.replace(/^src\/([^\/]+\/)public\/js/, '$1'); })) .pipe(gulp.dest('public/js/')); }); gulp.task('minify-js', function() { return gulp.src(['public/js/**/*.js', '!public/js/**/*.min.js'], { base: './' }) .pipe(cache('minify-js')) .pipe(uglify()) .pipe(rename(function (path) { path.dirname = path.dirname.replace(/^public\/js/, ''); })) .pipe(rename({ extname: '.min.js' })) .pipe(gulp.dest('public/js')) ; }); gulp.task('process-js', gulp.series('copy-js', 'minify-js')); // Image processing gulp.task('clean-img', function() { return del('public/img', { force: true }); }); gulp.task('process-img', function() { return gulp.src('src/*/public/img/**/*.{gif,jpg,jpeg,png,svg}', { base: './' }) .pipe(cache('process-img')) .pipe(imagemin()) .pipe(rename(function (path) { path.dirname = path.dirname.replace(/^src\/([^\/]+\/)public\/img/, '$1'); })) .pipe(gulp.dest('public/img')); }); // Top level commands gulp.task('default', gulp.parallel('process-js', 'process-css', 'process-img')); gulp.task('clean', gulp.parallel('clean-js', 'clean-css', 'clean-img')); gulp.task('watch', function() { gulp.watch(['src/*/public/sass/**/*.scss','src/*/public/css/**/*.css'], gulp.series('process-css')); gulp.watch('src/*/public/js/**/*.js', gulp.series('process-js')); gulp.watch('src/*/public/img/**/*.{gif,jpg,jpeg,png,svg}', gulp.series('process-img')); }); 

Запустите gulp и убедитесь, что он работает без ошибок.

Теперь вы можете запустить gulp для компиляции sass, минимизации CSS, минимизации JS и оптимизации изображений во всех ваших модулях. Вы можете следить за этим с помощью gulp watch чтобы все они автоматически обрабатывались по мере их изменения. Модуль cache gulp гарантирует, что когда-либо обрабатываются только измененные файлы, поэтому он должен обрабатывать изменения очень быстро.

Проверьте это, создав один из этих файлов:

  • src/App/public/sass/sasstest.scss
  • src/App/public/css/test.css
  • src/App/public/js/test.js
  • src/App/public/img/test.jpg

А затем запустить gulp . Найдите файлы в public/css/App , public/js/App или public/img/App .

Запустите gulp watch , измените исходные файлы, а затем проследите за обновлениями в публичных файлах.

Консольные Команды

И, наконец, что не менее важно, вам потребуется способ запуска консольных команд. Для этого мы будем использовать консоль Symfony , которая уже поставляется с Zend Expressive, поэтому нам не нужно вручную ее запрашивать.

Создайте файл с именем bin/console :

 #!/usr/bin/env php <?php chdir(dirname(__DIR__)); require 'vendor/autoload.php'; /** * Self-called anonymous function that creates its own scope and keep the global namespace clean. */ call_user_func(function () { /** @var \Interop\Container\ContainerInterface $container */ $container = require 'config/container.php'; $app = new \Symfony\Component\Console\Application('Application console'); $commands = $container->get('config')['console']['commands']; foreach ($commands as $command) { $app->add($container->get($command)); } $app->run(); }); 

Затем вы можете создавать команды Symfony и регистрировать их через config/autoload/console.global.php или из ваших модулей, например так:

 <?php return [ 'console' => [ 'commands' => [ \App\Command\HelloWorldCommand::class, ], ], ]; 

Добавьте любые зависимости, которые нужны вашим консольным командам, в конструктор, как и любой другой класс в Expressive. Обязательно вызовите parent::__construct() в вашем конструкторе, иначе ваша команда не будет работать.

Вот пример команды с зависимостью:

 <?php namespace App\Command; use Doctrine\ORM\EntityManager; use Monolog\Logger; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class HellowWorld extends Command { /** * @var EntityManager */ private $entityManager; public function __construct(EntityManager $entityManager, $name = null) { $this->entityManager = $entityManager; parent::__construct($name); } /** * Configures the command */ protected function configure() { $this->setName('hello') ->setDescription('Says hello') ; } /** * Executes the current command */ protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln("Hello World!"); // Do something with the entityManager $this->entityManager->find('Blog\Entity\BlogEntity'); } } 

Для запуска вашей команды:

 php bin/console hello 

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

Запустите эту команду:

 composer require monolog/monolog symfony/monolog-bridge; 

Затем добавьте это в ваш метод execute в вашей команде:

 protected function execute(InputInterface $input, OutputInterface $output) { $logger = new \Monolog\Logger('collect-product-data'); $logger->pushHandler(new \Symfony\Bridge\Monolog\Handler\ConsoleHandler($output)); $logger->debug('Log something); } 

Вывод

Теперь вы готовы приступить к созданию приложения уровня предприятия со всеми инструментами, которые вам когда-либо понадобятся.

В следующем посте мы начнем изучать, как создавать модули на этой основе, начиная с модуля блога.