Абе... превод на encapsulation
Не съм сигурен. Реално погледнато има логика. Явно Symfony правят тази грешка.
Тук имаш предвид shell команди? За това не служи ли exec() & co?
А дори и в такъв случай, не е ли по-добре shell-обработващата част да стои отделно от kernel-а?
Имам на предвид команди към твоето приложение, които да се викат от командният ред.
Пример:
За да не се налага да правя промени по crontab, тъй като използвам един код за много сайтове, на много сървъри, съм създал една команда
bin/console crontab:exec, която се извиква от crontab всяка минута. Тази команда посредством конфигурации (услиги в контейнера с таг crontab.job), си създава отделни нишки (thread-ове). След това всяка нишка извиква друга команда (примерно data:update, cache:warmup...). Всички подкоманди използват същият kernel защото при създаването им се използва kernel-а от контейнера.
Под "симулира заявка" имаш предвид извикване на $kernel->handle($req) с различен Request? Гледам Kernel-а на Symfony и доколкото виждам има __constructor и boot, както и shutdown и terminate... като shutdown извиква terminate...?? (в документацията не открих нищо...
Именно. Разликата между shutdown и terminate е следната: Ако стартираш shutdown на всички бъндъли (в симфони се приложението е разделено на самостоятелни преизползваеми части на име бъндъл) се извиква метода shutdown и се нулира контейнерът им. Terminate пуска събитие kernel.terminate в event.dispatcher-а на symfony, към което можеш да закачиш различни слушатели (listeners) (примерно да се отвърже от БД).
Това е малко условно - повече или по-малко зависимости?
Контейнерът се ползва, за да бръкнеш и да си вземеш каквото ти трябва без да изискваш референции при извикването на методите, т.е. динамично да можеш да ползваш всичко.
Логиката е следната: Ако даден код не е специфичен за приложението и може да се използва в множество приложения (като Encrypter-a), то е логично да не зависи от контейнер, а да получава зависимостите си като параметри. По този начин, когато го дефинираш пред контейнера, можеш да му зададеш необходимите dependencies. Ако кодът ти е специфичен за приложението и не е приложим извън него (примерно контролер), то той трябва да използва контейнера. Като все пак всички жизнени за него (като контролер, а не за конкретен контролер) зависимости трябва да се подават като параметри на конструктора.
Общо взето логиката е проста: Ако нещо е жизнено важно то е параметър. Ако не е, се задава с метод. Един контролер може да се изпълни и без контейнера - може да върне просто някакъв Response. В symfony контролера може да бъде клас, който не наследява нищо. Наследяването се прави за не пишеш всеки път ContainerAwareInterface alabala....
PHP:
class TestController
{
public function someAction()
{
return new Response('alabala');
}
}
Това е напълно валиден контролер в Symfony (тествано и работи).
От примера с class Encrypter се вижда, че не е необходим контейнер (в случая).
Тогава защо при използването му е необходим контейнер? Защо трябва да вземеш $encrypter от контейнера, вместо да го получиш от аргументите?
Както вече казах - Encrypter е код, който може да се използва във всяко приложение. В него няма код, който да е специфичен за приложението. Можеш спокойно да си напишеш $encrypter = new Encrypter('alabala', $sessionStorage);
Идеята обаче този Encrypter да е в контейнера е, че 1123123 контролера, мениджъра и т.н. го ползват. Ако след 1 година реша, че този Encrypter не ми върши работа и той не е в контейнера, ще трябва да променя кода на 123123 места. Ако е в контейнера обаче, ще трябва да сменя само дефиницията в контейнера. Грешката в примера, който съм дал е, че не съм създал EncryterInterface, на който да разчитат нещата които го ползват.
Когато си в тестов режим (test environment), нямам $_SESSION. Когато прескачам от тест в тест с phpunit няма да ми се помни сесията. За тази цел кернела дефинира друг клас като @session в контейнера (MockArraySessionStorage). Реално Encryptera си няма идея, че работи с MockArraySessionStorage, а не с реалния SessionStorage, който използва $_SESSION. По този начин кодът ми стои изолиран.
Съгласен съм. Но това (?май?) няма връзка с контейнера.
Мися, че обясних логиката с промяната на Encrypter.
В симфони контейнера се нарича DependencyInjectionContainer. Това не е случайно.
Ако има услига, която иска да ползва контейнера, мога просто в дефиницията и да добава calls с информация за контейнера. Същото важи и за други нежизнено важни dependencies.
Код:
some_event_subscriber:
class: MyBundle\EventSubscriber\SomeEventSubscriber
arguments:
- "%kernel.root_dir%"
calls:
- [setContainer, ["@service_container"]]
tags:
- {name: kernel.event_subscriber}
Сега контейнера на симфони ще направи следното:
$some_event_subscriber = new MyBundle\EventSubscriber\SomeEventSubscriber('/vat/www/..../app');
$some_event_subscriber->setContainer($this);
По време на генерирането на контейнера (компилиране), че бъде прочетен и тага kernel.event_subscriber.
Услугата event_dispatcher е добавила един CompilerPass, който проверява всички услуги с този таг и си ги записва като в дефиницията си добавя addMethodCall към addListenerService (Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass - за справка).
По този начин контейнера знае как да инициализира всяка услуга, но не го прави, докато не я поискаш.
Трябва да гледаш на нещата абстрактно.
ПП. Двата ми коментара събраха материал за урок
