$30 off During Our Annual Pro Sale. View Details »

Symfony best practices и не только Олег Зинченко

fwdays
November 18, 2014

Symfony best practices и не только Олег Зинченко

fwdays

November 18, 2014
Tweet

More Decks by fwdays

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. 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.

    View Slide

  5. Creating the Project

    View Slide

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

    View Slide

  7. AcmeDemoBundle

    View Slide

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

    View Slide

  9. 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

    View Slide

  10. Configuration

    View Slide

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

    View Slide

  12. 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

    View Slide

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

    View Slide

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

    View Slide

  15. Business Logic

    View Slide

  16. Business Logic
    or How to reach Zen

    View Slide

  17. Service Format: YAML

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. Annotations for Doctrine mappings

    View Slide

  23. 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

    View Slide

  24. Controllers

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. Templates

    View Slide

  29. Use Twig for your templates

    View Slide

  30. 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

    View Slide

  31. Forms

    View Slide

  32. 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')
    ;
    }

    View Slide

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

    View Slide

  34. 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

    View Slide

  35. Form Templates

    {{ form_widget(form) }}

    “Don't use the form() or form_start()”

    View Slide

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

    View Slide

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

    View Slide

  38. i18n

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  42. Security

    View Slide

  43. View Slide

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

    View Slide

  45. 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

    View Slide

  46. Authorization

    View Slide

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

    View Slide

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

    View Slide

  49. 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

    View Slide

  50. 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

    View Slide

  51. 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)

    View Slide

  52. Phew

    View Slide

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

    View Slide

  54. 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

    View Slide

  55. Web Assets

    View Slide

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

    View Slide

  57. Tests

    View Slide

  58. Write tests,
    they approve

    View Slide

  59. View Slide