how kris writes symfony apps
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.TRANSCRIPT
![Page 1: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/1.jpg)
How Kris Writes Symfony Apps
![Page 2: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/2.jpg)
@kriswallsmith
![Page 3: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/3.jpg)
father artist bowhunter hacker president widower gamer actor tapdancer lover hater singer writer founder yogi consultant archer musician architect slacker soccer player volunteer home owner scotch drinker pianist…
![Page 4: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/4.jpg)
assetic
![Page 5: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/5.jpg)
Buzz
![Page 6: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/6.jpg)
Spork
![Page 7: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/7.jpg)
![Page 8: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/8.jpg)
Getting Started
![Page 9: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/9.jpg)
composer create-project \! symfony/framework-standard-edition \! widgets-by-kris/ \! ~2.4
![Page 10: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/10.jpg)
- "doctrine/orm": "~2.2,>=2.2.3",!- "doctrine/doctrine-bundle": "~1.2",!+ "doctrine/mongodb-odm-bundle": "~3.0",!+ "jms/di-extra-bundle": "~1.4",!+ "jms/security-extra-bundle": "~1.5",!+ "jms/serializer-bundle": "~1.0",
![Page 11: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/11.jpg)
./app/console generate:bundle \! --namespace=Kris/Bundle/MainBundle
![Page 12: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/12.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 13: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/13.jpg)
Model
![Page 14: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/14.jpg)
Treat your model like a princess.
![Page 15: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/15.jpg)
She gets her own wing of the palace…
![Page 16: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/16.jpg)
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 17: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/17.jpg)
// repo for src/Kris/Model/Widget.php!$repo = $this->dm->getRepository('Model:User');
![Page 18: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/18.jpg)
…doesn't do any work…
![Page 19: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/19.jpg)
use Kris\Bundle\MainBundle\Canonicalizer;!!public function setUsername($username)!{! $this->username = $username;!! $canonicalizer = Canonicalizer::instance();! $this->usernameCanonical = $canonicalizer->canonicalize($username);!}
![Page 20: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/20.jpg)
use Kris\Bundle\MainBundle\Canonicalizer;!!public function setUsername($username, Canonicalizer $canonicalizer)!{! $this->username = $username;! $this->usernameCanonical = $canonicalizer->canonicalize($username);!}
![Page 21: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/21.jpg)
…and is unaware of the work being done around her.
![Page 22: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/22.jpg)
public function setUsername($username)!{! // a listener will update the! // canonical username! $this->username = $username;!}
![Page 23: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/23.jpg)
Anemic domain model is an anti-pattern?
![Page 24: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/24.jpg)
Anemic???
![Page 25: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/25.jpg)
“The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design;
which is to combine data and process together.” !
Martin Fowler
![Page 26: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/26.jpg)
$cabinet->open();
![Page 27: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/27.jpg)
Cabinets don’t open themselves.
![Page 28: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/28.jpg)
$asset->getLastModified();
![Page 29: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/29.jpg)
Mapping Layers
![Page 30: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/30.jpg)
thin
![Page 31: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/31.jpg)
thin controller fat model
![Page 32: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/32.jpg)
MVC
![Page 33: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/33.jpg)
Is Symfony an MVC framework?
![Page 34: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/34.jpg)
HTTP
![Page 35: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/35.jpg)
HT
TP Land
Application Land
Controller
![Page 36: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/36.jpg)
The controller is thin because it maps from
HTTP-land to application-land.
![Page 37: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/37.jpg)
What about the model?
![Page 38: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/38.jpg)
![Page 39: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/39.jpg)
![Page 40: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/40.jpg)
public function registerAction()!{! // ...! $user->sendWelcomeEmail();! // ...!}
![Page 41: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/41.jpg)
public function registerAction()!{! // ...! $mailer->sendWelcomeEmail($user);! // ...!}
![Page 42: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/42.jpg)
Application Land
Persistence Land
Model
![Page 43: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/43.jpg)
The model maps from application-land to persistence-land.
![Page 44: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/44.jpg)
Model
Application Land
Persistence Land
HT
TP Land
Controller
![Page 45: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/45.jpg)
Who lives in application land?
![Page 46: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/46.jpg)
Thin controller, thin model… Fat service layer!
![Page 47: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/47.jpg)
Application Events
![Page 48: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/48.jpg)
Use them.
![Page 49: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/49.jpg)
That happened.
![Page 50: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/50.jpg)
/** @DI\Observe("user.username_change") */!public function onUsernameChange(UserEvent $event)!{! $user = $event->getUser();! $dm = $event->getDocumentManager();!! $dm->getRepository('Model:Widget')! ->updateDenormalizedUsernames($user);!}
![Page 51: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/51.jpg)
One event class per model
• Event name constants
• Accepts object manager and object as arguments
• Simple accessors
![Page 52: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/52.jpg)
$event = new UserEvent($dm, $user);!$dispatcher->dispatch(UserEvent::CREATE, $event);
![Page 53: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/53.jpg)
$event = new UserUserEvent($dm, $user, $otherUser);!$dispatcher->dispatch(UserEvent::FOLLOW, $event);
![Page 54: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/54.jpg)
preFlush
![Page 55: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/55.jpg)
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 56: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/56.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 57: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/57.jpg)
/** @DI\Observe("user.follow_user") */!public function onFollowUser(UserUserEvent $event)!{! $event->getUser()! ->getStats()! ->incrementFollowedUsers(1);! $event->getOtherUser()! ->getStats()! ->incrementFollowers(1);!}
![Page 58: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/58.jpg)
Decouple your application by delegating work to clean, concise,
single-purpose event listeners.
![Page 59: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/59.jpg)
Contextual Configuration
![Page 60: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/60.jpg)
Save your future self a headache
![Page 61: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/61.jpg)
# @MainBundle/Resources/config/widget.yml!services:! widget_twiddler:! class: Kris\Bundle\MainBundle\Widget\Twiddler! arguments:! - @event_dispatcher! - @?logger
![Page 62: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/62.jpg)
JMSDiExtraBundle
![Page 63: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/63.jpg)
/** @DI\Service("widget_twiddler") */!class Twiddler!{! /** @DI\InjectParams */! public function __construct(! EventDispatcherInterface $dispatcher,! LoggerInterface $logger = null)! {! // ...! }!}
![Page 64: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/64.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 65: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/65.jpg)
require.js
![Page 66: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/66.jpg)
![Page 67: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/67.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 68: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/68.jpg)
// web/js/model/user.js!define(! [ "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 69: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/69.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 70: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/70.jpg)
Dependencies
• model: backbone, underscore
• view: backbone, jquery
• template: model, view
![Page 71: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/71.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 72: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/72.jpg)
Unused dependencies naturally slough off
![Page 73: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/73.jpg)
JMSSerializerBundle
![Page 74: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/74.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 75: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/75.jpg)
/** @ExclusionPolicy("ALL") */!class User!{! private $id;!!
/** @Expose */! private $firstName;!!
/** @Expose */! private $lastName;!}
![Page 76: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/76.jpg)
Miscellaneous
![Page 77: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/77.jpg)
When to create a new bundle
• Anything reusable
• Lots of classes relating to one feature
• Integration with a third party
![Page 78: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/78.jpg)
{% include 'MainBundle:Account/Widget:sidebar.html.twig' %}
![Page 79: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/79.jpg)
{% include 'AccountBundle:Widget:sidebar.html.twig' %}
![Page 80: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/80.jpg)
Access Control
![Page 81: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/81.jpg)
The Symfony ACL is for arbitrary permissions
![Page 82: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/82.jpg)
![Page 83: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/83.jpg)
Encapsulate access logic in custom voter classes
![Page 84: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/84.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 is_a($class, 'Kris\Model\Widget');! }!! public function vote(TokenInterface $token, $widget, array $attributes)! {! // ...! }!}
![Page 85: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/85.jpg)
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 86: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/86.jpg)
JMSSecurityExtraBundle
![Page 87: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/87.jpg)
/** @SecureParam(name="widget", permissions="OWNER") */!public function editAction(Widget $widget)!{! // ...!}
![Page 88: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/88.jpg)
{% if is_granted('OWNER', widget) %}!{# ... #}!{% endif %}
![Page 89: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/89.jpg)
No query builders outside of repositories
![Page 90: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/90.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 91: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/91.jpg)
Eager ID creation
![Page 92: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/92.jpg)
public function __construct()!{! $this->id = (string) new \MongoId();!}
![Page 93: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/93.jpg)
public function __construct()!{! $this->id = (string) new \MongoId();! $this->createdAt = new \DateTime();! $this->widgets = new ArrayCollection();!}
![Page 94: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/94.jpg)
Remember your clone constructor
![Page 95: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/95.jpg)
$foo = new Foo();!$bar = clone $foo;
![Page 96: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/96.jpg)
public function __clone()!{! $this->id = (string) new \MongoId();! $this->createdAt = new \DateTime();! $this->widgets = new ArrayCollection(! $this->widgets->toArray()! );!}
![Page 97: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/97.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 98: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/98.jpg)
Save space on field names
![Page 99: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/99.jpg)
/** @ODM\String(name="u") */!private $username;!!
/** @ODM\String(name="uc") @ODM\UniqueIndex */!private $usernameCanonical;
![Page 100: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/100.jpg)
Only flush from the controller
![Page 101: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/101.jpg)
public function theAction(Widget $widget)!{! $this->get('widget_twiddler')! ->skeedaddle($widget);! $this->flush();!}
![Page 102: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/102.jpg)
No proxy objects
![Page 103: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/103.jpg)
/** @ODM\ReferenceOne(targetDocument="User") */!private $user;
![Page 104: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/104.jpg)
public function getUser()!{! if ($this->userId && !$this->user) {! throw new UninitializedReferenceException('user');! }!! return $this->user;!}
![Page 105: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/105.jpg)
Questions?
![Page 106: How Kris Writes Symfony Apps](https://reader033.vdocument.in/reader033/viewer/2022052521/540cff077bef0ad7288b8aad/html5/thumbnails/106.jpg)
Thank You!
@kriswallsmith.net
https://joind.in/10371