Advertisement

How kris-writes-symfony-apps-london

Oct. 1, 2014
Advertisement

More Related Content

Advertisement
Advertisement

How kris-writes-symfony-apps-london

  1. How Kris Writes Symfony Apps
  2. Mapping Layers
  3. thin
  4. thin controller fat model
  5. MVC
  6. Is Symfony an MVC framework?
  7. HTTP
  8. Symfony is an HTTP framework
  9. Application Land Controller HTTP Land
  10. The controller is thin because it maps from HTTP-land to application-land.
  11. What about the model?
  12. Persistence Land Model Application Land
  13. The model maps from application-land to persistence-land.
  14. Persistence Land Model Application Land Controller HTTP Land
  15. Who lives in application land?
  16. Thin controller , thin model… Fat service layer
  17. Should there be managers?
  18. Application Events
  19. Listen
  20. /** @DIObserve(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); }
  21. /** @DIObserve(UserEvent::USERNAME_CHANGE) */ public function onUsernameChange(UserEvent $event) { $user = $event->getUser(); $dm = $event->getDocumentManager(); $dm->getRepository('Model:Widget') ->updateDenormalizedUsernames($user); }
  22. /** @DIObserve(UserEvent::FOLLOW) */ public function onFollow(UserUserEvent $event) { $event->getUser() ->getStats() ->incrementFollowedUsers(1); $event->getOtherUser() ->getStats() ->incrementFollowers(1); }
  23. Dispatch
  24. $event = new UserEvent($dm, $user); $dispatcher->dispatch(UserEvent::CREATE, $event);
  25. $event = new UserEvent($dm, $user); $dispatcher->dispatch(UserEvent::UPDATE, $event);
  26. $event = new UserUserEvent($dm, $user, $otherUser); $dispatcher->dispatch(UserEvent::FOLLOW, $event);
  27. preFlush
  28. public function preFlush(ManagerEventArgs $event) { $dm = $event->getObjectManager(); $uow = $dm->getUnitOfWork(); foreach ($uow->getIdentityMap() as $class => $docs) { if (is_a($class, 'KrisModelUser')) { foreach ($docs as $doc) { $this->processUserFlush($dm, $doc); } } elseif (is_a($class, 'KrisModelWidget')) { foreach ($docs as $doc) { $this->processWidgetFlush($dm, $doc); } } } }
  29. Decouple your application by delegating work to clean, concise, single-purpose event listeners.
  30. Model
  31. Treat your model like a princess.
  32. She gets her own wing of the palace…
  33. 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: KrisModel alias: Model
  34. // repo for src/Kris/Model/User.php $repo = $this->dm->getRepository('Model:User');
  35. …doesn't do any work…
  36. use KrisBundleMainBundleCanonicalizer; public function setUsername($username) { $this->username = $username; $canonicalizer = Canonicalizer::instance(); $this->usernameCanonical = $canonicalizer->canonicalize($username); }
  37. use KrisBundleMainBundleCanonicalizer; public function setUsername($username, Canonicalizer $canonicalizer) { $this->username = $username; $this->usernameCanonical = $canonicalizer->canonicalize($username); }
  38. …and is unaware of the work being done around her.
  39. public function setUsername($username) { // a listener will update the // canonical username $this->username = $username; }
  40. Cabinets don’t open themselves.
  41. Contextual Configuration
  42. Save your future self a headache.
  43. # @MainBundle/Resources/config/widget.yml services: widget_twiddler: class: KrisBundleMainBundleWidgetTwiddler arguments: - @event_dispatcher - @?logger
  44. JMSDiExtraBundle
  45. /** @DIService("widget_twiddler") */ class Twiddler { /** @DIInjectParams() */ public function __construct( EventDispatcherInterface $dispatcher, LoggerInterface $logger = null) { // ... } }
  46. services: # aliases for auto-wiring container: @service_container dm: @doctrine_mongodb.odm.document_manager doctrine: @doctrine_mongodb dispatcher: @event_dispatcher security: @security.context
  47. require.js
  48. {% 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 %}
  49. JMSSerializerBundle
  50. {% 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 %}
  51. /** @ExclusionPolicy("ALL") */ class User { private $id; /** @Expose() */ private $firstName; /** @Expose() */ private $lastName; }
  52. Five more things…
  53. When to create a new bundle • Anything reusable • A new feature • Lots of classes relating to one feature • Integration with a third party
  54. {% include 'MainBundle:Account/Widget:sidebar.html.twig' %}
  55. {% include 'AccountBundle:Widget:sidebar.html.twig' %}
  56. Access Control
  57. The Symfony ACL is for arbitrary permissions
  58. Encapsulate access logic in custom voter classes
  59. 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; }
  60. JMSSecurityExtraBundle
  61. /** @SecureParam(name="widget", permissions="OWNER") */ public function editAction(Widget $widget) { // ... }
  62. {% if is_granted('OWNER', widget) %} {# ... #} {% endif %}
  63. No query builders outside of repositories
  64. 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(); } }
  65. Eager ID creation
  66. public function __construct() { $this->id = (string) new MongoId(); }
  67. public function __construct() { $this->id = (string) new MongoId(); $this->createdAt = new DateTime(); $this->widgets = new ArrayCollection(); }
  68. Remember your clone constructor
  69. $foo = new Foo(); $bar = clone $foo;
  70. public function __clone() { $this->id = (string) new MongoId(); $this->createdAt = new DateTime(); $this->widgets = new ArrayCollection( $this->widgets->toArray() ); }
  71. 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() ); }
  72. Only flush from the controller
  73. public function theAction(Widget $widget) { $this->get('widget_twiddler') ->skeedaddle($widget); $this->flush(); }
  74. Questions?
Advertisement