Slide 1

Slide 1 text

Symfony Best Practices and beyond Oleg Zinchenko KNPLabs https://twitter.com/1cdecoder https://github.com/cystbear http://knplabs.com/

Slide 2

Slide 2 text

Symfony Best Practices Book http://symfony.com/doc/current/best_practices/index.html

Slide 3

Slide 3 text

Ryan Weaver (KNPer) https://twitter.com/weaverryan https://knpuniversity.com/

Slide 4

Slide 4 text

Disclaimer THE SOFTWARE SLIDES IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Slide 5

Slide 5 text

Creating the Project

Slide 6

Slide 6 text

Always use Composer to install Symfony https://getcomposer.org/ https://packagist.org/

Slide 7

Slide 7 text

AcmeDemoBundle

Slide 8

Slide 8 text

Create only one bundle called AppBundle uberCMS/ ├─ app/ │ ├─ console │ ├─ cache/ │ ├─ config/ │ └─ logs/ │ └─ Resources/ ├─ src/ │ └─ AppBundle/ ├─ vendor/ └─ web/ ├─ app.php └─ app_dev.php

Slide 9

Slide 9 text

Create only one bundle called AppBundle What about API Bundle? What about Core Bundle? What about DDD? Do not use Sf3 dir structure... yet Imagine several kernels

Slide 10

Slide 10 text

Configuration

Slide 11

Slide 11 text

Infrastructure/Application Related Configuration parameters.yml parameters.yml.dist app/config/config.yml

Slide 12

Slide 12 text

Infrastructure/App Related Configuration # app/config/config.yml imports: - resource: parameters.yml - resource: framework.yml - resource: doctrine.yml - resource: doctrine_extension.yml - resource: html_purifier.yml - resource: facebook.yml - resource: mailer.yml - resource: monolog.yml - resource: twig.yml - resource: imagine.yml

Slide 13

Slide 13 text

Use constants to define configuration options that rarely change Different environments Different clients

Slide 14

Slide 14 text

Semantic Configuration: Don't Do It services.xml/yml + *Extension.php + Configuration.php http://symfony2-document.readthedocs.org/en/latest/cookbook/bundles/extension.html

Slide 15

Slide 15 text

Business Logic

Slide 16

Slide 16 text

Business Logic or How to reach Zen

Slide 17

Slide 17 text

Service Format: YAML

Slide 18

Slide 18 text

Config Formats Routing DBmapping Services Validation Serializer map Translations Yaml Annotation XML Annotation, XML Yaml Yaml

Slide 19

Slide 19 text

Service: No Class Parameter # app/config/services.yml parameters: slugger.class: AppBundle\Slugger services: slugger: class: "%slugger.class%"

Slide 20

Slide 20 text

Service: No Class Parameter # app/config/services.yml services: slugger: class: AppBundle\Slugger

Slide 21

Slide 21 text

