The Bundle system is one of the greatest and most powerful features of Symfony2. Bundles contain all the files related to a single feature of your application: controllers, entities, event listeners, form types, Twig templates, etc. But how much of that actually needs to be inside a bundle?
In this talk we’ll take a bundle, containing all those different types of classes, configuration files and templates, and strip it down to the bare necessities. And I promise that after moving many files out of the bundle, everything still works.
While looking for ways to move things out of the bundle, I will discuss some of the more advanced features of bundle design, like prepending configuration, compiler passes and Doctrine mapping drivers. We will end with a very lean bundle, surrounded by a few highly reusable, maximally decoupled libraries.
21. But a framework is just a framework
● Quickstarter for your projects
● Prevents and solves big security
issues for you
● Has a community you can rely on
30. Behavioral conventions
Controller:
● Is allowed to return an array
● Actions can type-hint to objects which will be
fetched based on route parameters (??)
31. Configuration conventions
Use lots of annotations!
/**
* @Route("/{id}")
* @Method("GET")
* @ParamConverter("post", class="SensioBlogBundle:Post")
* @Template("SensioBlogBundle:Annot:show.html.twig")
* @Cache(smaxage="15", lastmodified="post.getUpdatedAt()")
* @Security("has_role('ROLE_ADMIN')")
*/
public function showAction(Post $post)
{
}
46. What do we rely on
HttpKernel
namespace SymfonyComponentHttpKernel;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
interface HttpKernelInterface
{
/**
* Handles a Request to convert it to a Response.
*/
public function handle(Request $request, ...);
}
47. Why? My secret missions
“Let's rebuild the application, but
this time we use Zend4 instead of
Symfony2”
56. use SymfonyComponentHttpFoundationRequest;
class ArticleController
{
public function editAction(Request $request)
{
$em = $this>
get('doctrine')>
getManager();
...
if (...) {
throw $this>
createNotFoundException();
}
...
return array(
'form' => $form>
createView()
);
}
}
Zooming in a bit
57. TODO
✔ Inject dependencies
✔ Don't use helper methods
✔ Render the template manually
✔ Keep using Request
(not really a TODO)
58. use DoctrineORMEntityManager;
class ArticleController
{
function __construct(
EntityManager $em,
) {
$this>
em = $em;
}
...
}
Inject dependencies
59. Inline helper methods
use SymfonyComponentHttpKernelExceptionNotFoundHttpException
class ArticleController
{
...
public function newAction(...)
{
...
throw new NotFoundHttpException();
...
}
}
60. use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentTemplatingEngineInterface;
class ArticleController
{
function __construct(..., EngineInterface $templating) {
}
public function newAction(...)
{
...
return new Response(
$this>
templating>
render(
'@MyBundle:Article:new.html.twig',
array(
'form' => $form>
createView()
)
)
);
}
}
Render the template manually
61. Dependencies are explicit now
Also: no mention of a “bundle”
anywhere!
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentTemplatingEngineInterface;
use DoctrineORMEntityManager;
62. Dependency overflow
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentTemplatingEngineInterface;
class ArticleController
{
function __construct(
EntityManager $entityManager,
EngineInterface $templating,
TranslatorInterface $translator,
ValidatorInterface $validator,
Swift_Mailer $mailer,
RouterInterface $router
) {
...
}
...
}
69. Bundle stuff: routing.xml
<!in
MyBundle/Resources/config/routing.xml →
<?xml version="1.0" encoding="UTF8"
?>
<routes>
<route id="new_article"
path="/article/new">
<default key="_controller">
new_article_controller:__invoke
</default>
</route>
</routes>
Pull request by Kevin Bond allows you to leave out the “:__invoke” part!
70. Controller – Achievements
● Can be anywhere
● No need to follow naming conventions
(“*Controller”, “*action”)
● Dependency injection, no service location
● Reusable in any application using
HttpFoundation
77. Are you ever going to use
anything else than Doctrine ORM?
78. Well...
Think about Doctrine MongoDB ODM,
Doctrine CouchDB ODM, etc.
namespace MyBundleEntity;
use DoctrineORMMapping as ORM;
use DoctrineODMMongoDBMappingAnnotations as MongoDB;
use DoctrineODMCouchDBMappingAnnotations as CoucheDB;
class Article
{
/**
* @ORMColumn
* @MognoDBField
* @CoucheDBField
*/
private $title;
}
79. TODO
✔ Remove annotations
✔ Find another way to map the data
80. namespace MyBundleEntity;
class Article
{
private $id;
private $title;
}
Nice and clean
A true POPO, the ideal of the data
mapper pattern
81. Use XML for mapping metadata
<doctrinemapping>
<entity name=”MyBundleEntityArticle”>
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name=”title” type=”string”>
</entity>
</doctrinemapping>
82. Conventions for XML metadata
● For MyBundleEntityArticle
● Put XML here:
@MyBundle/Resources/config/doctrine/
Article.orm.xml
83. We don't want it in the bundle!
There's a nice little trick
84. You need DoctrineBundle >=1.2
{
"require": {
...,
"doctrine/doctrinebundle":
"~1.2@dev"
}
}
85. use DoctrineBundleDoctrineBundleDependencyInjectionCompiler
DoctrineOrmMappingsPass;
class MyBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container>
addCompilerPass(
$this>
buildMappingCompilerPass()
);
}
private function buildMappingCompilerPass()
{
$xmlPath = '%kernel.root_dir%/../src/MyLibrary/Doctrine';
$namespacePrefix = 'MyLibraryModel';
return DoctrineOrmMappingsPass::createXmlMappingDriver(
array($xmlPath => $namespacePrefix)
);
}
}
86. Now:
● For MyLibraryModelArticle
● Put XML here:
src/MyLibrary/Doctrine/Article.orm.xml
87. Entities - Achievements
● Entity classes can be anywhere
● Mapping metadata can be
anywhere and in different formats
● Entities are true POPOs
89. Conventions
● In /Resources/views/[Controller]
● Filename: [Action].[format].[engine]
90. The difficulty with templates
They can have all kinds of implicit
dependencies:
● global variables, e.g. {{ app.request }}
● functions, e.g. {{ path(...) }}
● parent templates, e.g. {% extends
“::base.html.twig” %}
92. Documentation » The Cookbook » Templating »
How to use and Register namespaced Twig Paths
# in config.yml
twig:
...
paths:
Twig namespaces
"%kernel.root_dir%/../src/MyLibrary/Views": MyLibrary
// in the controller
return $this>
templating>
render('@MyLibrary/Template.html.twig');
93. Get rid of absolute paths
Using Puli, created by Bernhard
Schüssek (Symfony Forms,
Validation)
94. What Puli does
Find the absolute paths of
resources in a project
96. Register “prefixes”
Manually, or using the
Puli Composer plugin
// in the composer.json file of a package or project
{
"extra": {
"resources": {
"/mylibrary/
views": "src/MyLibrary/Views"
}
}
}
97. Twig templates
// in composer.json
{
"extra": {
"resources": {
"/mylibrary/
views": "src/MyLibrary/Views"
}
}
}
Puli Twig extension
// in the controller
return $this>
templating
>
render('/mylibrary/
views/index.html.twig');
98. Many possibilities
● Templates
● Translation files
●Mapping metadata
● Service definitions
● And so on!
99. The future is bright
● Puli is not stable yet
● But I expect much from it: