Symfony2 your way
by Rafał Wrzeszcz, 2014
rafal.wrzeszcz@wrzasq.pl
http://wrzasq.pl/
http://chilldev.pl/
https://github.com/rafalwrzeszcz
https://linkedin.com/in/rafalwrzeszcz
Symfony2 – ways to customize
● Dependency Injection Container
● Code
● Bundles
● External tools
Dependency Injection
(yet without “Container”)
class DataSource {
protected $adapter;
protected $prefix;
public function __construct(
DataAdapter $adapter,
$prefix
) {
$this->adapter = $adapter;
$this->prefix = $prefix;
}
}
class DataAdapter {
protected $sources;
public function __construct(
array $sources
) {
$this->sources = $sources;
}
}
$conn = new DataAdapter(
['srv1', 'srv2']
);$db = new DataSource(
$conn,
'prod.'
);
Putting dependencies into Container
class MyExtension extends Extension
{
public function load(
array $configs,
ContainerBuilder $container
) {
$loader = new XmlFileLoader(
$container,
new FileLocator(__DIR__ .
'/../Resources/config')
);
$loader->load('services.xml');
}
}
<parameter key="my.prefix">prod.</parameter>
<parameter key="my.sources" type="collection">
<parameter>srv1</parameter>
<parameter>srv2</parameter>
</parameter>
<service id="my.dataadapter" class="DataAdapter">
<argument>%my.sources%</argument>
</service>
<service id="my.datasource" class="DataSource">
<argument type="service"
id="my.dataadapter"/>
<argument>%my.prefix%</argument>
</service>
$db = $di->get(
'my.datasource'
);
Semantic configuration
my:
prefix: "prod."
sources:
- "srv1"
- "srv2"
class Configuration
implements ConfigurationInterface
{
public function getConfigTreeBuilder() {
$treeBuilder = new TreeBuilder();
$treeBuilder->root('my')
->children()
->scalarNode('prefix')->end()
->arrayNode('sources')
->prototype('scalar')->end()
->end()
->end();
return $treeBuilder;
}
}
//MyExtension::load()
$config = $this->processConfiguration(
new Configuration(),
$configs);
$container->setParameter(
'my.prefix', $config['prefix']);
$container->setParameter(
'my.sources', $config['sources']);
Alles zusammen
my:
prefix: "prod."
sources:
- "srv1"
- "srv2"
$db = $di->get(
'my.datasource'
);
Services
definitions
Configuration
schema
DI
extension
Overriding services and parameters
services:
form.resolved_type_factory:
class: "MyBundleApplicationBundleFormProfiledTypeFactory"
arguments:
- "@profiler"
parameters:
web_profiler.debug_toolbar.position: "top"
Less invasive way - %*.class%
parameters:
form.resolved_type_factory.class: "MyBundleApplicationBundleFormProfiledTypeFactory"
Adding .class parameter
<service id="my.dataadapter" class="DataAdapter">
<argument>%my.sources%</argument>
</service>
<service id="my.datasource" class="DataSource">
<argument type="service"
id="my.dataadapter"/>
<argument>%my.prefix%</argument>
</service>
<parameter key="my.dataadapter.class">DataAdapter</parameter>
<parameter key="my.datasource.class">DataSource</parameter>
<service id="my.dataadapter" class="%my.dataadapter.class%">
<argument>%my.sources%</argument>
</service>
<service id="my.datasource" class="%my.datasource.class%">
<argument type="service" id="my.dataadapter"/>
<argument>%my.prefix%</argument>
</service>
DIC compiler
Loading all extensions
Merging them
DIC compiler
Dumping compiled DIC
into cache
Creating own compiler phase
class MyPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->hasDefinition('form.resolved_type_factory')) {
$definition = $container->getDefinition('form.resolved_type_factory');
$definition->setClass('MyBundleApplicationBundleFormProfiledTypeFactory');
$definition->addMethodCall(
'setProfiler',
[new Reference('profiler')]
);
}
}
}
Registering compiler pass
class MyBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(
new MyCompilerPass(),
PassConfig::TYPE_OPTIMIZE
);
}
}
Adding tags to services
<parameter key="my.dataadapter.class">DataAdapter</parameter>
<parameter key="my.datasource.class">DataSource</parameter>
<service id="my.dataadapter" class="%my.dataadapter.class%">
<argument>%my.sources%</argument>
<tag name="my.datasource" alias="data" prefix="%my.prefix_data%"/>
<tag name="my.datasource" alias="meta" prefix="%my.prefix_meta%"/>
</service>
Own tags handler
class MyPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$class = $container->getParameter('my.datasource.class');
foreach ($container->findTaggedServiceIds('my.datasource') as $id => $tags) {
foreach ($tags as $tag) {
$definition = $container->register($tag['alias'], $class);
$definition->setArguments([
new Reference($id),
$tag['prefix']
]);
}
}
}
}
Most important pre-defined tags
● twig.extension/templating.helper – registering templating helpers,
● security.voter – custom security access logic,
● monolog.logger – marking specific channels for the logger,
● kernel.event_listener/kernel.event_subscriber – subscribing to
events,
● data_collector – web profiler data collector.
More on http://symfony.com/doc/current/reference/dic_tags.html.
Symfony2 – ways to customize
● Dependency Injection Container
● Code
● Bundles
● External tools
Events
● move your non-core features to event handlers,
● keep core logic thin and simple,
● provide before and after events.
Custom event object
class MyLibFormEvent extends Event
{
const FORM_SUBMITTED = 'my_lib.form.event.submitted';
const FORM_HANDLED = 'my_lib.form.event.handled';
// properties
public function __construct(/* custom event params */)
{
// assign properties
}
// getters and setters
}
Dispatching the event
$eventDispatcher = $di->get('event_dispatcher');
$eventDispatcher->dispatch(
MyLibFormEvent::FORM_SUBMITTED,
new MyLibFormEvent(/* custom data */)
);
// process form
$eventDispatcher->dispatch(
MyLibFormEvent::FORM_HANDLED,
new MyLibFormEvent(/* custom data */)
);
Subscribing to events
$di->get('event_dispatcher')->addListener(
MyLibFormEvent::FORM_SUBMITTED,
function (MyLibFormEvent $event)
{
// handle the event
}
);
Symfony kernel events
● kernel.requestnew request is being dispatched,
● kernel.responseresponse is generated by request handler (like controller),
● kernel.finish_requestfired after request is handled – regardless of operation
status,
● kernel.terminateresponse is already sent – we can perform some heavy tasks that
won’t afect page load,
● kernel.exceptionunhandled exception occured during request handling.
DI tag event_listener
<service id="my.lib.response_listener" class="%my.lib.response_listener.class%">
<tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse"/>
</service>
JMSAopBundle
"jms/aop-bundle": "1.0.1"
Writing own aspects
<service id="my.lib.security.pointcut" class="%my.lib.security.pointcut.class%">
<tag name="jms_aop.pointcut" interceptor="my.lib.security.interceptor"/>
</service>
<service id="my.lib.security.interceptor" class="%my.lib.security.interceptor.class%"/>
Pointcut
class MySecurityPointcut implements PointcutInterface
{
public function matchesClass(ReflectionClass $class)
{
return $class->implementsInterface('MyDataAdapterInterface');
}
public function matchesMethod(ReflectionMethod $method)
{
return $method->getName() == 'read';
}
}
Interceptor
class MySecurityInterceptor implements MethodInterceptorInterface
{
public function intercept(MethodInvocation $invocation)
{
if (/* security checks */) {
// you can modify $invocation->arguments array
$result = $invocation->proceed();
// you can post-process results
return $result;
} else {
throw new SecurityException('Unauthorized access attempt.');
}
}
}
Symfony2 – ways to customize
● Dependency Injection Container
● Code
● Bundles
● External tools
Bundles inheritance
class MySecurityBundle extends Bundle
{
public function getParent()
{
return 'SonataUserBundle';
}
}
Overriding controllers
class AdminSecurityController
extends SonataUserBundleControllerAdminSecurityController
{
public function loginAction()
{
// own login handling logic
}
}
Overriding routing
_admin_security:
# cascade checks:
# MySecurityBundle/Resources/config/routing/admin_security.xml
# SonataUserBundle/Resources/config/routing/admin_security.xml
resource: "@SonataUserBundle/Resources/config/routing/admin_security.xml"
prefix: "/admin"
Overriding templates
public function loginAction()
{
// action logic
return $this->templating->renderResponse(
'SonataUserBundle:Admin:Security/login.html.php',
$params
);
}
Overriding versus extending
{# MySecurityBundle/Resources/views/Admin/Security/login.html.twig #}
{% extends '::layout.html.twig' %}
{% block sidebar %}
<h3>Table of Contents</h3>
{# ... #}
{{ parent() }}
{% endblock %}
Application templates
● app/Resources/MySecurityBundle/views/Admin/Security/login.html.php
application resource,
● src/MySecurityBundle/Resources/views/Admin/Security/login.html.php
inheriting bundle,
● src/SonataUserBundle/Resources/views/Admin/Security/login.html.php
source template.
Symfony2 – ways to customize
● Dependency Injection Container
● Code
● Bundles
● External tools
Composer
https://getcomposer.org/
wget -O - https://getcomposer.org/installer
| php -- --install-dir=/usr/local/bin
Scripts
incenteev/composer-parameter-handler:
ScriptHandler::buildParameters
sensio/distribution-bundle:
ScriptHandler::buildBootstrap
ScriptHandler::clearCache
ScriptHandler::installAssets
ScriptHandler::installRequirementsFile
ScriptHandler::removeSymfonyStandardFiles
Autoloading overriding
{
"autoload": {
"classmap": ["src/Symfony/"]
}
}
React
(react/http)
$app = function ($request, $response) {
$response->writeHead(200, ['Content-Type' => 'text/plain']);
$response->end("Hello Worldn");
};
$loop = ReactEventLoopFactory::create();
$socket = new ReactSocketServer($loop);
$http = new ReactHttpServer($socket, $loop);
$http->on('request', $app);
$socket->listen(1337);
$loop->run();
PHP ProcessManager
(marcj/php-pm)
http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html
./bin/ppm start ~/my/path/to/symfony/ --bridge=httpkernel
Varnish
templating:
esi: true
ESI example
<body>
<?php echo $this['actions']->render(
$this['actions']->controller('MySecurityBundle:Panel:sidebar'),
['strategy' => 'esi']
); ?>
<h1>My blog</h1>
<?php /* page content */ ?>
</body>
QA tools – CLI
● php -l
● phpcs (squizlabs/php_codesniffer)
● phpcpd (sebastian/phpcpd)
● phpmd (phpmd/phpmd)
● phpunit (phpunit/phpunit)
● phpdoc (phpdocumentor/phpdocumentor)
QA tools – in the cloud
● CIfor open source – Travis (https://travis-ci.org/),
● Version Eyedependencies tracking
(https://www.versioneye.com/),
● Coverallscode coverage tracking (https://coveralls.io/).
Scrutinizer
https://scrutinizer-ci.com/
SensioLabs Insights
https://insight.sensiolabs.com/
Thank you!

Symfony2 your way

  • 1.
    Symfony2 your way byRafał Wrzeszcz, 2014 [email protected] http://wrzasq.pl/ http://chilldev.pl/ https://github.com/rafalwrzeszcz https://linkedin.com/in/rafalwrzeszcz
  • 2.
    Symfony2 – waysto customize ● Dependency Injection Container ● Code ● Bundles ● External tools
  • 3.
    Dependency Injection (yet without“Container”) class DataSource { protected $adapter; protected $prefix; public function __construct( DataAdapter $adapter, $prefix ) { $this->adapter = $adapter; $this->prefix = $prefix; } } class DataAdapter { protected $sources; public function __construct( array $sources ) { $this->sources = $sources; } } $conn = new DataAdapter( ['srv1', 'srv2'] );$db = new DataSource( $conn, 'prod.' );
  • 4.
    Putting dependencies intoContainer class MyExtension extends Extension { public function load( array $configs, ContainerBuilder $container ) { $loader = new XmlFileLoader( $container, new FileLocator(__DIR__ . '/../Resources/config') ); $loader->load('services.xml'); } } <parameter key="my.prefix">prod.</parameter> <parameter key="my.sources" type="collection"> <parameter>srv1</parameter> <parameter>srv2</parameter> </parameter> <service id="my.dataadapter" class="DataAdapter"> <argument>%my.sources%</argument> </service> <service id="my.datasource" class="DataSource"> <argument type="service" id="my.dataadapter"/> <argument>%my.prefix%</argument> </service> $db = $di->get( 'my.datasource' );
  • 5.
    Semantic configuration my: prefix: "prod." sources: -"srv1" - "srv2" class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $treeBuilder->root('my') ->children() ->scalarNode('prefix')->end() ->arrayNode('sources') ->prototype('scalar')->end() ->end() ->end(); return $treeBuilder; } } //MyExtension::load() $config = $this->processConfiguration( new Configuration(), $configs); $container->setParameter( 'my.prefix', $config['prefix']); $container->setParameter( 'my.sources', $config['sources']);
  • 6.
    Alles zusammen my: prefix: "prod." sources: -"srv1" - "srv2" $db = $di->get( 'my.datasource' ); Services definitions Configuration schema DI extension
  • 7.
    Overriding services andparameters services: form.resolved_type_factory: class: "MyBundleApplicationBundleFormProfiledTypeFactory" arguments: - "@profiler" parameters: web_profiler.debug_toolbar.position: "top"
  • 8.
    Less invasive way- %*.class% parameters: form.resolved_type_factory.class: "MyBundleApplicationBundleFormProfiledTypeFactory"
  • 9.
    Adding .class parameter <serviceid="my.dataadapter" class="DataAdapter"> <argument>%my.sources%</argument> </service> <service id="my.datasource" class="DataSource"> <argument type="service" id="my.dataadapter"/> <argument>%my.prefix%</argument> </service> <parameter key="my.dataadapter.class">DataAdapter</parameter> <parameter key="my.datasource.class">DataSource</parameter> <service id="my.dataadapter" class="%my.dataadapter.class%"> <argument>%my.sources%</argument> </service> <service id="my.datasource" class="%my.datasource.class%"> <argument type="service" id="my.dataadapter"/> <argument>%my.prefix%</argument> </service>
  • 10.
    DIC compiler Loading allextensions Merging them DIC compiler Dumping compiled DIC into cache
  • 11.
    Creating own compilerphase class MyPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if ($container->hasDefinition('form.resolved_type_factory')) { $definition = $container->getDefinition('form.resolved_type_factory'); $definition->setClass('MyBundleApplicationBundleFormProfiledTypeFactory'); $definition->addMethodCall( 'setProfiler', [new Reference('profiler')] ); } } }
  • 12.
    Registering compiler pass classMyBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass( new MyCompilerPass(), PassConfig::TYPE_OPTIMIZE ); } }
  • 13.
    Adding tags toservices <parameter key="my.dataadapter.class">DataAdapter</parameter> <parameter key="my.datasource.class">DataSource</parameter> <service id="my.dataadapter" class="%my.dataadapter.class%"> <argument>%my.sources%</argument> <tag name="my.datasource" alias="data" prefix="%my.prefix_data%"/> <tag name="my.datasource" alias="meta" prefix="%my.prefix_meta%"/> </service>
  • 14.
    Own tags handler classMyPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $class = $container->getParameter('my.datasource.class'); foreach ($container->findTaggedServiceIds('my.datasource') as $id => $tags) { foreach ($tags as $tag) { $definition = $container->register($tag['alias'], $class); $definition->setArguments([ new Reference($id), $tag['prefix'] ]); } } } }
  • 15.
    Most important pre-definedtags ● twig.extension/templating.helper – registering templating helpers, ● security.voter – custom security access logic, ● monolog.logger – marking specific channels for the logger, ● kernel.event_listener/kernel.event_subscriber – subscribing to events, ● data_collector – web profiler data collector. More on http://symfony.com/doc/current/reference/dic_tags.html.
  • 16.
    Symfony2 – waysto customize ● Dependency Injection Container ● Code ● Bundles ● External tools
  • 17.
    Events ● move yournon-core features to event handlers, ● keep core logic thin and simple, ● provide before and after events.
  • 18.
    Custom event object classMyLibFormEvent extends Event { const FORM_SUBMITTED = 'my_lib.form.event.submitted'; const FORM_HANDLED = 'my_lib.form.event.handled'; // properties public function __construct(/* custom event params */) { // assign properties } // getters and setters }
  • 19.
    Dispatching the event $eventDispatcher= $di->get('event_dispatcher'); $eventDispatcher->dispatch( MyLibFormEvent::FORM_SUBMITTED, new MyLibFormEvent(/* custom data */) ); // process form $eventDispatcher->dispatch( MyLibFormEvent::FORM_HANDLED, new MyLibFormEvent(/* custom data */) );
  • 20.
  • 21.
    Symfony kernel events ●kernel.requestnew request is being dispatched, ● kernel.responseresponse is generated by request handler (like controller), ● kernel.finish_requestfired after request is handled – regardless of operation status, ● kernel.terminateresponse is already sent – we can perform some heavy tasks that won’t afect page load, ● kernel.exceptionunhandled exception occured during request handling.
  • 22.
    DI tag event_listener <serviceid="my.lib.response_listener" class="%my.lib.response_listener.class%"> <tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse"/> </service>
  • 23.
  • 24.
    Writing own aspects <serviceid="my.lib.security.pointcut" class="%my.lib.security.pointcut.class%"> <tag name="jms_aop.pointcut" interceptor="my.lib.security.interceptor"/> </service> <service id="my.lib.security.interceptor" class="%my.lib.security.interceptor.class%"/>
  • 25.
    Pointcut class MySecurityPointcut implementsPointcutInterface { public function matchesClass(ReflectionClass $class) { return $class->implementsInterface('MyDataAdapterInterface'); } public function matchesMethod(ReflectionMethod $method) { return $method->getName() == 'read'; } }
  • 26.
    Interceptor class MySecurityInterceptor implementsMethodInterceptorInterface { public function intercept(MethodInvocation $invocation) { if (/* security checks */) { // you can modify $invocation->arguments array $result = $invocation->proceed(); // you can post-process results return $result; } else { throw new SecurityException('Unauthorized access attempt.'); } } }
  • 27.
    Symfony2 – waysto customize ● Dependency Injection Container ● Code ● Bundles ● External tools
  • 28.
    Bundles inheritance class MySecurityBundleextends Bundle { public function getParent() { return 'SonataUserBundle'; } }
  • 29.
    Overriding controllers class AdminSecurityController extendsSonataUserBundleControllerAdminSecurityController { public function loginAction() { // own login handling logic } }
  • 30.
    Overriding routing _admin_security: # cascadechecks: # MySecurityBundle/Resources/config/routing/admin_security.xml # SonataUserBundle/Resources/config/routing/admin_security.xml resource: "@SonataUserBundle/Resources/config/routing/admin_security.xml" prefix: "/admin"
  • 31.
    Overriding templates public functionloginAction() { // action logic return $this->templating->renderResponse( 'SonataUserBundle:Admin:Security/login.html.php', $params ); }
  • 32.
    Overriding versus extending {#MySecurityBundle/Resources/views/Admin/Security/login.html.twig #} {% extends '::layout.html.twig' %} {% block sidebar %} <h3>Table of Contents</h3> {# ... #} {{ parent() }} {% endblock %}
  • 33.
    Application templates ● app/Resources/MySecurityBundle/views/Admin/Security/login.html.php applicationresource, ● src/MySecurityBundle/Resources/views/Admin/Security/login.html.php inheriting bundle, ● src/SonataUserBundle/Resources/views/Admin/Security/login.html.php source template.
  • 34.
    Symfony2 – waysto customize ● Dependency Injection Container ● Code ● Bundles ● External tools
  • 35.
    Composer https://getcomposer.org/ wget -O -https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin
  • 36.
  • 37.
  • 38.
    React (react/http) $app = function($request, $response) { $response->writeHead(200, ['Content-Type' => 'text/plain']); $response->end("Hello Worldn"); }; $loop = ReactEventLoopFactory::create(); $socket = new ReactSocketServer($loop); $http = new ReactHttpServer($socket, $loop); $http->on('request', $app); $socket->listen(1337); $loop->run();
  • 39.
  • 40.
  • 41.
    ESI example <body> <?php echo$this['actions']->render( $this['actions']->controller('MySecurityBundle:Panel:sidebar'), ['strategy' => 'esi'] ); ?> <h1>My blog</h1> <?php /* page content */ ?> </body>
  • 42.
    QA tools –CLI ● php -l ● phpcs (squizlabs/php_codesniffer) ● phpcpd (sebastian/phpcpd) ● phpmd (phpmd/phpmd) ● phpunit (phpunit/phpunit) ● phpdoc (phpdocumentor/phpdocumentor)
  • 43.
    QA tools –in the cloud ● CIfor open source – Travis (https://travis-ci.org/), ● Version Eyedependencies tracking (https://www.versioneye.com/), ● Coverallscode coverage tracking (https://coveralls.io/).
  • 44.
  • 45.
  • 46.

Editor's Notes

  • #9 Ale przedefiniowywanie usług trąci nieco hackowaniem - możemy w ten sposób naruszyć pewien kontrakt. Może się okazać, że z czasem usługa, którą nadpisujemyu zacznie przyjmować dodatkowe parametry. Istnieje bardziej subtelny sposób, który sprowadza się mniej więcej do tego samego - podmienienia klasy, z której będzie instancjowana usługa - lecz przy pomocy samych parametrów, tak, aby nie ryzykować ingerencji w jakiekolwiek interakcje z usługą (takie jak wywołania metod). Ten sposob to parametr z sufiksem ".class". Jest to konwencja, dzięki której właśnie mamy możliwość zawsze podmiany implementacji usługi na naszą (oczywiście musi ona być kompatybilna).
  • #10 Jak to działa? Po prostu definiując nasze usługi tworzymy jeden dodatkowy parametr dla każdej z nich z sufiksem ".class" (także dobrze jest o tym pamiętać rónież nazywając parametry - tak, aby unikać nazw parametrów z tym sufiksem). Parametr ten przechowuje nazwę klasy wykorzystywanej przez usługę i umożliwia jej łatwą zmianę (wystarczy nadpisać jego wartość, nie trzeba nadpisywać całej usługi). W taki sposób (z użyciem parametru klasy) zdefiniowane są wszystkie usługi samego Symfony2, a także większości popularnych (i tych mniej popularnych też) bundli. Generalnie praktycznie nic nas nie kosztuje dodanie takiego parametru do naszych usług, a potrafi to nieraz uratować tyłek, szczególnie, jeśli tworzymy jakiś bundle ogólnego przeznaczenia, z którego mieliby korzystać inni.
  • #31 Kolejną rzeczą, którą możemy modyfikować względem bazowego bundle’a jest routing. Przy czym aby nasze przesłanianie było możliwe trzeba skorzystać ze specjalnej notacji, która oznacza odniesienie do zasobów względem danego bundle’a. W ten sposób możemy przesłonić dany statyczny plik tworząc plik o takiej samej nazwie i ścieżce.
  • #32 Bardzo podobnie ma się sprawa z przesłanianiem szablonów. Właściwie dosłownie tak samo – aby przesłonić szablon widoku tworzymy plik o takiej samej nazwie i takiej samej ścieżce w naszym bundlu.
  • #33 Ale, w przypadku szablonów należy pamiętać o jednej istotnej rzeczy, a mianowicie różnice pomiędzy przesłanianiem szablonów, a ich rozszerzaniem. Jest to o tyle problematyczne, że angielskojęzyczne nazwy konstrukcji odpowiedzialnych za te poszczególne cechy systemu szablonów są zbieżne z nomenklaturą w programowaniu obiektowym, ale ich działanie jest odmienne, żeby nawet nie powiedzieć odwrotne. Przesłaniając szablon całkowicie przesłaniamy go w systemie, nie ma żadnej możliwości dostępu do niego, nasz szablon musi go w całości zastąpić Korzystając z mechanizmu rozszerzania definiujemy logiczny ciąg zagnieżdżania szablonów – w tym przypadku nasz widok zostanie osadzony w innym, konkretnie w głównym layoucie aplikacji. Użycie `parent()` w kodzie odnosi się właśnie do tego zagnieżdżenia, nie do przsłoniętego widoku z bundle’a.
  • #34 W przypadku szablonów mamy też możliwość przesłaniania szablonów w jeszcze jeden sposób – przez zasoby samej aplikacji. Zasoby aplikacji mają najwyższy priorytet, lecz warto zwrócić uwagę na inną budowę ścieżki.
  • #35 I w końcu ostatnia część, a mianowicie zewnętrzne usługi i narzędzia. Nie samym Symfony człowiek żyje. Na szczęście społeczność Symfony dobrze o tym wie, framework ten jest bardzo otwarty i “przyjazny” w kwestii integracji z zewnętrznymi usługami, API, czy narzędziami. W bardzo wielu frameworkach istnieje podejście tworzenia wszystkiego samemu, podczas gdy w Symfony podejście jest zazwyczaj odwrotne – jeśli istnieje już do czegoś rowzwiązanie to w Symfony2 często znajdziemy integrację z takim rozwiązaniem.
  • #36 Tworząc projekt w Symfony praktycznie nie obejdziemy się bez Composera (nie jestem jego wielbicielem, ale jest to obecnie praktycznie de facto standard, więc nie ma co wynajdywać koła na nowo).
  • #37 Jedną z większych, moim zdaniem, ułomności w Composerze jest sposób podpinania się z własną implementacją pod cokolwiek, no ale nie będę się rozwodził o moich wewnętrznych odczuciach… W Composerze możemy podpiąć się pod zdarzenia (ich listę można znaleźć w specyfikacji na stronie). Sensio (twórcy Symfony) udostępniają kilka gotowych skryptów, które pozwalają zautomatyzować cykl pracy z aplikacją.
  • #38 Ale Composer daje nam pewną bardzo potężną broń. Chyba najbrzydszy z jakichkolwiek dziś przeze mnie tutaj prezentowanych hacków. Jeśli myśleliście, że przedefiniowywanie definicji usług w DIC było złe, to spójrzcie na to.<klik> Otóż możemy całkiem przesłonić klasy dowolnej ładowanej przez nas biblioteki naszymi własnymi jeśli tylko nadpiszemy autoloader generowany przez Composera. Ale jest jeden warunek – musimy skorzystać z autoloadera typu classmap (niezależnie od tego, że Composer i tak generuje dla nas class-mapę, chodzi o hierarchię loaderów, class-mapa jako najszybsza i generowana docelowo, jest pierwszym miejscem, gdzie wygenerowany autoloader szuka klasy).
  • #39 Kolejnym ciekawym i niezmiernie obiecującym projektem jest React. Implementuje on model przetwarzania request-response znany z wielu innych platform, takich jak chociażby Node.js. Co to ma wspólnego z Symfony? Oczywiście możemy, jako funkcję obsługującą żądanie zaimplementować bootstrapping naszej aplikacji Symfony, ale jest jeszcz lepiej – coś takiego już istnieje.
  • #40 Kożyści są bardzo duże, przede wszystkim odchodzi konieczność każdorazowego bootstrapowania aplikacji, która nawet przy bardzo zoptymalizowanych projektach wymaga zazwyczaj dużo operacji wejścia/wyjścia (chociażby wczytywanie plików), co jest nieraz największym obciążeniem. Możemy również wykorzystać nową architekturę i wyeliminować niektóre inne zależności, na przykład niektórych rzeczy nie trzeba trzymać w cache’u, czy sesji, gdyż można je trzymać bezpośrednio w pamięci procesu. Nie ma niestety róży bez kolców. Jest to kompletna zmiana podejścia w przypadku PHP, pamiętajmy, że w normalnym trybie skrypty PHP są zawsze uruchamiane w odrębnym środowisku. W tym przypadku musimy pamiętać, że nasz skrypt działa nieprzerwanie, każde kolejne zapytanie operuje w tym samym środowisku, a więc musimy być świadomi, że na przykład ta sama instancja naszego obiektu będzie wykorzystana podobnie, więc inicjując obiekty musimy być pewni kiedy się ona wykonuje.