Annotations for Doctrine mappings /** @MongoDB\Document */ class Topic { /** @MongoDB\Id */ protected $id; /** @MongoDB\String */ protected $name; /** @MongoDB\Int */ protected $code; // .... }

Slide 22

Slide 22 text

Annotations for Doctrine mappings

Slide 23

Slide 23 text

Data Fixtures One fixture file per entity OrderedFixtureInterface DoctrineFixturesBundle + Alice & bundle + Faker https://github.com/doctrine/DoctrineFixturesBundle https://github.com/hautelook/AliceBundle https://github.com/fzaninotto/Faker

Slide 24

Slide 24 text

Controllers

Slide 25

Slide 25 text

Make your controller extend the FrameworkBundle Get Request Submit form if any Call one Service method Return Response Rendering HTML far away

Slide 26

Slide 26 text

Routing Configuration Annotations External format (YAML, XML) /** @Route("/") */ public function indexAction() { } _index: pattern: / defaults: { _controller: AcmeBundle:Frontend:index }

Slide 27

Slide 27 text

Use ParamConverter /** @ParamConverter("post", class="BlogBundle:Post") */ public function showAction(Post $post) { return $this->render('show.html.twig', array( 'post' => $post, )); }

Slide 28

Slide 28 text

Templates

Slide 29

Slide 29 text

Use Twig for your templates

Slide 30

Slide 30 text

Store all your templates in app/Resources/views/ Inconsistently with Bundle coupling Hard to maintain Probably easier write template path Inside bundles In app/ DemoBunde:Default:index.html.twig default/index.html.twig ::layout.html.twig layout.html.twig AcmeDemoBundle::index.html.twig index.html.twig

Slide 31

Slide 31 text

Forms

Slide 32

Slide 32 text

Use Form Types Define your forms as PHP classes class PostType extends AbstractType { public function buildForm( FormBuilderInterface $builder, array $options ) { $builder ->add('title') ->add('summary', 'textarea') ->add('content', 'textarea') ; }

Slide 33

Slide 33 text

public function setDefaultOptions( OptionsResolverInterface $resolver ) { $resolver->setDefaults(array( 'data_class' => 'AppBundle\Entity\Post' )); } Use Form Types Always add “form model”

Slide 34

Slide 34 text

class PostType extends AbstractType { public function buildForm(// ...) { $builder // ... ->add('save', 'submit', array('label' => 'Create Post')) ; } Wrong code example “Add buttons in the templates, not in the form classes or the controllers” Form Templates

Slide 35

Slide 35 text

Form Templates {{ form_widget(form) }} “Don't use the form() or form_start()”

Slide 36

Slide 36 text

Handling Forms $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($post); $em->flush(); } return $this->redirect(...);

Slide 37

Slide 37 text

Handling Forms $form->handleRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($post); $em->flush(); } return $this->redirect(...);

Slide 38

Slide 38 text

i18n

Slide 39

Slide 39 text

Use the XLIFF for your translation files Too Verbose Extra complexity Accidental complexity Inconsistently with service configs

Slide 40

Slide 40 text

Translation files location “Store the translation files in the app/Resources/translations directory” Same issues as for templates Inconsistently with Bundle coupling

Slide 41

Slide 41 text

Translation Keys “Always use keys for translations instead of content strings” At form types At templates At validation messages

Slide 42

Slide 42 text

Security

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

Firewalls “Be simple: use only one firewall entry with the anonymous key enabled” Remember about API auth

Slide 45

Slide 45 text

Password hashing “Use the bcrypt encoder for encoding your users passwords” security: encoders: AppBundle\Entity\User: bcrypt For old Sf systems <2.3 https://github.com/elnur/ElnurBlowfishPasswordEncoderBundle

Slide 46

Slide 46 text

Authorization

Slide 47

Slide 47 text

Authorization Protecting broad URL patterns, use access_control access_control: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: ROLE_ADMIN }

Slide 48

Slide 48 text

Authorization Whenever possible, use the @Security annotation /** * @Security("has_role('ROLE_ADMIN')") */ public function indexAction() { // ... }

Slide 49

Slide 49 text

Custom Voter class PostVoter extends AbstractVoter { const CREATE = 'create'; const EDIT = 'edit'; protected function getSupportedAttributes() { return array(self::CREATE, self::EDIT); } protected function getSupportedClasses() { return array('AppBundle\Entity\Post'); } } For fine-grained restrictions, define a custom security voter

Slide 50

Slide 50 text

For fine-grained restrictions, define a custom security voter protected function isGranted($attribute, $post, $user = null) { if (!$user instanceof UserInterface) return false; if ($attribute == self::CREATE && in_array(ROLE_ADMIN, $user->getRoles()) ) return true; if ($attribute == self::EDIT && $user->getEmail() === $post->getAuthorEmail() ) return true; return false; } Custom Voter

Slide 51

Slide 51 text

Applying voter Custom Voter # app/config/services.yml services: post_voter: class: AppBundle\Security\PostVoter public: false tags: - { name: security.voter } /** * @Security("is_granted('edit', post)") */ public function editAction(Post $post)

Slide 52

Slide 52 text

Phew

Slide 53

Slide 53 text

Authorization ACL For restricting access to any object by any user via an admin interface, use the Symfony ACL

Slide 54

Slide 54 text

Drop ACE, use voters http://symfony.com/doc/current/cookbook/security/acl.html http://slides.com/marieminasyan/drop-ace-use-role-voters https://www.youtube.com/watch?v=e7HfW4TgnUY

Slide 55

Slide 55 text

Web Assets

Slide 56

Slide 56 text

Use Assetic, Kris approves https://twitter.com/kriswallsmith http://kriswallsmith.net/

Slide 57

Slide 57 text

Tests

Slide 58

Slide 58 text

Write tests, they approve

Slide 59

Slide 59 text

No content