Building Better Controllers Symfony Live Berlin 2014. Benjamin Eberlei <benjamin@qafoo.com> 31.10.2014



Similar documents
Symfony2 and Drupal. Why to talk about Symfony2 framework?

A Tour of Silex and Symfony Components. Robert

CERN Summer Student Program 2013 Report

Drupal CMS for marketing sites

Symfony2: estudo de caso IngressoPrático

Web Development Frameworks

This course provides students with the knowledge and skills to develop ASP.NET MVC 4 web applications.

What s really under the hood? How I learned to stop worrying and love Magento

Testing Rails. by Josh Steiner. thoughtbot

Migration Eclipse 3 to Eclipse 4

Automating Rich Internet Application Development for Enterprise Web 2.0 and SOA

Designing framework for web development

Paul Boisvert. Director Product Management, Magento

THE ROAD TO CODE. ANDROID DEVELOPMENT IMMERSIVE May 31. WEB DEVELOPMENT IMMERSIVE May 31 GENERAL ASSEMBLY

Case Studies of Running the Platform. NetBeans UML Servlet JSP GlassFish EJB

Developing ASP.NET MVC 4 Web Applications MOC 20486

Advanced Web Development SCOPE OF WEB DEVELOPMENT INDUSTRY

Ruby on Rails. Object Oriented Analysis & Design CSCI-5448 University of Colorado, Boulder. -Dheeraj Potlapally

Developer Tutorial Version 1. 0 February 2015

Ruby On Rails. CSCI 5449 Submitted by: Bhaskar Vaish

Drupal 8 Development Retrospective. A timeline and retrospective from a core contributor

Facebook - Twitter - Google +1 all in one plugin for Joomla enable "Twitter button", "Google +1

HOW TO CREATE THEME IN MAGENTO 2

alchemy webapp framework Introduction What is alchemy?

Easy configuration of NETCONF devices

