6. What's wrong?
Too many comments
Routing and a controller
Translations
Twig templates
A useless test
7. You are not going to use it all,
but it will be committed!
8. Before we continue, clean up your
bundle
Remove the following files and directories:
Controller
Resources/doc
Resources/public
Resources/translations
Resources/views
Tests
Also remove any superfluous comments!
19. Twig
Why Twig? I though Symfony didn't care about this.
Documentation » The Book » Creating and Using Templates
20. The old view on bundles is
not sufficient anymore
People are reimplementing things because existing
solutions are too tightly coupled to a framework (or even a
specific version).
Why is it necessary to do all these things again for Symfony,
Laravel, Zend, CodeIgniter, CakePHP, etc.?
31. Create an entity
Use app/console doctrine:generate:entity
Specs
The entity shortcut name: DpcTutorialBundle:Post.
Configuration format: annotation
It has a title(string) field.
Run app/console doctrine:schema:createor
update --forceand make sure your entity has a
corresponding table in your database.
32. Let's say you've modelled the Post
entity very well
You may want to reuse this in other projects.
Yet it's only useful if that project uses Doctrine ORM too!
34. Also: why are my entities inside a
bundle?
They are not only useful inside a Symfony project.
35. Move the entity to another
namespace
E.g. DpcTutorialModelPost.
36. Create an XML mapping file
E.g. DpcTutorialModelMappingPost.orm.xml
<doctrine-mappingxmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-ma
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entityname="DpcTutorialModelPost">
<idname="id"type="integer">
<generatorstrategy="AUTO"/>
</id>
<fieldname="title"type="string"/>
</entity>
</doctrine-mapping>
You can copy the basic XML from
/vendor/doctrine/orm/docs/en/reference/xml-
mapping.rst.
37. In fact
Always use XML mapping, it makes a lot of sense, and you
get auto-completion in your IDE!
39. If you are going to try the following at home:
Update DoctrineBundle
Modify composer.json:
{
"require":{
...
"doctrine/doctrine-bundle":"~1.2@dev"
}
}
Run composer update doctrine/doctrine-bundle
40. Add a compiler pass to your bundle
It will load the XML mapping files
useDoctrineBundleDoctrineBundleDependencyInjectionCompilerDoctrineOrmMappingsPass;
classDpcTutorialBundle
{
publicfunctionbuild(ContainerBuilder$container)
{
$container->addCompilerPass($this->buildMappingCompilerPass());
}
privatefunctionbuildMappingCompilerPass()
{
returnDoctrineOrmMappingsPass::createXmlMappingDriver(
array(
__DIR__.'/../../Test/Model/Mapping/'
=>'DpcTutorialModel'
)
);
}
}
41. What have we won?
Clean model classes
They are reusable in non-Symfony projects
They are reusable with different persistence libraries
Documentation » The Cookbook » Doctrine » How to provide model classes for several Doctrine
implementations
43. Create a controller
Use app/console generate:controller
Specs
Name: DpcTutorialBundle:Post
Configuration: annotation
Template: twig
The route contains an idparameter.
Action: showAction
Route: /post/{id}/show
44. Implement the following logic
Modify the action to retrieve a Postentity from the
database:
publicfunctionshowAction(Post$post)
{
returnarray('post'=>$post);
}
45. Don't forget to register the route
#inthebundle'srouting.ymlfile:
DpcTutorialBundle_Controllers:
resource:"@DpcTutorialBundle/Controller"
type:"annotation"
50. Create a service for the controller
services:
dpc_tutorial.post_controller:
class:DpcTutorialControllerPostController
51. Remove @Routeannotations
Instead: define actual routes in the bundle's routing.yml
file.
Use the service id of the controller instead of its class name.
dpc_tutorial.post_controller.show:
path:/post/{id}/show
defaults:
_controller:dpc_tutorial.post_controller:showAction
52. Remove @Templateannotations
Inject the templatingservice instead and use it to render
the template.
useSymfonyComponentHttpFoundationResponse;
useSymfonyComponentTemplatingEngineInterface;
classPostController
{
publicfunction__construct(EngineInterface$templating)
{
$this->templating=$templating;
}
publicfunctionshowAction(Post$post)
{
returnnewResponse(
$this->templating->render(
'DpcTutorialBundle:Post:show.html.twig',
array('post'=>$post)
)
);
}
}
55. Move the template to the library
E.g. from Dpc/Bundle/TutorialBundle/Resources/views/Post/show.html.twigto
Dpc/Tutorial/View/Post/show.html.twig
56. Change the template reference
$this->templating->render(
'@DpcTutorial/Post/show.html.twig',
array('post'=>$post)
)
57. Register the new location of the
templates
#inconfig.yml
twig:
...
paths:
"%kernel.root_dir%/../src/Dpc/Tutorial/View":DpcTutorial
Documentation » The Cookbook » Templating » How to use and Register namespaced Twig Paths
60. One last step!
The action's $postargument relies on something called
.param converters
Those convert the idfrom the route to the actual Post
entity.
This is actually Symfony framework-specific behavior
61. Rewrite the controller to make use
of a repository
useDoctrineCommonPersistenceObjectRepository;
classPostController
{
publicfunction__construct(...,ObjectRepository$postRepository)
{
...
$this->postRepository=$postRepository;
}
publicfunctionshowAction($id)
{
$post=$this->postRepository->find($id);
if(!($postinstanceofPost)){
thrownewNotFoundHttpException();
}
...
}
}
67. Create a console command
Use app/console generate:console-command
Make it insert a new post in the database.
It takes one argument: the post's title.
72. Create a service for it
Give it the tag console.command.
Or else it won't be recognized anymore!
services:
dpc_tutorial.create_post_command:
class:DpcTutorialCommandCreatePostCommand
tags:
-{name:console.command}
74. Extend from Command
Then inject dependencies instead of fetching them from the
container.
useDoctrineCommonPersistenceManagerRegistry;
classCreatePostCommandextendsCommand
{
private$doctrine;
publicfunction__construct(ManagerRegistry$doctrine)
{
parent::__construct();
$this->doctrine=$doctrine;
}
...
protectedfunctionexecute(InputInterface$input,OutputInterface$output)
{
$manager=$this->doctrine->getManager();
...
}
}
76. What do we have?
Explicit dependencies
Reusable commands that works in all projects that use the
Symfony Console Component (like )
A bit less magic (no auto-registering commands)
Which means now we can put anything we want in the
Commanddirectory
Cilex
82. Prepare a test suite for your
Configurationclass
Create a directory Tests/DependencyInjectioninside
the bundle.
In that directory create a new class:
ConfigurationTest.
83. Create the test class
The ConfigurationTestshould extend from
AbstractConfigurationTestCase
Implement the missing method getConfiguration()
namespaceDpcBundleTutorialBundleTestsDependencyInjection;
useDpcBundleTutorialBundleDependencyInjectionConfiguration;
useMatthiasSymfonyConfigTestPhpUnitAbstractConfigurationTestCase;
classConfigurationTestextendsAbstractConfigurationTestCase
{
protectedfunctiongetConfiguration()
{
returnnewConfiguration();
}
}
84. Desired structure in config.yml
dpc_tutorial:
#hostshouldbearequiredkey
host:localhost
85. A required value: host
Test first
/**
*@test
*/
publicfunctionthe_host_key_is_required()
{
$this->assertConfigurationIsInvalid(
array(
array()
),
'host'
);
}
If we provide no values at all, we expect an exception
containing "host".
99. Disable deep merging
Values from different configuration sources should not be
merged.
$rootNode
->children()
->arrayNode('servers')
->performNoDeepMerging()
...
->end()
->end();
100. Advantages of TDD for
Configurationclasses
We gradually approach our goal.
We immediately get feedback on what's wrong.
We can test different configuration values without
changing config.ymlmanually.
We can make sure the user gets very specific error
messages about wrong configuration values.
Learn more about all the options by reading the
.
offical
documentation of the Config component
102. Create a test class for your extension
Directory: Tests/DependencyInjection
Class name: [NameOfTheExtension]Test
Class should extend AbstractExtensionTestCase
Implement getContainerExtensions(): return an
instance of your extension class
namespaceDpcBundleTutorialBundleTestsDependencyInjection;
useDpcBundleTutorialBundleDependencyInjectionDpcTutorialExtension;
useMatthiasSymfonyDependencyInjectionTestPhpUnitAbstractExtensionTestCase;
classDpcTutorialExtensionTestextendsAbstractExtensionTestCase
{
protectedfunctiongetContainerExtensions()
{
returnarray(
newDpcTutorialExtension()
);
}
}
108. Shortcuts versus the Real deal
The base class provides some useful shortcuts
To get the most out of testing your extension:
Read all about classes like Definitionin the official
documentation
119. The alias is used to find out which configuration belongs to
which bundle:
#inconfig.yml
dpc_tutorial:
...
By convention it's the lowercase underscored bundle name.
120. But what happens when I
rename the bundle?
The alias changes too, which means configuration in
config.ymlwon't be recognized anymore.
121. Also:
The extension needs to be renamed too, because of the
naming conventions...
DpcTutorialBundle,DpcTutorialExtension,dpc_tutorial
NobackTestBundle,NobackTestExtension,noback_test
122. So: open your extension class
Override the getAlias()method.
Make it return the alias of your extension (a string).
E.g. DpcTutorialBundle::getAlias()returns
dpc_tutorial.
126. Modify extension and configuration
How can we make sure that the name of the root node in
the configuration class is the same as the alias returned by
getAlias()?
138. We introduced flexibility...
By hard-coding the alias
And by skipping all the magic stuff
Now we can
Change *Bundleinto *Bandle
Change *Extensioninto *Plugin
Change Configurationinto Complexity
If we want...