Все зависит от модели. Если логика сильно меняется от класса к классу, то возможно не стоит создавать базовый контроллер. Обычно хватает 2-3 методов в качестве точек расширения.
Согласен, надо подумать на этот счет, возможно, создать интерфейс для загрузчика, и в бандле получать все загрузчики, так же, как сейчас endpoint-ы.
Основная причина, почему routes() нестатический — чтобы при наследовании можно было использовать абстрактные методы.
Если в контроллере нет каких-то других параметров, типа имени класса, которые влияют на генерацию роутов, то лучше вынести в отдельный класс. Но могут быть и другие параметры, иногда довольно сложные, от которых зависит логика контроллера и которые не хочется раскрывать.
Добавил пример с наследованием.
Теоретически можно сделать ее статической, но при генерации роутов могут понадобиться сторонние сервисы, например, для получения конфигов.
<?php
use Doctrine\ORM\EntityManagerInterface;
use PaLabs\EndpointBundle\EndpointInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Route;
abstract class BaseEntityListController implements EndpointInterface {
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
protected abstract function className(): string;
public function routes()
{
return new Route(sprintf("/rest/%s/list", $this->sectionName()));
}
public function execute(Request $request): Response
{
// do work
$this->em->getRepository($this->className())->findAll();
}
protected function sectionName(): string {
$reflection = new \ReflectionClass($this->className());
return $reflection->getShortName();
}
}
class SomeEntityController extends BaseEntityListController {
protected function className(): string
{
return MyEntity::class;
}
}
На этапе генерации кэша. Но теоретически можно брать конфигурацию из БД и на ее основе генерировать роуты (на целевом сервере). Правда, это все будет статически.
Основное преимущество — программная генерация роутов. Это важно, в частности, для наследования контроллеров. Но также полезно, если есть сложная логика генерации. В сторонних библиотеках этот бандо лучше не использовать.
Такой подход есть во многих языках, try-with-resources. Он хорошо подходит в случае одного ресурса. Но в случае одновременной работы с несколькими ресурсами получается довольно громоздкие вложенные конструкции. Например, вам надо открыть какое-то (заранее неизвестное) число ssh-прокси соединений, и в конце обязательно закрыть их. Как в этом случае использовать with?
Да, всегда найдется способ использовать неправильно. Но использование замыкания в deferred() гарантирует, что обработчики в любом случае выполнятся после завершения замыкания.
Передать контекст во внутреннюю функцию иногда может быть полезно. Например, если в рамках какого-то процесса нужно создать несколько временных файлов, которые будут использоваться в разных местах процесса, а удалить все файлы нужно в конце процесса. В этом смысле Defer мощнее, чем в go.
Ваш вариант интересный. Но есть несколько моментов:
1) Я бы не стал полагаться на, что деструктор будет вызван когда нужно. Например, я могу передать $defer куда-нибудь еще, или сохранить в какой-то глобальной переменной, и тогда логика работы функций-обработчиков может нарушиться. Хотя для локального использования так даже проще
2) Обработчики нужно выполнять в порядке, обратном их добавлению
3) Нужно ловить исключения при вызове обработчика
В качестве замены трейту можно использовать что-то такое:
function deferred(callable $callback) {
$context = new DeferredContext();
try {
$callback($context);
} finally {
$context->executeDeferredActions();
}
}
И использовать:
deferred(function(DeferredContext $context){
// Some actions
});
В Gitlab CI обещают улучшить работу с артефактами — можно будет загружать артефакты от предыдущих задач в конвейере, наверное, будет апи, с помощью которого можно будет делать многое из того, что вы описываете. Ручного запуска билдов, насколько я знаю, нет. А build matrix есть, но она задается вручную перечислением задач.
С другой стороны, в Jenkins довольно тяжело работать с docker. А без него сложно обеспечить изоляцию билдов — например, на одном воркере тестировать проект с разными версиями python и mysql одновременно. И с автоматическим созданием воркеров в облаках непросто.
то нужно явно указывать тип в readable_enum. Самое плохое то, что если раньше EventQueueStatusEnum не было, а потом его добавили, то все, что использовало EventStatusEnumType в виде tem.type|readable_enum|trans, сломается
А если есть 2 Enum-а с одинаковыми значениями, но разным переводом? Тогда в любом случае нужно указать его тип. А если где-то уже есть {{ item.type|readable_enum|trans }}, то и не забыть указать тип там.
Это хороший бандл, но у него есть недостатки, важные для нас. В частности, значения перечисления там — строки. И при использовании в шаблонах нужно указывать тип перечисления, например readable_enum('BasketballPositionType'). Что создает проблемы при рефакторинге — нужно не забыть изменить шаблоны. А если значения перечисления — объекты, то можно не думать об этом и избежать ошибок, особенно в перечислениях с похожими значениями.
Основная причина, почему routes() нестатический — чтобы при наследовании можно было использовать абстрактные методы.
Теоретически можно сделать ее статической, но при генерации роутов могут понадобиться сторонние сервисы, например, для получения конфигов.
Основное преимущество — программная генерация роутов. Это важно, в частности, для наследования контроллеров. Но также полезно, если есть сложная логика генерации. В сторонних библиотеках этот бандо лучше не использовать.
Передать контекст во внутреннюю функцию иногда может быть полезно. Например, если в рамках какого-то процесса нужно создать несколько временных файлов, которые будут использоваться в разных местах процесса, а удалить все файлы нужно в конце процесса. В этом смысле Defer мощнее, чем в go.
1) Я бы не стал полагаться на, что деструктор будет вызван когда нужно. Например, я могу передать $defer куда-нибудь еще, или сохранить в какой-то глобальной переменной, и тогда логика работы функций-обработчиков может нарушиться. Хотя для локального использования так даже проще
2) Обработчики нужно выполнять в порядке, обратном их добавлению
3) Нужно ловить исключения при вызове обработчика
В качестве замены трейту можно использовать что-то такое:
И использовать:
С другой стороны, в Jenkins довольно тяжело работать с docker. А без него сложно обеспечить изоляцию билдов — например, на одном воркере тестировать проект с разными версиями python и mysql одновременно. И с автоматическим созданием воркеров в облаках непросто.
то нужно явно указывать тип в readable_enum. Самое плохое то, что если раньше EventQueueStatusEnum не было, а потом его добавили, то все, что использовало EventStatusEnumType в виде tem.type|readable_enum|trans, сломается