Pentesting Web Frameworks (preview of next year's SEC642 update)

Safewhere*Identify 3.4. Release Notes

Developing ASP.NET MVC 4 Web Applications

Web Frameworks. web development done right. Course of Web Technologies A.A. 2010/2011 Valerio Maggio, PhD Student Prof.

Model-View-Controller. and. Struts 2

Aspect Oriented Programming. with. Spring

Test-Drive ASP.NET MVC

IT Insights. Using Microsoft SharePoint 2013 to build a robust support and training portal. A service of Microsoft IT Showcase

Add a new payment method plugin

Design and Functional Specification

Web Cloud Architecture

EZcast technical documentation

Getting Started with Kanban Paul Klipp

RepoGuard Validation Framework for Version Control Systems

Developing ASP.NET MVC 4 Web Applications Course 20486A; 5 Days, Instructor-led

Certified PHP/MySQL Web Developer Course

JUG Münster. Modern Java web development. Thomas Kruse

Pattern Insight Clone Detection

Customer Bank Account Management System Technical Specification Document

Ektron to EPiServer Digital Experience Cloud: Information Architecture

After many years we are happy to create a new social plugin with a great potential.

Stock Trader System. Architecture Description

A MORE FLEXIBLE MULTI-TENANT SOA FOR SAAS

Symfony vs. Integrating products when to use a framework

Oracle Application Development Framework Overview

White Paper On. Single Page Application. Presented by: Yatin Patel

Overview. How It Works

Java and RDBMS. Married with issues. Database constraints

Website Promotion for Voice Actors: How to get the Search Engines to give you Top Billing! By Jodi Krangle

An introduction to creating JSF applications in Rational Application Developer Version 8.0

How To Understand The Benefits Of Cloud Computing

Framework as a master tool in modern web development

How To Build A Web App

Shipbeat Magento Module. Installation and user guide

WEB AND APPLICATION DEVELOPMENT ENGINEER

Official Amazon Checkout Extension for Magento Commerce. Documentation

Sponsored by: Speaker: Brian Madden, Independent Industry Analyst and Blogger

BUILDING MULTILINGUAL WEBSITES WITH DRUPAL 7

Designing RESTful Web Applications

Power Tools for Pivotal Tracker

A How-to Guide By: Riaan Van Der Merwe, General Manager, Dynamics, Neudesic

The Rules 1. One level of indentation per method 2. Don t use the ELSE keyword 3. Wrap all primitives and Strings

Volkov Vyacheslav. Summary. Saransk, , Mordovia, Russian Federation Moscow, Russian Federation +7(925) , +7(917)

Multi Factor Authentication API

User Stories Applied

Code Qualities and Coding Practices

Specialized Programme on Web Application Development using Open Source Tools

A This panel lists all the IVR queues you have built so far. This is where you start the creation of a IVR

Marketing Tips. From M R K Development

Choosing a Content Management System (CMS)

Real World SOA. Sean A Corfield CTO, Scazu Inc.

symfony symfony as a platform Fabien Potencier

Structured Content: the Key to Agile. Web Experience Management. Introduction

Filter NEW IN FIRSTCLASS CLIENT WHAT S NEW IN FIRSTCLASS 9.0. New Look. Login screen. List View Sort Order. Filtering Containers.

From the Monolith to Microservices: Evolving Your Architecture to Scale. Randy linkedin.com/in/randyshoup

Best Practices for Programming Eclipse and OSGi

Transcription:

Building Better Controllers Symfony Live Berlin 2014 Benjamin Eberlei <benjamin@qafoo.com> 31.10.2014

Me Working at Qafoo We promote high quality code with trainings and consulting http://qafoo.com Doctrine and Symfony http://whitewashing.de Twitter @beberlei and @qafoo

Outline Motivation

Motivation Application-Design A passion/obsession of mine. I occassionally blog about it. Mostly Symfony related

Motivation Give me a one-handed economist! All my economists say, On the one hand, on the other hand. (Harry S. Truman)

Motivation Design is about choices/trade-offs

Failure: Overengineering

Success: Simple design

Some (good!) Choices Buzzing in the Symfony Community (My blogging is partially to blame for this, sorry!) Domain-Driven-Design Command-Query-Responsibility-Seperation Event-Sourcing Hexagonal Architectures Microservices Symfony without Symfony Not even close to 100% of all the choices you can consider.

Consider A good architecture allows you to defer critical decisions, it doesn t force you to defer them. However, if you can defer them, it means you have lots of flexibility. (Uncle Bob)

Consider And since your controllers should be thin and contain nothing more than a few lines of glue-code, spending hours trying to decouple them from your framework doesn t benefit you in the long run. The amount of time wasted isn t worth the benefit. (Symfony Best Practices Book)

I don t agree 100% But there is a lot of truth in it! Standard Symfony Controllers usually end up fat! Leads us to think we need abstraction from Symfony Improving Symfony is easier, requires less time Goal: Defer complex abstractions

Requirements for Incremental Design Testability Refactorabillity Loose coupling Small number of dependencies

Quintessential Symfony Controller Example 1 p u b l i c f u n c t i o n e d i t A c t i o n ( $id, Request $ r e q u e s t ) 2 { 3 $entitymanager = $this >get ( doctrine. orm. default entity manager ) ; 4 $ r e p o s i t o r y = $entitymanager >g e t R e p o s i t o r y ( AcmeHelloBundle : A r t i c l e ) ; 5 6 t r y { 7 $ a r t i c l e = $ r e p o s i t o r y >f i n d Q u e r y ( $ i d ) ; 8 } c a t c h ( N o R e s u l t E x c e p t i o n $e ) { 9 throw new NotFoundHttpException ( ) ; 10 } 11 12 i f (! $ t h i s >g e t ( s e c u r i t y. c o n t e x t ) >i s G r a n t e d ( EDIT, $ a r t i c l e ) ) { 13 throw new AccessDeniedHttpException ( ) ; 14 } 15 16 $form = $this >createform ( new E d i t A r t i c l e T y p e ( ), $ a r t i c l e ) ; 17 $form >h a n d l e R e q u e s t ( $ r e q u e s t ) ; 18 19 i f ( $form >isbound ( ) && $form >i s V a l i d ( ) ) { 20 $ e n t i t y M a n ager >f l u s h ( ) ; 21 22 $ t h i s >get ( s e s s i o n ) >getflashbag ( ) >add ( 23 n o t i c e, 24 Your changes were saved! 25 ) ; 26 27 r e t u r n $ t h i s >r e d i r e c t ( $ t h i s >g e n e r a t e U r l ( A r t i c l e. show, $ i d ) ) ; 28 } 29 30 r e t u r n $ t h i s >r e n d e r ( 31 AcmeHelloBundle : A r t i c l e : e d i t. html. t w i g, 32 a r r a y ( 33 form => $form >createview ( ), 34 article => $article, 35 ) 36 ) ; 37 }

Quintessential Symfony Controller Example 1 p u b l i c f u n c t i o n e d i t A c t i o n ( $id, Request $ r e q u e s t ) 2 { 3 $ e n t i t y M a n a g e r = $ t h i s 4 >g e t ( d o c t r i n e. orm. d e f a u l t e n t i t y m a n a g e r ) ; 5 $ r e p o s i t o r y = $ e n t i t y M a n a g e r 6 >g e t R e p o s i t o r y ( AcmeHelloBundle : A r t i c l e ) ; 7 8 t r y { 9 $ a r t i c l e = $ r e p o s i t o r y >f i n d Q u e r y ( $ i d ) ; 10 } c a t c h ( N o R e s u l t E x c e p t i o n $e ) { 11 throw new NotFo undh ttpex cepti on ( ) ; 12 } 13 //.. 14 }

Quintessential Symfony Controller Example 1 p u b l i c f u n c t i o n e d i t A c t i o n ( $id, Request $ r e q u e s t ) 2 { 3 //.. 4 5 $ s e c u r i t y = $ t h i s >g e t ( s e c u r i t y. c o n t e x t ) ; 6 i f (! $ s e c u r i t y >i s G r a n t e d ( EDIT, $ a r t i c l e ) ) { 7 throw new A c c e s s D e n i e d H t t p E x c e p t i o n ( ) ; 8 } 9 10 //.. 11 }

Quintessential Symfony Controller Example 1 p u b l i c f u n c t i o n e d i t A c t i o n ( $id, Request $ r e q u e s t ) 2 { 3 //... 4 $form = $ t h i s >createform ( 5 new E d i t A r t i c l e T y p e ( ), $ a r t i c l e 6 ) ; 7 $form >h a n d l e R e q u e s t ( $ r e q u e s t ) ; 8 9 i f ( $form >isbound ( ) && $form > i s V a l i d ( ) ) { 10 //... 11 } 12 13 //... 14 }

Quintessential Symfony Controller Example 1 $entitymanager >f l u s h ( ) ; 2 3 $ t h i s >g e t ( s e s s i o n ) >g etflashbag ( ) >add ( 4 n o t i c e, 5 Your changes were saved! 6 ) ; 7 8 r e t u r n $ t h i s > r e d i r e c t ( 9 $ t h i s >g e n e r a t e U r l ( A r t i c l e. show, $ i d ) 10 ) ;

Quintessential Symfony Controller Example 1 p u b l i c f u n c t i o n e d i t A c t i o n ( $id, Request $ r e q u e s t ) 2 { 3 //.. 4 r e t u r n $ t h i s >r e n d e r ( 5 AcmeHelloBundle : A r t i c l e : e d i t. html. t w i g, 6 a r r a y ( 7 form => $form >c r e a t e V i e w ( ), 8 a r t i c l e => $ a r t i c l e, 9 ) 10 ) ; 11 }

Painful! Unfortunately the document is made in this style

Problems Coupling: 7 services, 3 exceptions and 1 entity class Even with impressive typing skills the time lost typing matters. Abstracting Forms, Security, Doctrine is alot of work Number of bugs are correlated to lines of code And this is just a CRUD example (no real behavior involved!)

Solution? Best Practices suggest Annotations But this just moves the lines/problem elsewere I don t trust them, especially not with security.

Solution? With some simple improvements on top of Symfony we can: cut down the number of services to one remove all exception code cut number of lines by half still keep the Symfony style

Two Approaches Move code related to response generation into listeners. Move code related to request/state into param converters. Contrary to Best Practices claim, the overhead is neglectible

Template EventListener Goal: Getting rid of the "templating" service dependency: Return array from controller Automatically convert to rendering a template Use naming convention to find the template Return new TemplateView() to pick a different template. Avoids having to use annotations.

Template EventListener 1 p u b l i c f u n c t i o n e d i t A c t i o n ( $ i d /,... / ) 2 { 3 r e t u r n a r r a y ( 4 a r t i c l e => $ a r t i c l e, 5 form => $form >c r e a t e V i e w ( ) 6 ) ; 7 }

Template EventListener 1 u s e QafooLabs \MVC\ TemplateView ; 2 3 p u b l i c f u n c t i o n e d i t A c t i o n ( $ i d /,... / ) 4 { 5 r e t u r n new TemplateView ( 6 AcmeHelloBundle : A r t i c l e : form. html. t w i g, 7 a r r a y ( 8 a r t i c l e => $ a r t i c l e, 9 form => $form >c r e a t e V i e w ( ) 10 ) 11 ) ; 12 }

Redirect Listener Goal: Getting rid of the "router" service dependency: The router is needed in controllers for redirecting Introduce new RedirectRouteResponse()

Redirect Listener 1 use QafooLabs \MVC\ R e d i r e c t R o u t e R e s p o n s e ; 2 3 p u b l i c f u n c t i o n e d i t A c t i o n ( $ i d /,... / ) 4 { 5 //... 6 7 r e t u r n new R e d i r e c t R o u t e R e s p o n s e ( 8 A r t i c l e. show, 9 a r r a y ( i d => $ i d ) 10 ) ; 11 }

Convert Exceptions Goal: Getting rid of Exception casting and handling in controller. Introduce configuration to map exceptions to status codes. Example: Doctrine NoResultException is always a 404 This reuses functionality that was only available for REST APIs before

Convert Exceptions 1 q a f o o l a b s n o f r a m e w o r k : 2 c o n v e r t e x c e p t i o n s : 3 D o c t r i n e \\ORM\\ N o R e s u l t E x c e p t i o n : 404

Handling Flash-Messages Goal: Get rid of the "session" service dependency. Introduce ParamConverter that passes the flash state into controller. Typehint for Flashes Suddenly turns into data object, not a service anymore. Applies learning from Request as a service failure

Handling Flash-Messages 1 p u b l i c f u n c t i o n e d i t A c t i o n ( /... /, F l a s h e s $ f l a s h e s ) 2 { 3 //.. 4 $ f l a s h e s >add ( n o t i c e, Your changes were saved ) ; 5 //... 6 }

Handling Forms Goal: Get rid of the "form.factory" service dependency. Introduce ParamConverter that passes new FormRequest FormRequest wraps both Request and FormFactory Typehint for FormRequest Forms turns into data object, not a service anymore.

Handling Forms 1 p u b l i c f u n c t i o n e d i t A c t i o n ( /. /, FormRequest $ r e q u e s t ) 2 { 3 $type = new E d i t A r t i c l e T y p e ( ) ; 4 i f ( $formrequest >h a n d l e ( $type, $ a r t i c l e ) ) { 5 //... 6 } 7 8 r e t u r n a r r a y ( 9 //... 10 form => $formrequest >c r e a t e V i e w ( ) 11 ) ; 12 }

Handling Security Goal: Get rid of the "security.context" service dependency. Security Token is a data object Data should be passed into functions as argument. Typehint for FrameworkContext Does someone have a better name? Applies learning from Request as a service failure

Handling Security 1 p u b l i c f u n c t i o n e d i t A c t i o n ( FrameworkContext $ c o n t e x t ) 2 { 3 //... 4 $context >a s s e r t I s G r a n t e d ( EDIT, $ a r t i c l e ) ; 5 //... 6 }

Controller as a Service Goal: Get rid of the "container" dependency Inject only the services that you need Controversial: Fabien does not approve http://symfony.com/doc/current/cookbook/ controller/service.html

Controller as a Service 1 <?php 2 3 c l a s s A r t i c l e C o n t r o l l e r 4 { 5 / 6 @var A r t i c l e R e p o s i t o r y 7 / 8 p r i v a t e $ r e p o s i t o r y ; 9 10 p u b l i c f u n c t i o n e d i t A c t i o n ( $id, /. / ) 11 { 12 $ a r t i c l e = $ t h i s >r e p o s i t o r y >f i n d Q u e r y ( $ i d ) ; 13 14 //.. 15 } 16 }

Result 1 p u b l i c function editaction ( $id, FormRequest $request, FrameworkContext $context, F l a s h e s $ f l a s h e s ) 2 { 3 $ a r t i c l e = $ t h i s >r e p o s i t o r y >f i n d Q u e r y ( $ i d ) ; 4 $ c o n t e x t >a s s e r t I s G r a n t e d ( EDIT, $ a r t i c l e ) ; 5 6 $type = new E d i t A r t i c l e T y p e ( ) ; 7 i f ( $formrequest >h a n d l e ( $type, $ a r t i c l e ) ) { 8 $ t h i s >r e p o s i t o r y >s a v e ( $ a r t i c l e ) ; 9 10 $ f l a s h e s >add ( n o t i c e,... ) ; 11 12 return new RedirectRouteResponse ( /.. / ) ; 13 } 14 15 r e t u r n a r r a y ( a r t i c l e => $ a r t i c l e, 16 form => $formrequest >createview ( ) ) ; 17 }

Why? No service-layer needed in the beginning Treat contoller as service layer Refactoring towards services is simple Extract code into services Very small number of service dependencies Testable controllers

Testing? 1 p u b l i c f u n c t i o n t e s t E d i t ( ) 2 { 3 $repo = $ t h i s >getmock ( A r t i c l e R e p o s i t o r y : : c l a s s ) ; 4 $repo >e x p e c t s ( $ t h i s >once ( ) ) >method ( f i n d ) 5 > w i l l ( $ t h i s >r e t u r n V a l u e ( new A r t i c l e ) ) ; 6 $repo >expects ( $this >once ( ) ) >method ( save ) ; 7 8 $ c o n t r o l l e r = new A r t i c l e C o n t r o l l e r ( $repo ) ; 9 10 $ r e s p o n s e = $ c o n t r o l l e r >e d i t A c t i o n ( 11 $ a r t i c l e I d = 10, 12 new ValidFormRequest ( ), 13 new GrantAllContext ( ), 14 new FlashMock ( ) 15 ) ; 16 17 $ t h i s >a s s e r t I n s t a n c e O f ( R e d i r e c t R o u t e R e s p o n s e : : c l a s s, $ r e s p o n s e ) ; 18 }

https://github.com/qafoolabs/qafoolabsnoframeworkbundle