Matthias Noback presented on creating "naked bundles" in Symfony that do not rely on bundle conventions and can be reused outside of Symfony. He advocated extracting code from bundles into independent libraries with explicit dependencies instead of implicit ones. This would allow the code to be reused in other frameworks. He provided examples of creating controller classes without extending base classes, using dependency injection instead of service location, and mapping entities with XML instead of annotations. The goal is to remove unnecessary ties to the framework so code is truly decoupled and portable.
22. 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
31. Behavioral conventions
Controller:
● Is allowed to return an array
● Actions can type-hint to objects which will be
fetched based on route parameters (??)
32. 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)
{
}
47. 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, ...);
}
48. Why? My secret missions
“Let's rebuild the application, but
this time we use Zend4 instead of
Symfony2”
57. 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
58. TODO
✔ Inject dependencies
✔ Don't use helper methods
✔ Render the template manually
✔ Keep using Request
(not really a TODO)
59. use DoctrineORMEntityManager;
class ArticleController
{
function __construct(
EntityManager $em,
) {
$this>
em = $em;
}
...
}
Inject dependencies
60. Inline helper methods
use SymfonyComponentHttpKernelExceptionNotFoundHttpException
class ArticleController
{
...
public function newAction(...)
{
...
throw new NotFoundHttpException();
...
}
}
61. 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
62. Dependencies are explicit now
Also: no mention of a “bundle”
anywhere!
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentTemplatingEngineInterface;
use DoctrineORMEntityManager;
63. Dependency overflow
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentTemplatingEngineInterface;
class ArticleController
{
function __construct(
EntityManager $entityManager,
EngineInterface $templating,
TranslatorInterface $translator,
ValidatorInterface $validator,
Swift_Mailer $mailer,
RouterInterface $router
) {
...
}
...
}
70. 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!
71. Controller – Achievements
● Can be anywhere
● No need to follow naming conventions
(“*Controller”, “*action”)
● Dependency injection, no service location
● Reusable in any application using
HttpFoundation
78. Are you ever going to use
anything else than Doctrine ORM?
79. 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;
}
80. TODO
✔ Remove annotations
✔ Find another way to map the data
81. namespace MyBundleEntity;
class Article
{
private $id;
private $title;
}
Nice and clean
A true POPO, the ideal of the data
mapper pattern
82. 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>
83. Conventions for XML metadata
● For MyBundleEntityArticle
● Put XML here:
@MyBundle/Resources/config/doctrine/
Article.orm.xml
84. We don't want it in the bundle!
There's a nice little trick
85. You need DoctrineBundle >=1.2
{
"require": {
...,
"doctrine/doctrinebundle":
"~1.2@dev"
}
}
86. 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)
);
}
}
87. Now:
● For MyLibraryModelArticle
● Put XML here:
src/MyLibrary/Doctrine/Article.orm.xml
88. Entities - Achievements
● Entity classes can be anywhere
● Mapping metadata can be
anywhere and in different formats
● Entities are true POPOs
90. Conventions
● In /Resources/views/[Controller]
● Filename: [Action].[format].[engine]
91. 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” %}
93. 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');
94. Get rid of absolute paths
Using Puli, created by Bernhard
Schüssek (Symfony Forms,
Validation)
95. What Puli does
Find the absolute paths of
resources in a project
97. 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"
}
}
}
98. 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');
99. Many possibilities
● Templates
● Translation files
●Mapping metadata
● Service definitions
● And so on!
100. The future is bright
● Puli is not stable yet
● But I expect much from it: