The Naked Bundle 
Matthias Noback 
@matthiasnoback
What's it all about?
An actual naked bundle
I could've called it 
BundleLitetm 
The No Code Bundle 
The Clean Bundle
But “naked” is catchy and controversial
The official view on bundles
First-class citizens 
Documentation » The Quick Tour » The Architecture
Importance 
Your code is more important than 
the framework, 
which is an implementation detail
Reuse
Nice!
All your code lives in a bundle 
Documentation » The Book » Creating Pages in Symfony2
Reuse 
“All your code in a bundle” 
contradicts the promise of reuse
Everything lives inside a bundle 
Documentation » Glossary
Not really true 
Many things live inside libraries 
(the Symfony components are 
libraries too!)
Which is good!
But you probably know that already
“libraries first”
What about... 
● Controllers 
● Entities 
● Templates 
● ...
They just need to be in a bundle 
Or do they?
Don't get me wrong 
I love Symfony!
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
A framework is there for you
Your code doesn't need a framework
Noback's Principle 
Code shouldn't rely on something 
it doesn't truly need
Bundle conventions 
Things in a bundle often rely on 
conventions to work
Conventions aren't necessary at all
So according to Noback's Principle, 
code shouldn't rely on bundle conventions too
Naming conventions 
Controllers: 
● *Controller classes 
● *action methods 
Templates: 
● in /Resources/views 
● name: Controller/Action.html.twig
Structural conventions 
Controller: 
● Extends framework Controller class 
● Is ContainerAware
Behavioral conventions 
Controller: 
● Is allowed to return an array 
● Actions can type-hint to objects which will be 
fetched based on route parameters (??)
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) 
{ 
}
These conventions are what makes an application 
a Symfony2 application
A Year With Symfony
About bundles
A bundle exposes resources
Resources 
● Service definitions 
● Controllers 
● Routes 
● Templates 
● Entities 
● Form types 
● Event listeners 
● Translations 
● ...
No need for them to be inside a bundle
When placed outside the bundle 
the resources could be reused separately
The bundle would be really small
And could just as well be a: 
Laravel package, 
Zend or Drupal module, 
CakePHP plugin, 
...
So the challenge is to 
Make the bundle as clean as possible
Move the “misplaced” things to 
● a library 
● with dependencies 
● but not symfony/framework­bundle 
;)
Being realistic 
Practical reusability
Reuse within the Symfony family 
Think: Silex, Laravel, etc.
Allowed dependency 
HttpFoundation 
● Request 
● Response 
● Exceptions 
● etc.
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, ...); 
}
Why? My secret missions 
“Let's rebuild the application, but 
this time we use Zend4 instead of 
Symfony2”
And of course 
Education
You need a strong coupling radar
Explicit dependencies 
● Function calls 
● Imported classes (“use”) 
● Included files 
● ...
Implicit dependencies 
● File locations 
● File, class, method names 
● Structure of return values 
● ...
There we go!
use SymfonyBundleFrameworkBundleControllerController; 
use SensioBundleFrameworkExtraBundleConfigurationRoute; 
use SensioBundleFrameworkExtraBundleConfigurationTemplate; 
/** 
* @Route(“/article”) 
*/ 
class ArticleController extends Controller 
{ 
/** 
* @Route(“/edit”) 
* @Template() 
*/ 
function editAction(...) 
{ 
... 
} 
} 
Controller
TODO 
✔ Don't rely on things that may not 
be there in another context: 
✔ Parent Controller class 
✔ Routing, template, annotations, etc.
class ArticleController 
{ 
function editAction(...) 
{ 
... 
} 
} 
Nice and clean ;)
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
TODO 
✔ Inject dependencies 
✔ Don't use helper methods 
✔ Render the template manually 
✔ Keep using Request 
(not really a TODO)
use DoctrineORMEntityManager; 
class ArticleController 
{ 
function __construct( 
EntityManager $em, 
) { 
$this­> 
em = $em; 
} 
... 
} 
Inject dependencies
Inline helper methods 
use SymfonyComponentHttpKernelExceptionNotFoundHttpException 
class ArticleController 
{ 
... 
public function newAction(...) 
{ 
... 
throw new NotFoundHttpException(); 
... 
} 
}
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
Dependencies are explicit now 
Also: no mention of a “bundle” 
anywhere! 
use SymfonyComponentHttpFoundationRequest; 
use SymfonyComponentHttpFoundationResponse; 
use SymfonyComponentTemplatingEngineInterface; 
use DoctrineORMEntityManager;
Dependency overflow 
use SymfonyComponentHttpFoundationResponse; 
use SymfonyComponentTemplatingEngineInterface; 
class ArticleController 
{ 
function __construct( 
EntityManager $entityManager, 
EngineInterface $templating, 
TranslatorInterface $translator, 
ValidatorInterface $validator, 
Swift_Mailer $mailer, 
RouterInterface $router 
) { 
... 
} 
... 
}
The cause? 
Convention
One controller, many actions 
one action!
__invoke() 
namespace MyLibraryControllerArticle; 
class New 
{ 
function __construct(...) 
{ 
// only what's necessary for this “action” 
} 
public function __invoke(...) 
{ 
... 
} 
}
Nice! 
└─Controller 
└─Article 
├─New.php 
├─Edit.php 
├─Archive.php 
└─Delete.php
TODO 
✔ Set up routing 
✔ Create a service and provide the 
right arguments
Bundle stuff: services.xml 
<!­­in 
MyBundle/Resources/config/services.xml → 
<?xml version="1.0" ?> 
<container> 
<services> 
<service id="new_article_controller" 
class="MyBundleControllerArticleNew"> 
<argument type="service" 
id="doctrine.orm.default_entity_manager" /> 
<argument type="service" id="templating" /> 
</service> 
</services> 
</container>
Bundle stuff: routing.xml 
<!­­in 
MyBundle/Resources/config/routing.xml → 
<?xml version="1.0" encoding="UTF­8" 
?> 
<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!
Controller – Achievements 
● Can be anywhere 
● No need to follow naming conventions 
(“*Controller”, “*action”) 
● Dependency injection, no service location 
● Reusable in any application using 
HttpFoundation
Next up: Entities
namespace MyBundleEntity; 
use DoctrineORMMapping as ORM; 
class Article 
{ 
... 
/** 
* @ORMColumn(type=”string”) 
*/ 
private $title; 
} 
Entity conventions
What's wrong with annotations? 
namespace MyBundleEntity; 
use DoctrineORMMapping as ORM; 
class Article 
{ 
... 
/** 
* @ORMColumn(type=”string”) 
*/ 
private $title; 
}
Annotations are classes
use DoctrineCommonAnnotationsAnnotationReader; 
$reader = new AnnotationReader(); 
$class = new ReflectionClass('Article'); 
$reader­> 
getClassAnnotations($class); 
BANG 
Class 
DoctrineORMMappingColumn 
not found
Well, uhm, yes, but...
Are you ever going to use 
anything else than Doctrine ORM?
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; 
}
TODO 
✔ Remove annotations 
✔ Find another way to map the data
namespace MyBundleEntity; 
class Article 
{ 
private $id; 
private $title; 
} 
Nice and clean 
A true POPO, the ideal of the data 
mapper pattern
Use XML for mapping metadata 
<doctrine­mapping> 
<entity name=”MyBundleEntityArticle”> 
<id name="id" type="integer" column="id"> 
<generator strategy="AUTO"/> 
</id> 
<field name=”title” type=”string”> 
</entity> 
</doctrine­mapping>
Conventions for XML metadata 
● For MyBundleEntityArticle 
● Put XML here: 
@MyBundle/Resources/config/doctrine/ 
Article.orm.xml
We don't want it in the bundle! 
There's a nice little trick
You need DoctrineBundle >=1.2 
{ 
"require": { 
..., 
"doctrine/doctrine­bundle": 
"~1.2@dev" 
} 
}
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) 
); 
} 
}
Now: 
● For MyLibraryModelArticle 
● Put XML here: 
src/MyLibrary/Doctrine/Article.orm.xml
Entities - Achievements 
● Entity classes can be anywhere 
● Mapping metadata can be 
anywhere and in different formats 
● Entities are true POPOs
Finally: Templates
Conventions 
● In /Resources/views/[Controller] 
● Filename: [Action].[format].[engine]
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” %}
Still, we want them out! 
And it's possible
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');
Get rid of absolute paths 
Using Puli, created by Bernhard 
Schüssek (Symfony Forms, 
Validation)
What Puli does 
Find the absolute paths of 
resources in a project
use WebmozartPuliRepositoryResourceRepository; 
$repo = new ResourceRepository(); 
$repo­> 
add('/my­library/ 
views', '/absolute/path/to/views/*'); 
/my-library/views /index.html.twig 
/absolute/path/to/views /index.html.twig 
echo $repo 
­> 
get('/my­library/ 
views/index.html.twig') 
­> 
getRealPath(); 
// => /absolute/path/to/views/index.html.twig
Register “prefixes” 
Manually, or using the 
Puli Composer plugin 
// in the composer.json file of a package or project 
{ 
"extra": { 
"resources": { 
"/my­library/ 
views": "src/MyLibrary/Views" 
} 
} 
}
Twig templates 
// in composer.json 
{ 
"extra": { 
"resources": { 
"/my­library/ 
views": "src/MyLibrary/Views" 
} 
} 
} 
Puli Twig extension 
// in the controller 
return $this­> 
templating 
­> 
render('/my­library/ 
views/index.html.twig');
Many possibilities 
● Templates 
● Translation files 
●Mapping metadata 
● Service definitions 
● And so on!
The future is bright 
● Puli is not stable yet 
● But I expect much from it:
Puli will be the ultimate tool
to create NAKED BUNDLES
and to enable reuse of 
many kinds of resources
not limited by 
project, 
framework, 
even language 
boundaries!
But even without Puli 
There's a whole lot you can do to make your code 
not rely on the framework
Remember 
The framework is for you 
Your code doesn't need it
Questions?
Get a $7,50 discount: 
http://leanpub.com/a-year-with-symfony/c/SymfonyLiveLondon2014
Get a $10,00 introduction discount: 
http://leanpub.com/principles-of-php-package-design/c/SymfonyLive 
London2014
Thank you 
Feedback: joind.in/11553 
Talk to me: @matthiasnoback
Images 
Sally MacKenzie: 
www.amazon.com

The Naked Bundle - Symfony Live London 2014

  • 1.
    The Naked Bundle Matthias Noback @matthiasnoback
  • 2.
  • 3.
  • 4.
    I could've calledit BundleLitetm The No Code Bundle The Clean Bundle
  • 5.
    But “naked” iscatchy and controversial
  • 6.
  • 7.
    First-class citizens Documentation» The Quick Tour » The Architecture
  • 8.
    Importance Your codeis more important than the framework, which is an implementation detail
  • 9.
  • 10.
  • 11.
    All your codelives in a bundle Documentation » The Book » Creating Pages in Symfony2
  • 12.
    Reuse “All yourcode in a bundle” contradicts the promise of reuse
  • 13.
    Everything lives insidea bundle Documentation » Glossary
  • 14.
    Not really true Many things live inside libraries (the Symfony components are libraries too!)
  • 15.
  • 16.
    But you probablyknow that already
  • 17.
  • 18.
    What about... ●Controllers ● Entities ● Templates ● ...
  • 19.
    They just needto be in a bundle Or do they?
  • 20.
    Don't get mewrong I love Symfony!
  • 21.
    But a frameworkis just a framework ● Quickstarter for your projects ● Prevents and solves big security issues for you ● Has a community you can rely on
  • 22.
    A framework isthere for you
  • 23.
    Your code doesn'tneed a framework
  • 24.
    Noback's Principle Codeshouldn't rely on something it doesn't truly need
  • 25.
    Bundle conventions Thingsin a bundle often rely on conventions to work
  • 26.
  • 27.
    So according toNoback's Principle, code shouldn't rely on bundle conventions too
  • 28.
    Naming conventions Controllers: ● *Controller classes ● *action methods Templates: ● in /Resources/views ● name: Controller/Action.html.twig
  • 29.
    Structural conventions Controller: ● Extends framework Controller class ● Is ContainerAware
  • 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 Uselots 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) { }
  • 32.
    These conventions arewhat makes an application a Symfony2 application
  • 33.
    A Year WithSymfony
  • 34.
  • 35.
  • 36.
    Resources ● Servicedefinitions ● Controllers ● Routes ● Templates ● Entities ● Form types ● Event listeners ● Translations ● ...
  • 37.
    No need forthem to be inside a bundle
  • 38.
    When placed outsidethe bundle the resources could be reused separately
  • 39.
    The bundle wouldbe really small
  • 40.
    And could justas well be a: Laravel package, Zend or Drupal module, CakePHP plugin, ...
  • 41.
    So the challengeis to Make the bundle as clean as possible
  • 42.
    Move the “misplaced”things to ● a library ● with dependencies ● but not symfony/framework­bundle ;)
  • 43.
  • 44.
    Reuse within theSymfony family Think: Silex, Laravel, etc.
  • 45.
    Allowed dependency HttpFoundation ● Request ● Response ● Exceptions ● etc.
  • 46.
    What do werely 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 secretmissions “Let's rebuild the application, but this time we use Zend4 instead of Symfony2”
  • 48.
    And of course Education
  • 49.
    You need astrong coupling radar
  • 50.
    Explicit dependencies ●Function calls ● Imported classes (“use”) ● Included files ● ...
  • 51.
    Implicit dependencies ●File locations ● File, class, method names ● Structure of return values ● ...
  • 52.
  • 53.
    use SymfonyBundleFrameworkBundleControllerController; useSensioBundleFrameworkExtraBundleConfigurationRoute; use SensioBundleFrameworkExtraBundleConfigurationTemplate; /** * @Route(“/article”) */ class ArticleController extends Controller { /** * @Route(“/edit”) * @Template() */ function editAction(...) { ... } } Controller
  • 54.
    TODO ✔ Don'trely on things that may not be there in another context: ✔ Parent Controller class ✔ Routing, template, annotations, etc.
  • 55.
    class ArticleController { function editAction(...) { ... } } Nice and clean ;)
  • 56.
    use SymfonyComponentHttpFoundationRequest; classArticleController { 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 ✔ Injectdependencies ✔ Don't use helper methods ✔ Render the template manually ✔ Keep using Request (not really a TODO)
  • 58.
    use DoctrineORMEntityManager; classArticleController { 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; useSymfonyComponentTemplatingEngineInterface; 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 explicitnow Also: no mention of a “bundle” anywhere! use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentTemplatingEngineInterface; use DoctrineORMEntityManager;
  • 62.
    Dependency overflow useSymfonyComponentHttpFoundationResponse; use SymfonyComponentTemplatingEngineInterface; class ArticleController { function __construct( EntityManager $entityManager, EngineInterface $templating, TranslatorInterface $translator, ValidatorInterface $validator, Swift_Mailer $mailer, RouterInterface $router ) { ... } ... }
  • 63.
  • 64.
    One controller, manyactions one action!
  • 65.
    __invoke() namespace MyLibraryControllerArticle; class New { function __construct(...) { // only what's necessary for this “action” } public function __invoke(...) { ... } }
  • 66.
    Nice! └─Controller └─Article ├─New.php ├─Edit.php ├─Archive.php └─Delete.php
  • 67.
    TODO ✔ Setup routing ✔ Create a service and provide the right arguments
  • 68.
    Bundle stuff: services.xml <!­­in MyBundle/Resources/config/services.xml → <?xml version="1.0" ?> <container> <services> <service id="new_article_controller" class="MyBundleControllerArticleNew"> <argument type="service" id="doctrine.orm.default_entity_manager" /> <argument type="service" id="templating" /> </service> </services> </container>
  • 69.
    Bundle stuff: routing.xml <!­­in MyBundle/Resources/config/routing.xml → <?xml version="1.0" encoding="UTF­8" ?> <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
  • 71.
  • 72.
    namespace MyBundleEntity; useDoctrineORMMapping as ORM; class Article { ... /** * @ORMColumn(type=”string”) */ private $title; } Entity conventions
  • 73.
    What's wrong withannotations? namespace MyBundleEntity; use DoctrineORMMapping as ORM; class Article { ... /** * @ORMColumn(type=”string”) */ private $title; }
  • 74.
  • 75.
    use DoctrineCommonAnnotationsAnnotationReader; $reader= new AnnotationReader(); $class = new ReflectionClass('Article'); $reader­> getClassAnnotations($class); BANG Class DoctrineORMMappingColumn not found
  • 76.
  • 77.
    Are you evergoing to use anything else than Doctrine ORM?
  • 78.
    Well... Think aboutDoctrine 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 ✔ Removeannotations ✔ Find another way to map the data
  • 80.
    namespace MyBundleEntity; classArticle { private $id; private $title; } Nice and clean A true POPO, the ideal of the data mapper pattern
  • 81.
    Use XML formapping metadata <doctrine­mapping> <entity name=”MyBundleEntityArticle”> <id name="id" type="integer" column="id"> <generator strategy="AUTO"/> </id> <field name=”title” type=”string”> </entity> </doctrine­mapping>
  • 82.
    Conventions for XMLmetadata ● For MyBundleEntityArticle ● Put XML here: @MyBundle/Resources/config/doctrine/ Article.orm.xml
  • 83.
    We don't wantit in the bundle! There's a nice little trick
  • 84.
    You need DoctrineBundle>=1.2 { "require": { ..., "doctrine/doctrine­bundle": "~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: ● ForMyLibraryModelArticle ● 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
  • 88.
  • 89.
    Conventions ● In/Resources/views/[Controller] ● Filename: [Action].[format].[engine]
  • 90.
    The difficulty withtemplates 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” %}
  • 91.
    Still, we wantthem out! And it's possible
  • 92.
    Documentation » TheCookbook » 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 ofabsolute paths Using Puli, created by Bernhard Schüssek (Symfony Forms, Validation)
  • 94.
    What Puli does Find the absolute paths of resources in a project
  • 95.
    use WebmozartPuliRepositoryResourceRepository; $repo= new ResourceRepository(); $repo­> add('/my­library/ views', '/absolute/path/to/views/*'); /my-library/views /index.html.twig /absolute/path/to/views /index.html.twig echo $repo ­> get('/my­library/ views/index.html.twig') ­> getRealPath(); // => /absolute/path/to/views/index.html.twig
  • 96.
    Register “prefixes” Manually,or using the Puli Composer plugin // in the composer.json file of a package or project { "extra": { "resources": { "/my­library/ views": "src/MyLibrary/Views" } } }
  • 97.
    Twig templates //in composer.json { "extra": { "resources": { "/my­library/ views": "src/MyLibrary/Views" } } } Puli Twig extension // in the controller return $this­> templating ­> render('/my­library/ views/index.html.twig');
  • 98.
    Many possibilities ●Templates ● Translation files ●Mapping metadata ● Service definitions ● And so on!
  • 99.
    The future isbright ● Puli is not stable yet ● But I expect much from it:
  • 100.
    Puli will bethe ultimate tool
  • 101.
  • 102.
    and to enablereuse of many kinds of resources
  • 103.
    not limited by project, framework, even language boundaries!
  • 104.
    But even withoutPuli There's a whole lot you can do to make your code not rely on the framework
  • 105.
    Remember The frameworkis for you Your code doesn't need it
  • 106.
  • 107.
    Get a $7,50discount: http://leanpub.com/a-year-with-symfony/c/SymfonyLiveLondon2014
  • 108.
    Get a $10,00introduction discount: http://leanpub.com/principles-of-php-package-design/c/SymfonyLive London2014
  • 109.
    Thank you Feedback:joind.in/11553 Talk to me: @matthiasnoback
  • 110.
    Images Sally MacKenzie: www.amazon.com