Top Banner
How Kris Writes Symfony Apps
75

How kris-writes-symfony-apps-london

Dec 02, 2014

Download

Technology

Kris Wallsmith

You've seen Kris' open source libraries, but how does he tackle coding out an application? Walk through green fields with a Symfony expert as he takes his latest “next big thing” idea from the first line of code to a functional prototype. Learn design patterns and principles to guide your way in organizing your own code and take home some practical examples to kickstart your next project.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: How kris-writes-symfony-apps-london

How Kris Writes Symfony Apps

Page 2: How kris-writes-symfony-apps-london

Mapping Layers

Page 3: How kris-writes-symfony-apps-london

thin

Page 4: How kris-writes-symfony-apps-london

thin controller fat model

Page 5: How kris-writes-symfony-apps-london

MVC

Page 6: How kris-writes-symfony-apps-london

Is Symfony an MVC framework?

Page 7: How kris-writes-symfony-apps-london

HTTP

Page 8: How kris-writes-symfony-apps-london

Symfony is an HTTP framework

Page 9: How kris-writes-symfony-apps-london

HT

TP Land

Application Land

Controller

Page 10: How kris-writes-symfony-apps-london

The controller is thin because it maps from

HTTP-land to application-land.

Page 11: How kris-writes-symfony-apps-london

What about the model?

Page 12: How kris-writes-symfony-apps-london

Application Land

Persistence Land

Model

Page 13: How kris-writes-symfony-apps-london

The model maps from application-land to persistence-land.

Page 14: How kris-writes-symfony-apps-london

Model

Application Land

Persistence Land

HT

TP Land

Controller

Page 15: How kris-writes-symfony-apps-london

Who lives in application land?

Page 16: How kris-writes-symfony-apps-london

Thin controller, thin model… Fat service layer

Page 17: How kris-writes-symfony-apps-london

Should there be managers?

Page 18: How kris-writes-symfony-apps-london

Application Events

Page 19: How kris-writes-symfony-apps-london

Listen

Page 20: How kris-writes-symfony-apps-london

/** @DI\Observe(UserEvent::CREATE) */public function onUserCreate(UserEvent $event){ $user = $event->getUser();

$activity = new Activity(); $activity->setActor($user); $activity->setVerb('register'); $activity->setCreatedAt($user->getCreatedAt());

$this->dm->persist($activity);}

Page 21: How kris-writes-symfony-apps-london

/** @DI\Observe(UserEvent::USERNAME_CHANGE) */public function onUsernameChange(UserEvent $event){ $user = $event->getUser(); $dm = $event->getDocumentManager();

$dm->getRepository('Model:Widget') ->updateDenormalizedUsernames($user);}

Page 22: How kris-writes-symfony-apps-london

/** @DI\Observe(UserEvent::FOLLOW) */public function onFollow(UserUserEvent $event){ $event->getUser() ->getStats() ->incrementFollowedUsers(1); $event->getOtherUser() ->getStats() ->incrementFollowers(1);}

Page 23: How kris-writes-symfony-apps-london

Dispatch

Page 24: How kris-writes-symfony-apps-london

$event = new UserEvent($dm, $user);$dispatcher->dispatch(UserEvent::CREATE, $event);

Page 25: How kris-writes-symfony-apps-london

$event = new UserEvent($dm, $user);$dispatcher->dispatch(UserEvent::UPDATE, $event);

Page 26: How kris-writes-symfony-apps-london

$event = new UserUserEvent($dm, $user, $otherUser);$dispatcher->dispatch(UserEvent::FOLLOW, $event);

Page 27: How kris-writes-symfony-apps-london

preFlush

Page 28: How kris-writes-symfony-apps-london

public function preFlush(ManagerEventArgs $event){ $dm = $event->getObjectManager(); $uow = $dm->getUnitOfWork();

foreach ($uow->getIdentityMap() as $class => $docs) { if (is_a($class, 'Kris\Model\User')) { foreach ($docs as $doc) { $this->processUserFlush($dm, $doc); } } elseif (is_a($class, 'Kris\Model\Widget')) { foreach ($docs as $doc) { $this->processWidgetFlush($dm, $doc); } } }}

Page 29: How kris-writes-symfony-apps-london

Decouple your application by delegating work to clean, concise,

single-purpose event listeners.

Page 30: How kris-writes-symfony-apps-london

Model

Page 31: How kris-writes-symfony-apps-london

Treat your model like a princess.

Page 32: How kris-writes-symfony-apps-london

She gets her own wing of the palace…

Page 33: How kris-writes-symfony-apps-london

doctrine_mongodb: auto_generate_hydrator_classes: %kernel.debug% auto_generate_proxy_classes: %kernel.debug% connections: { default: ~ } document_managers: default: connection: default database: kris mappings: model: type: annotation dir: %src%/Kris/Model prefix: Kris\Model alias: Model

Page 34: How kris-writes-symfony-apps-london

// repo for src/Kris/Model/User.php$repo = $this->dm->getRepository('Model:User');

Page 35: How kris-writes-symfony-apps-london

…doesn't do any work…

Page 36: How kris-writes-symfony-apps-london

use Kris\Bundle\MainBundle\Canonicalizer;

public function setUsername($username){ $this->username = $username;

$canonicalizer = Canonicalizer::instance(); $this->usernameCanonical = $canonicalizer->canonicalize($username);}

Page 37: How kris-writes-symfony-apps-london

use Kris\Bundle\MainBundle\Canonicalizer;

public function setUsername($username, Canonicalizer $canonicalizer){ $this->username = $username; $this->usernameCanonical = $canonicalizer->canonicalize($username);}

Page 38: How kris-writes-symfony-apps-london

…and is unaware of the work being done around her.

Page 39: How kris-writes-symfony-apps-london

