75
How Kris Writes Symfony Apps

How kris-writes-symfony-apps-london

Embed Size (px)

DESCRIPTION

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.

Citation preview

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?