Эпоха фуллстековых фреймворков ушла в прошлое.
Современные разработчики фреймворков разделяют свои монолитные репозитории на компоненты с помощью веток Git, позволяя разработчику выбирать, что на самом деле нужно его проекту.
Это означает, что вы можете создать свое приложение на основе Zend Service Manager, Aura Router, Doctrine ORM, Laravel (Illuminate) Eloquent, Plates, Monolog, Symfony Cache или любых других компонентов, которые можно установить через Composer.
Надежная структура проекта
Основным шагом является создание и поддержание строгой структуры проекта для установки и объединения компонентов из инфраструктуры любого фреймворка.я посвятил это вся статья , чтобы охватить вопросы структуры каталогов, организации и группировки источников, соглашений об именах и других связанных с этим вещей.
Выбор подходящего инструмента для работы
При разработке проекта всегда необходимо обращать внимание на бизнес-логику ядра.Для всех распространенных задач, которые необходимо реализовать в вашем проекте, следует использовать различные решения, компоненты и библиотеки с открытым исходным кодом, которые облегчат процесс разработки приложений.
DBAL, ORM, маршрутизация, почтовик, кэш, логгер — это далеко не полный список примеров того, что не нужно создавать заново.
Напомню, что вы можете использовать компоненты независимо от фреймворка (Zend Framework, Symfony, Laravel, Aura и т. д.).
Соответственно, зависимости в созданном композиторе.
json могут выглядеть так:
{ "require": { "php": "^7.0", "container-interop/container-interop": "^1.0", "zendframework/zend-servicemanager": "^3.0.3", "symfony/console": "^3.1", "symfony/event-dispatcher": "^2.8", "doctrine/dbal": "^2.5", "zendframework/zend-filter": "^2.7", "aura/intl": "^3.0", "psr/log": "^1.0", "monolog/monolog": "^1.21", "illuminate/support": "^5.3", "league/plates": "^3.1", "slim/slim": "^3.7", "mongodb/mongodb": "^1.0", "filp/whoops": "^2.1", "ramsey/uuid": "^3.5", "robmorgan/phinx": "^0.6.5", "psr/simple-cache": "^1.0", "symfony/cache": "3.3.*@dev" } }
Компоненты фреймворка
Использование разных компонентов фреймворка дает нам большое преимущество, но при необдуманном использовании может привести к безвыходным ситуациям.Основная, но непростая задача — отделить вашу бизнес-логику от фреймворка или библиотеки для автономного использования.
Если вы не уделяете этой задаче должного внимания, у вас могут возникнуть проблемы при попытке перехода на компонент от другого разработчика или даже при обновлении версии текущего компонента.
Невозможно на 100% отделить код от фреймворка, если вы его вообще не используете, но вы можете значительно уменьшить связанность.
Создавать уровень интерфейса абстракции и разделите свой код на внешние зависимости или используйте PSR-интерфейсы с целью снижения трудозатрат при переходе на альтернативные реализации компонентов.
Короче говоря, создание интерфейсов — это лучшая практика, которую вы должны освоить и уметь применять на практике.
В идеале вот список того, от чего у вас могут быть прямые зависимости:
- Реализации сервисов для использования абстрактных внешних зависимостей
- Заводы
- Промежуточное ПО, контроллеры, заголовки, CLI, причем предполагается, что все они не должны содержать бизнес-логику.
Управление конфигурацией
Вместо жестко прописанных параметров подключения к базе данных следует использовать отдельные файлы, в которых можно переопределять различные настройки.Это будет полезно при использовании разных сред (например, для разработки, для производственной версии и т.д.) Существует несколько подходов к настройке файлов.
Наиболее распространенным является наличие одного файла конфигурации для каждой среды, который загружается соответствующим образом в зависимости от установленной переменной среды: config/
config_development.php
config_production.php
config_testing.php
Основным недостатком такого подхода является дублирование параметров в нескольких файлах конфигурации.
Я предпочитаю другой способ работы с конфигурацией окружения, который практикуется в Zend Framework (о нем хорошо написано в документация ).
При использовании этого метода структура файлов конфигурации выглядит следующим образом: config/
database.global.php
development.php
global.php
logger.global.php
production.php
services.global.php
В этом примере параметры могут быть четко распределены по разным файлам конфигурации в зависимости от их назначения, а также переопределяться в зависимости от среды.
Такие файлы конфигурации содержат только переопределенные параметры.
Эти файлы объединяются в единую конфигурацию с помощью глобус скобка .
Внедрение зависимости
Практическое использование внедрения зависимостей очень важно для гибкости и надежности вашего кода.Контейнер DI — это ключевая концепция, которая управляет логикой при построении блоков вашего приложения.
Вот что должно быть определено в контейнере DI:
- Службы общего назначения (адаптер базы данных, кэш, почтовая программа, регистратор и т. д.)
- Доменные сервисы, репозитории
- промежуточное ПО, контроллеры, заголовки (да, у них есть зависимости для внедрения!)
- Сервисы запуска веб-приложений и CLI-приложений
Служба — это общее имя для любого объекта PHP, который служит определенной цели (например, отправка почты) и используется в приложении только тогда, когда нам действительно нужна определенная функциональность.
Если сервис имеет сложную логику построения (имеет зависимости) или является зависимостью для другого класса и не предназначен для создания нескольких экземпляров в рамках одного запроса, то его необходимо зарегистрировать в DI-контейнере.
Другие группы классов представляют такие типы, как объекты предметной области, сущности и значения объектов.
Подумайте о User, Post, DateTime как о конкретных примерах этих классов.
Все они не являются сервисами, поэтому их не следует определять в контейнере.
Настройка DI-контейнера
Вместо программного заполнения DI-контейнера имеет смысл определить все зависимости внутри конфигурации: return [
'di' => [
'factories' => [
Psr\SimpleCache\CacheInterface::class => App\Cache\CacheFactory::class,
App\Db\DbAdapterInterface::class => App\Db\DbAdapterFactory::class,
App\User\UserService::class => App\User\UserServiceFactory::class,
App\User\UserRepository::class => App\User\UserRepositoryFactory::class,
],
],
];
Некоторые DI-контейнеры, такие как Zend Service Manager, поддерживают этот подход «из коробки», иначе вам придется писать простую логику для его заполнения на основе массива конфигурации.
Возможно, вы заметили, что я предпочитаю использовать полное имя интерфейса в качестве имени сервиса, в котором этот интерфейс реализован.
В местах, где нет интерфейса, я использую полное имя класса.
Причина проста: извлечение сервисов из контейнеров не только делает код более читабельным, но и облегчает пользователю понимание того, с чем он работает.
Начальная загрузка
Код, который загружает конфигурацию и инициализирует контейнер внедрения зависимостей, обычно содержится в так называемом сценарии начальной загрузки.
В зависимости от конфигурации и реализации DI-контейнера он может принимать следующие формы: $config = [];
$files = glob(sprintf('config/{{,*.
}global,{,*.
}%s}.
php', getenv('APP_ENV') ?: 'local'), GLOB_BRACE);
foreach ($files as $file) {
$config = array_merge($config, include $file);
}
$config = new ArrayObject($config, ArrayObject::ARRAY_AS_PROPS);
$diContainer = new Zend\ServiceManager\ServiceManager($config['services']);
$diContainer->set('Config', $config);
return $diContainer;
Контейнер DI — это конечный результат операции начальной загрузки, посредством которого реализуются все дальнейшие действия.
Хотя это очень простой пример, логика загрузки и объединения конфигурации может быть довольно сложной.
В случае модульных систем конфигурация собирается из разных источников, поэтому при начальной загрузке будет использоваться более продвинутый механизм настройки.
Фундаментация
Логика начальной загрузки может быть довольно громоздкой и дублироваться между проектами, поэтому я создал библиотеку Phoundation, которая дает мне более компактный загрузочный файл: $bootstrap = new Phoundation\Bootstrap\Bootstrap(
new Phoundation\Config\Loader\FileConfigLoader(glob(
sprintf('config/{{,*.
\}global,{,*.
}%s}.
php', getenv('APP_ENV') ?: 'local'),
GLOB_BRACE
)),
new Phoundation\Di\Container\Factory\ZendServiceManagerFactory()
);
$diContainer = $bootstrap();
return $diContainer;
Полный пример
Чтобы получить общую картину, возьмем в качестве примера это простое приложение для ведения блога, которое можно использовать либо через браузер (public/index.php), либо через командную строку (bin/app).
Он использует микроплатформу Slim для веб-части приложения и консоль Symfony для CLI. Структура проекта bin/
app
config/
database.global.php
development.php
global.php
production.php
services.global.php
public/
index.php
src/
Framework/ # general-purpose code, interfaces, adapters for framework components
Cache/
CacheFactory.php
Logger/
Handler/
IndexesCapableMongoDBHandler.php
Queue/
PheanstalkQueueClient.php
QueueClientInterface.php
QueueClientFactory.php
Web/
ActionFactory.php
ConsoleAppFactory.php
WebAppFactory.php
Post/ # domain code
Web/
SubmitPostAction.php
ViewPostAction.php
Post.php
PostRepository.php
PostRepositoryFactory.php
PostService.php
PostServiceFactory.php
User/ # domain code
CLI/
CreateUserCommand.php
Web/
ViewUserAction.php
User.php
UserRepository.php
UserRepositoryFactory.php
UserService.php
UserServiceFactory.php
bootstrap.php
конфигурация/services.global.php return [
'di' => [
'factories' => [
//Domain services
Blog\User\UserService::class => Blog\User\UserServiceFactory::class,
Blog\User\UserRepository::class => Blog\User\UserRepositoryFactory::class,
Blog\Post\PostService::class => Blog\Post\PostServiceFactory::class,
Blog\Post\PostRepository::class => Blog\Post\PostRepositoryFactory::class,
Blog\User\Web\ViewUserAction::class => Blog\Framework\Web\ActionFactory::class,
Blog\Post\Web\SubmitPostAction::class => Blog\Framework\Web\ActionFactory::class,
Blog\Post\Web\ViewPostAction::class => Blog\Framework\Web\ActionFactory::class,
//App-wide (system) services
Blog\Framework\Queue\QueueClientInterface::class => Blog\Framework\Queue\QueueClientFactory::class,
Psr\SimpleCache\CacheInterface::class => Blog\Framework\Cache\CacheFactory::class,
//App runners
'App\Web' => Blog\Framework\WebAppFactory::class,
'App\Console' => Blog\Framework\ConsoleAppFactory::class,
],
],
];
мусорное ведро/приложение #!/usr/bin/env php
<Эphp
/* @var \Interop\Container\ContainerInterface $container */
$container = require __DIR__ .
'/.
/src/bootstrap.php';
/* @var $app \Symfony\Component\Console\Application */
$app = $container->get('App\Console');
$app->run();
общественный/index.php use Slim\Http\Request;
use Slim\Http\Response;
/* @var \Interop\Container\ContainerInterface $container */
$container = require __DIR__ .
'/.
/src/bootstrap.php';
/* @var $app \Slim\App */
$app = $container->get('App\Web');
$app->get('/', function (Request $request, Response $response) {
return $this->get('view')->render($response, 'app::home');
})->setName('home');
$app->get('/users/{id}', Blog\User\Web\ViewUserAction::class);
$app->get('/posts/{id}', Blog\Post\Web\ViewPostAction::class);
$app->post('/posts', Blog\Post\Web\SubmitPostAction::class);
$app->run();
Подведение итогов
Описанная концепция представляет собой скелет, оболочку ядра кодовой базы, состоящую из предметной логики, поддерживаемой различными компонентами общего назначения.Эта оболочка является основой для создания приложений с использованием библиотек и инструментов по вашему выбору.
При запуске нового проекта вопрос должен быть не «какой фреймворк мне использоватьЭ», а «какие компоненты я буду использовать в проектеЭ» Теги: #php #архитектура приложения #composer #структура проекта #структура кода #структура приложения #архитектура веб-приложения #Компоненты #загрузка #модульное программирование #модульная архитектура #структура файлов #разработка веб-сайта #php #программирование #дизайн и рефакторинг #ООП
-
Холодович Александр Алексеевич
19 Oct, 24 -
Одно Интервью Из Жизни Сменного Аналитика
19 Oct, 24 -
Редактор Фрагментов Кода Для Visual Studio
19 Oct, 24 -
Диктофон С Метками И Картой Для Ipad
19 Oct, 24 -
Отчет О Шестой Киевской Хабравстрече
19 Oct, 24