How Kris Writes Symfony Apps
-
Upload
kris-wallsmith -
Category
Technology
-
view
14.412 -
download
0
description
Transcript of How Kris Writes Symfony Apps
![Page 1: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/1.jpg)
How Kris Writes Symfony Apps@kriswallsmith • February 9, 2013
![Page 2: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/2.jpg)
About Me
![Page 3: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/3.jpg)
• Born, raised, & live in Portland
• 10+ years of experience
• Lead Architect at OpenSky
• Open source fanboy
@kriswallsmith.net
![Page 4: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/4.jpg)
![Page 5: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/5.jpg)
![Page 6: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/6.jpg)
![Page 7: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/7.jpg)
![Page 8: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/8.jpg)
brewcycleportland.com
![Page 9: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/9.jpg)
![Page 10: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/10.jpg)
![Page 11: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/11.jpg)
![Page 12: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/12.jpg)
assetic
![Page 13: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/13.jpg)
Buzz
![Page 14: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/14.jpg)
Spork
![Page 15: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/15.jpg)
![Page 16: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/16.jpg)
Go big or go home.
![Page 17: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/17.jpg)
Getting Started
![Page 18: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/18.jpg)
composer create-project \ symfony/framework-standard-edition \ opti-grab/ 2.2.x-dev
![Page 19: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/19.jpg)
![Page 20: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/20.jpg)
- "doctrine/orm": "~2.2,>=2.2.3",- "doctrine/doctrine-bundle": "1.2.*",+ "doctrine/mongodb-odm-bundle": "3.0.*",+ "jms/serializer-bundle": "1.0.*",
![Page 21: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/21.jpg)
./app/console generate:bundle \ --namespace=OptiGrab/Bundle/MainBundle
![Page 22: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/22.jpg)
assetic: debug: %kernel.debug% use_controller: false bundles: [ MainBundle ] filters: cssrewrite: ~ uglifyjs2: { compress: true, mangle: true } uglifycss: ~
![Page 23: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/23.jpg)
jms_di_extra: locations: bundles: - MainBundle
![Page 24: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/24.jpg)
public function registerContainerConfiguration(LoaderInterface $loader){ $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
// load local_*.yml or local.yml if ( file_exists($file = __DIR__.'/config/local_'.$this->getEnvironment().'.yml') || file_exists($file = __DIR__.'/config/local.yml') ) { $loader->load($file); }}
![Page 25: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/25.jpg)
MongoDB
![Page 26: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/26.jpg)
Treat your model like a princess.
![Page 27: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/27.jpg)
She gets her own wingof the palace…
![Page 28: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/28.jpg)
doctrine_mongodb: auto_generate_hydrator_classes: %kernel.debug% auto_generate_proxy_classes: %kernel.debug% connections: { default: ~ } document_managers: default: connection: default database: optiGrab mappings: model: type: annotation dir: %src_dir%/OptiGrab/Model prefix: OptiGrab\Model alias: Model
![Page 29: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/29.jpg)
// repo for src/OptiGrab/Model/Widget.php$repo = $this->dm->getRepository('Model:User');
![Page 30: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/30.jpg)
…doesn't do any work…
![Page 31: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/31.jpg)
use OptiGrab\Bundle\MainBundle\Canonicalizer;
public function setUsername($username){ $this->username = $username;
$canonicalizer = Canonicalizer::instance(); $this->usernameCanonical = $canonicalizer->canonicalize($username);}
![Page 32: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/32.jpg)
use OptiGrab\Bundle\MainBundle\Canonicalizer;
public function setUsername($username, Canonicalizer $canonicalizer){ $this->username = $username; $this->usernameCanonical = $canonicalizer->canonicalize($username);}
![Page 33: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/33.jpg)
…and is unaware of the work being done around her.
![Page 34: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/34.jpg)
public function setUsername($username){ // a listener will update the // canonical username $this->username = $username;}
![Page 35: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/35.jpg)
No query buildersoutside of repositories
![Page 36: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/36.jpg)
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 37: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/37.jpg)
Eager id creation
![Page 38: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/38.jpg)
public function __construct(){ $this->id = (string) new \MongoId();}
![Page 39: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/39.jpg)
public function __construct(){ $this->id = (string) new \MongoId(); $this->createdAt = new \DateTime(); $this->widgets = new ArrayCollection();}
![Page 40: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/40.jpg)
Remember yourclone constructor
![Page 41: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/41.jpg)
$foo = new Foo();$bar = clone $foo;
![Page 42: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/42.jpg)
public function __clone(){ $this->id = (string) new \MongoId(); $this->createdAt = new \DateTime(); $this->widgets = new ArrayCollection( $this->widgets->toArray() );}
![Page 43: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/43.jpg)
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 44: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/44.jpg)
Only flush from the controller
![Page 45: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/45.jpg)
public function theAction(Widget $widget){ $this->get('widget_twiddler') ->skeedaddle($widget); $this->flush();}
![Page 46: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/46.jpg)
Save space on field names
![Page 47: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/47.jpg)
/** @ODM\String(name="u") */private $username;
/** @ODM\String(name="uc") @ODM\UniqueIndex */private $usernameCanonical;
![Page 48: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/48.jpg)
public function getUsername(){ return $this->username ?: $this->usernameCanonical;}
public function setUsername($username){ if ($username) { $this->usernameCanonical = strtolower($username); $this->username = $username === $this->usernameCanonical ? null : $username; } else { $this->usernameCanonical = null; $this->username = null; }}
![Page 49: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/49.jpg)
No proxy objects
![Page 50: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/50.jpg)
/** @ODM\ReferenceOne(targetDocument="User") */private $user;
![Page 51: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/51.jpg)
public function getUser(){ if ($this->userId && !$this->user) { throw new UninitializedReferenceException('user'); }
return $this->user;}
![Page 52: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/52.jpg)
Mapping Layers
![Page 53: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/53.jpg)
What is a mapping layer?
![Page 54: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/54.jpg)
A mapping layer is thin
![Page 55: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/55.jpg)
Thin controller, fat model…
![Page 56: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/56.jpg)
Is Symfony an MVC framework?
![Page 57: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/57.jpg)
Symfony is an HTTP framework
![Page 58: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/58.jpg)
HT
TP Land
Application Land
Controller
![Page 59: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/59.jpg)
The controller maps fromHTTP-land to application-land.
![Page 60: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/60.jpg)
What about the model?
![Page 61: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/61.jpg)
![Page 62: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/62.jpg)
![Page 63: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/63.jpg)
public function registerAction(){ // ... $user->sendWelcomeEmail(); // ...}
![Page 64: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/64.jpg)
public function registerAction(){ // ... $mailer->sendWelcomeEmail($user); // ...}
![Page 65: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/65.jpg)
Application Land
Persistence Land
Model
![Page 66: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/66.jpg)
The model maps fromapplication-land to persistence-land.
![Page 67: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/67.jpg)
Model
Application Land
Persistence Land
HT
TP Land
Controller
![Page 68: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/68.jpg)
Who lives in application land?
![Page 69: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/69.jpg)
Thin controller, thin model…Fat service layer!
![Page 70: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/70.jpg)
Application Events
![Page 71: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/71.jpg)
Use lots of them
![Page 72: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/72.jpg)
That happened.
![Page 73: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/73.jpg)
/** @DI\Observe("user.username_change") */public function onUsernameChange(UserEvent $event){ $user = $event->getUser(); $dm = $event->getDocumentManager();
$dm->getRepository('Model:Widget') ->updateDenormalizedUsernames($user);}
![Page 74: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/74.jpg)
Unit of Work
![Page 75: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/75.jpg)
public function onFlush(OnFlushEventArgs $event){ $dm = $event->getDocumentManager(); $uow = $dm->getUnitOfWork();
foreach ($uow->getIdentityMap() as $class => $docs) { if (self::checkClass('OptiGrab\Model\User', $class)) { foreach ($docs as $doc) { $this->processUserFlush($dm, $doc); } } elseif (self::checkClass('OptiGrab\Model\Widget', $class)) { foreach ($docs as $doc) { $this->processWidgetFlush($dm, $doc); } } }}
![Page 76: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/76.jpg)
private function processUserFlush(DocumentManager $dm, User $user){ $uow = $dm->getUnitOfWork(); $meta = $dm->getClassMetadata('Model:User'); $changes = $uow->getDocumentChangeSet($user);
if (isset($changes['id'][1])) { $this->dispatcher->dispatch(UserEvents::CREATE, new UserEvent($dm, $user)); }
if (isset($changes['usernameCanonical'][0]) && null !== $changes['usernameCanonical'][0]) { $this->dispatcher->dispatch(UserEvents::USERNAME_CHANGE, new UserEvent($dm, $user)); }
if ($followedUsers = $meta->getFieldValue($user, 'followedUsers')) { foreach ($followedUsers->getInsertDiff() as $otherUser) { $this->dispatcher->dispatch( UserEvents::FOLLOW_USER, new UserUserEvent($dm, $user, $otherUser) ); }
foreach ($followedUsers->getDeleteDiff() as $otherUser) { // ... } }}
![Page 77: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/77.jpg)
/** @DI\Observe("user.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 78: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/78.jpg)
/** @DI\Observe("user.create") */public function onUserCreate(UserEvent $event){ $dm = $event->getDocumentManager(); $user = $event->getUser();
$widget = new Widget(); $widget->setUser($user);
$dm->persist($widget);
// manually notify the event $event->getDispatcher()->dispatch( WidgetEvents::CREATE, new WidgetEvent($dm, $widget) );}
![Page 79: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/79.jpg)
/** @DI\Observe("user.follow_user") */public function onFollowUser(UserUserEvent $event){ $event->getUser() ->getStats() ->incrementFollowedUsers(1); $event->getOtherUser() ->getStats() ->incrementFollowers(1);}
![Page 80: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/80.jpg)
Two event classes per model
• @MainBundle\UserEvents: encapsulates event name constants such as UserEvents::CREATE and UserEvents::CHANGE_USERNAME
• @MainBundle\Event\UserEvent: base event object, accepts $dm and $user arguments
• @MainBundle\WidgetEvents…
• @MainBundle\Event\WidgetEvent…
![Page 81: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/81.jpg)
$event = new UserEvent($dm, $user);$dispatcher->dispatch(UserEvents::CREATE, $event);
![Page 82: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/82.jpg)
Delegate work to clean, concise, single-purpose event listeners
![Page 83: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/83.jpg)
Contextual Configuration
![Page 84: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/84.jpg)
Save your future self a headache
![Page 85: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/85.jpg)
# @MainBundle/Resources/config/widget.ymlservices: widget_twiddler: class: OptiGrab\Bundle\MainBundle\Widget\Twiddler arguments: - @event_dispatcher - @?logger
![Page 86: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/86.jpg)
/** @DI\Service("widget_twiddler") */class Twiddler{ /** @DI\InjectParams */ public function __construct( EventDispatcherInterface $dispatcher, LoggerInterface $logger = null) { // ... }}
![Page 87: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/87.jpg)
services: # aliases for auto-wiring container: @service_container dm: @doctrine_mongodb.odm.document_manager doctrine: @doctrine_mongodb dispatcher: @event_dispatcher security: @security.context
![Page 88: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/88.jpg)
JMSDiExtraBundle
![Page 89: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/89.jpg)
require.js
![Page 90: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/90.jpg)
![Page 91: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/91.jpg)
<script src="{{ asset('js/lib/require.js') }}"></script><script>require.config({ baseUrl: "{{ asset('js') }}", paths: { "jquery": "//ajax.googleapis.com/.../jquery.min", "underscore": "lib/underscore", "backbone": "lib/backbone" }, shim: { "jquery": { exports: "jQuery" }, "underscore": { exports: "_" }, "backbone": { deps: [ "jquery", "underscore" ], exports: "Backbone" } }})require([ "main" ])</script>
![Page 92: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/92.jpg)
// web/js/model/user.jsdefine( [ "underscore", "backbone" ], function(_, Backbone) { var tmpl = _.template("<%- first %> <%- last %>") return Backbone.Model.extend({ name: function() { return tmpl({ first: this.get("first_name"), last: this.get("last_name") }) } }) })
![Page 93: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/93.jpg)
{% 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 94: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/94.jpg)
Dependencies
• model: backbone, underscore
• view: backbone, jquery
• template: model, view
![Page 95: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/95.jpg)
{% javascripts "js/lib/jquery.js" "js/lib/underscore.js" "js/lib/backbone.js" "js/model/user.js" "js/view/user.js" filter="?uglifyjs2" output="js/packed/user.js" %}<script src="{{ asset_url }}"></script>{% endjavascripts %}
<script>var view = new UserView({ model: new User({{ user|serialize|raw }}), el: document.getElementById("user")})</script>
![Page 96: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/96.jpg)
Unused dependenciesnaturally slough off
![Page 97: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/97.jpg)
JMSSerializerBundle
![Page 98: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/98.jpg)
{% 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 99: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/99.jpg)
/** @ExclusionPolicy("ALL") */class User{ private $id;
/** @Expose */ private $firstName;
/** @Expose */ private $lastName;}
![Page 100: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/100.jpg)
Miscellaneous
![Page 101: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/101.jpg)
When to create a new bundle
![Page 102: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/102.jpg)
Lots of classes pertaining toone feature
![Page 103: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/103.jpg)
{% include 'MainBundle:Account/Widget:sidebar.html.twig' %}
![Page 104: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/104.jpg)
{% include 'AccountBundle:Widget:sidebar.html.twig' %}
![Page 105: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/105.jpg)
Access Control
![Page 106: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/106.jpg)
The Symfony ACL is forarbitrary permissions
![Page 107: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/107.jpg)
Encapsulate access logic incustom voter classes
![Page 108: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/108.jpg)
/** @DI\Service(public=false) @DI\Tag("security.voter") */class WidgetVoter implements VoterInterface{ public function supportsAttribute($attribute) { return 'OWNER' === $attribute; }
public function supportsClass($class) { return 'OptiGrab\Model\Widget' === $class || is_subclass_of($class, 'OptiGrab\Model\Widget'); }
public function vote(TokenInterface $token, $widget, array $attributes) { // ... }}
![Page 109: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/109.jpg)
public function vote(TokenInterface $token, $map, array $attributes){ $result = VoterInterface::ACCESS_ABSTAIN;
if (!$this->supportsClass(get_class($map))) { return $result; }
foreach ($attributes as $attribute) { if (!$this->supportsAttribute($attribute)) { continue; }
$result = VoterInterface::ACCESS_DENIED; if ($token->getUser() === $map->getUser()) { return VoterInterface::ACCESS_GRANTED; } }
return $result;}
![Page 110: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/110.jpg)
/** @SecureParam(name="widget", permissions="OWNER") */public function editAction(Widget $widget){ // ...}
![Page 111: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/111.jpg)
{% if is_granted('OWNER', widget) %}{# ... #}{% endif %}
![Page 112: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/112.jpg)
Only mock interfaces
![Page 113: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/113.jpg)
interface FacebookInterface{ function getUser(); function api();}
/** @DI\Service("facebook") */class Facebook extends \BaseFacebook implements FacebookInterface{ // ...}
![Page 114: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/114.jpg)
$facebook = $this->getMock('OptiGrab\Bundle\MainBundle\Facebook\FacebookInterface');$facebook->expects($this->any()) ->method('getUser') ->will($this->returnValue(123));
![Page 115: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/115.jpg)
Questions?
![Page 116: How Kris Writes Symfony Apps](https://reader034.fdocuments.us/reader034/viewer/2022052522/554a0632b4c905e56c8b566e/html5/thumbnails/116.jpg)
Thank You!
joind.in/8024
@kriswallsmith.net