public function setUsername($username){ // a listener will update the // canonical username $this->username = $username;}

Page 40: How kris-writes-symfony-apps-london

Cabinets don’t open themselves.

Page 41: How kris-writes-symfony-apps-london

Contextual Configuration

Page 42: How kris-writes-symfony-apps-london

Save your future self a headache.

Page 43: How kris-writes-symfony-apps-london

# @MainBundle/Resources/config/widget.ymlservices: widget_twiddler: class: Kris\Bundle\MainBundle\Widget\Twiddler arguments: - @event_dispatcher - @?logger

Page 44: How kris-writes-symfony-apps-london

JMSDiExtraBundle

Page 45: How kris-writes-symfony-apps-london

/** @DI\Service("widget_twiddler") */class Twiddler{ /** @DI\InjectParams() */ public function __construct( EventDispatcherInterface $dispatcher, LoggerInterface $logger = null) { // ... }}

Page 46: How kris-writes-symfony-apps-london

services: # aliases for auto-wiring container: @service_container dm: @doctrine_mongodb.odm.document_manager doctrine: @doctrine_mongodb dispatcher: @event_dispatcher security: @security.context

Page 47: How kris-writes-symfony-apps-london

require.js

Page 48: How kris-writes-symfony-apps-london

{% block head %}<script>require( [ "view/user", "model/user" ], function(UserView, User) { var view = new UserView({ model: new User({{ user|serialize|raw }}), el: document.getElementById("user") }) })</script>{% endblock %}

Page 49: How kris-writes-symfony-apps-london

JMSSerializerBundle

Page 50: How kris-writes-symfony-apps-london

{% block head %}<script>require( [ "view/user", "model/user" ], function(UserView, User) { var view = new UserView({ model: new User({{ user|serialize|raw }}), el: document.getElementById("user") }) })</script>{% endblock %}

Page 51: How kris-writes-symfony-apps-london

/** @ExclusionPolicy("ALL") */class User{ private $id;

/** @Expose() */ private $firstName;

/** @Expose() */ private $lastName;}

Page 52: How kris-writes-symfony-apps-london

Five more things…

Page 53: How kris-writes-symfony-apps-london

When to create a new bundle

• Anything reusable

• A new feature

• Lots of classes relating to one feature

• Integration with a third party

Page 54: How kris-writes-symfony-apps-london

{% include 'MainBundle:Account/Widget:sidebar.html.twig' %}

Page 55: How kris-writes-symfony-apps-london

{% include 'AccountBundle:Widget:sidebar.html.twig' %}

Page 56: How kris-writes-symfony-apps-london

Access Control

Page 57: How kris-writes-symfony-apps-london

The Symfony ACL is for arbitrary permissions

Page 58: How kris-writes-symfony-apps-london
Page 59: How kris-writes-symfony-apps-london

Encapsulate access logic in custom voter classes

Page 60: How kris-writes-symfony-apps-london

public function vote(TokenInterface $token, $widget, array $attributes){ $result = VoterInterface::ACCESS_ABSTAIN; if (!$this->supportsClass(get_class($widget))) { return $result; }

foreach ($attributes as $attribute) { if (!$this->supportsAttribute($attribute)) { continue; }

$result = VoterInterface::ACCESS_DENIED; if ($token->getUser() === $widget->getUser()) { return VoterInterface::ACCESS_GRANTED; } }

return $result;}

Page 61: How kris-writes-symfony-apps-london

JMSSecurityExtraBundle

Page 62: How kris-writes-symfony-apps-london

/** @SecureParam(name="widget", permissions="OWNER") */public function editAction(Widget $widget){ // ...}

Page 63: How kris-writes-symfony-apps-london

{% if is_granted('OWNER', widget) %}{# ... #}{% endif %}

Page 64: How kris-writes-symfony-apps-london

No query builders outside of repositories

Page 65: How kris-writes-symfony-apps-london

class WidgetRepository extends DocumentRepository{ public function findByUser(User $user) { return $this->createQueryBuilder() ->field('userId')->equals($user->getId()) ->getQuery() ->execute(); }

public function updateDenormalizedUsernames(User $user) { $this->createQueryBuilder() ->update() ->multiple() ->field('userId')->equals($user->getId()) ->field('userName')->set($user->getUsername()) ->getQuery() ->execute(); }}

Page 66: How kris-writes-symfony-apps-london

Eager ID creation

Page 67: How kris-writes-symfony-apps-london

public function __construct(){ $this->id = (string) new \MongoId();}

Page 68: How kris-writes-symfony-apps-london

public function __construct(){ $this->id = (string) new \MongoId(); $this->createdAt = new \DateTime(); $this->widgets = new ArrayCollection();}

Page 69: How kris-writes-symfony-apps-london

Remember your clone constructor

Page 70: How kris-writes-symfony-apps-london

$foo = new Foo();$bar = clone $foo;

Page 71: How kris-writes-symfony-apps-london

public function __clone(){ $this->id = (string) new \MongoId(); $this->createdAt = new \DateTime(); $this->widgets = new ArrayCollection( $this->widgets->toArray() );}

Page 72: How kris-writes-symfony-apps-london

public function __construct(){ $this->id = (string) new \MongoId(); $this->createdAt = new \DateTime(); $this->widgets = new ArrayCollection();}

public function __clone(){ $this->id = (string) new \MongoId(); $this->createdAt = new \DateTime(); $this->widgets = new ArrayCollection( $this->widgets->toArray() );}

Page 73: How kris-writes-symfony-apps-london

Only flush from the controller

Page 74: How kris-writes-symfony-apps-london

public function theAction(Widget $widget){ $this->get('widget_twiddler') ->skeedaddle($widget);

$this->flush();}

Page 75: How kris-writes-symfony-apps-london

Questions?