Matters of State

10.914 Aufrufe

Veröffentlicht am

If you have used Facebook's React library, then you are familiar with the concept of application state. React components are, at their core (and as noted in the official documentation), simple state machines. This declarative approach to building a UI may take some adjusting to, but it ultimately simplifies kludgy imperative code into smaller, much more manageable pieces.

This pattern of manipulating state and responding to those changes can be implemented to great effect using the Symfony Event Dispatcher. This talk will step through this state-based approach to building an easily maintained and testable PHP application, tease out a few gotchas, and share real-world applications.

Veröffentlicht in: Internet
0 Kommentare
8 Gefällt mir
Statistik
Notizen
  • Als Erste(r) kommentieren

Keine Downloads
Aufrufe
Aufrufe insgesamt
10.914
Auf SlideShare
0
Aus Einbettungen
0
Anzahl an Einbettungen
6.471
Aktionen
Geteilt
0
Downloads
65
Kommentare
0
Gefällt mir
8
Einbettungen 0
Keine Einbettungen

Keine Notizen für die Folie

Matters of State

  1. 1. Matters of State Using Symfony’s Event Dispatcher to 
 turn your application into a simple state machine
  2. 2. Photo by jeff_golden Bon anniversaire Symfony!
  3. 3. About me • Portland • twitter.com/kriswallsmith • github.com/kriswallsmith • kriswallsmith.net
  4. 4. React Nicolas Raymond
  5. 5. var HelloWorld = React.createClass({ render: function() { return ( <div>Hello World</div> ) } })
  6. 6. var HelloWorld = React.createClass({ render: function() { return ( <div>Hello World</div> ) } }) JSX
  7. 7. Just the view
  8. 8. (there is no MVC to violate)
  9. 9. One-way data flow
  10. 10. (everything flows from state)
  11. 11. Virtual DOM
  12. 12. (DOM changesets)
  13. 13. “React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent.” https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#components-are-just-state-machines
  14. 14. var Analytics = React.createClass({ getInitialState: function() { return { clicks: 0 } }, incrementClicks: function() { var clicks = this.state.clicks; this.setState({ clicks: ++clicks }); }, render: function() { var clicks = this.state.clicks; return ( <button onClick={this.incrementClicks}> {clicks} click{clicks == 1 ? '' : 's'} </button> ) } })
  15. 15. var Analytics = React.createClass({ getInitialState: function() { return { clicks: 0 } }, incrementClicks: function() { var clicks = this.state.clicks; this.setState({ clicks: ++clicks }); }, render: function() { var clicks = this.state.clicks; return ( <button onClick={this.incrementClicks}> {clicks} click{clicks == 1 ? '' : 's'} </button> ) } })
  16. 16. <button id="click_button"> 0 click(s) </button>
  17. 17. $('#my_button').click(function() { var $btn = $(this); var clicks = $btn.text().match(/d+/)[0]; var text = clicks + ' click'; if (clicks != 1) text += 's'; $btn.text(text); })
  18. 18. $('#my_button').click(function() { var $btn = $(this); var clicks = $btn.text().match(/d+/)[0]; var text = clicks + ' click'; if (clicks != 1) text += 's'; $btn.text(text); }) oops…
  19. 19. var Analytics = React.createClass({ getInitialState: function() { return { clicks: 0 } }, incrementClicks: function() { var clicks = this.state.clicks; this.setState({ clicks: ++clicks }); }, render: function() { var clicks = this.state.clicks; return ( <button onClick={this.incrementClicks}> {clicks} click{clicks == 1 ? '' : 's'} </button> ) } })
  20. 20. Paradigm shift
  21. 21. Imperative vs. Declarative Photo by
  22. 22. Imperative programming
  23. 23. (code that explains how to reach your goal)
  24. 24. Declarative programming
  25. 25. (code that describes what your goal is)
  26. 26. Take the next right • Depress the gas pedal and increase your speed to 10mph • Engage your right turn signal • As you approach the intersection, remove your foot from the gas pedal and gently apply the brake • Check for pedestrians crossing the intersection • Check your mirrors and blind spot for bicyclists • Lift your foot from the brake and slowly increase speed by depressing the gas pedal • Turn the steering wheel to the right
  27. 27. Take the next right • Depress the gas pedal and increase your speed to 10mph • Engage your right turn signal • As you approach the intersection, remove your foot from the gas pedal and gently apply the brake • Check for pedestrians crossing the intersection • Check your mirrors and blind spot for bicyclists • Lift your foot from the brake and slowly increase speed by depressing the gas pedal • Turn the steering wheel to the right declarative
  28. 28. Take the next right • Depress the gas pedal and increase your speed to 10mph • Engage your right turn signal • As you approach the intersection, remove your foot from the gas pedal and gently apply the brake • Check for pedestrians crossing the intersection • Check your mirrors and blind spot for bicyclists • Lift your foot from the brake and slowly increase speed by depressing the gas pedal • Turn the steering wheel to the right imperative
  29. 29. $('#my_button').click(function() { var $btn = $(this); var clicks = $btn.text().match(/d+/)[0]; var text = clicks + ' click'; if (clicks != 1) text += 's'; $btn.text(text); })
  30. 30. var Analytics = React.createClass({ getInitialState: function() { return { clicks: 0 } }, incrementClicks: function() { var clicks = this.state.clicks; this.setState({ clicks: ++clicks }); }, render: function() { var clicks = this.state.clicks; return ( <button onClick={this.incrementClicks}> {clicks} click{clicks == 1 ? '' : 's'} </button> ) } })
  31. 31. How vs. What
  32. 32. SELECT * FROM users WHERE username="kriswallsmith";
  33. 33. PHP controllers
  34. 34. public function registerAction(Request $request) { $form = $this->createForm('user'); $form->handleRequest($request); if ($form->isValid()) { $user = $form->getData(); $message = Swift_Message::newInstance() ->setTo($user->getEmail()) ->setSubject('Welcome!') ->setBody($this->renderView( 'AppBundle:Email:welcome.html.twig', ['user' => $user] )); $mailer = $this->get('mailer'); $mailer->send($message); $em = $this->get('doctrine')->getManager(); $em->persist($user); $em->flush(); return $this->redirectToRoute('home'); } return $this->render( 'AppBundle:User:register.html.twig', ['form' => $form->createView()] ); }
  35. 35. public function registerAction(Request $request) { $form = $this->createForm('user'); $form->handleRequest($request); if ($form->isValid()) { $user = $form->getData(); $mailer = $this->get('app.mailer'); $mailer->sendWelcomeEmail($user); $em = $this->get('doctrine')->getManager(); $em->persist($user); $em->flush(); return $this->redirectToRoute('home'); } return $this->render( 'AppBundle:User:register.html.twig', ['form' => $form->createView()] ); }
  36. 36. public function registerAction(Request $request) { $form = $this->createForm('user'); $form->handleRequest($request); if ($form->isValid()) { $user = $form->getData(); $mailer = $this->get('app.mailer'); $mailer->sendWelcomeEmail($user); $em = $this->get('doctrine')->getManager(); $em->persist($user); $em->flush(); return $this->redirectToRoute('home'); } return $this->render( 'AppBundle:User:register.html.twig', ['form' => $form->createView()] ); } declarative!
  37. 37. Why stop there?
  38. 38. public function registerAction(Request $request) { $form = $this->createForm('user'); $form->handleRequest($request); if ($form->isValid()) { $em = $this->get('doctrine')->getManager(); $em->persist($form->getData()); $em->flush(); return $this->redirectToRoute('home'); } return $this->render( 'AppBundle:User:register.html.twig', ['form' => $form->createView()] ); }
  39. 39. Excuse me Kris,
 but what about the email?
  40. 40. Photo by
  41. 41. Event Dispatcher Photo by rhodesj
  42. 42. The Event Dispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them. symfony.com
  43. 43. public function addListener( $eventName, $listener, $priority = 0 ); public function dispatch( $eventName, Event $event = null );
  44. 44. $dispatcher->dispatch('user.create');
  45. 45. $dispatcher->dispatch( 'user.create', new UserEvent($user) );
  46. 46. $dispatcher->dispatch( UserEvents::CREATE, new UserEvent($user) );
  47. 47. $dispatcher->addListener( UserEvents::CREATE, function(UserEvent $event) { echo "Welcome, {$event->getUserName()}!"; } );
  48. 48. $dispatcher->addListener( UserEvents::CREATE, [$listener, 'onUserCreate'] );
  49. 49. Something just happened or is about to happen
  50. 50. Photo by Simon Law Reactive Listeners
  51. 51. class UserListener { public function onUserCreate(UserEvent $event) { $user = $event->getUser(); $this->mailer->sendWelcomeEmail($user); } }
  52. 52. class ActivityFeedListener { public function onUserCreate(UserEvent $event) { $user = $event->getUser(); $this->feed->logRegistration($user); } }
  53. 53. class ActivityFeedListener { public function onFollow(UserUserEvent $event) { $user = $event->getUser(); $otherUser = $event->getOtherUser(); $this->feed->logFollow($user, $otherUser); } }
  54. 54. Testable
  55. 55. Reusable
  56. 56. Readable
  57. 57. State Events
  58. 58. Where do they come from?
  59. 59. Option 1: Controller dispatch
  60. 60. public function registerAction(Request $request) { $form = $this->createForm('user'); $form->handleRequest($request); if ($form->isValid()) { $user = $form->getData(); $dispatcher = $this->get('event_dispatcher'); $dispatcher->dispatch(UserEvents::CREATE, new UserEvent($user)); $em = $this->get('doctrine')->getManager(); $em->persist(); $em->flush(); return $this->redirectToRoute('home'); } return $this->render( 'AppBundle:Register:register.html.twig', ['form' => $form->createView()] ); }
  61. 61. Option 2: Service dispatch
  62. 62. class UserManager { public function persistUser(User $user) { $this->em->persist($user); $this->dispatcher->dispatch( UserEvents::CREATE, new UserEvent($user) ); } }
  63. 63. Option 3: Flush dispatch
  64. 64. class FlushListener { public function preFlush(PreFlushEventArgs $event) { $em = $event->getEntityManager(); $uow = $em->getUnitOfWork(); foreach ($uow->getIdentityMap() as $class => $objects) { if (User::class === $class) { foreach ($objects as $user) { if ($uow->isScheduledForInsert($user)) { $this->dispatcher->dispatch( UserEvents::CREATE, new UserEvent($user) ); } } } } } }
  65. 65. a simple state machine
  66. 66. Cool, but…
  67. 67. That flush listener is ugly…
  68. 68. …and imperative!
  69. 69. What if… Photo by Jared Cherup What if…
  70. 70. /** @OnCreate(UserEvents::CREATE) */ class User { }
  71. 71. /** @OnChange(UserEvents::CHANGE) */ class User { }
  72. 72. /** @OnDestroy(UserEvents::DESTROY) */ class User { }
  73. 73. /** * @OnCreate(UserEvents::CREATE) * @OnUpdate(UserEvents::UPDATE) * @OnDestroy(UserEvents::DESTROY) */ class User { }
  74. 74. Property events
  75. 75. class User { /** @OnChange(UserEvents::USERNAME_CHANGE) */ private $username; }
  76. 76. class User { /** * @OnAdd(UserEvents::FAVORITE) * @OnRemove(UserEvents::UNFAVORITE) */ private $favorites; }
  77. 77. Conditional events
  78. 78. class User { /** * @OnAdd( * UserEvents::FIRST_FAVORITE, * unless="old_value()" * ) */ private $favorites; }
  79. 79. Custom event objects
  80. 80. class User { /** * @OnAdd( * UserEvents::FOLLOW, * class=UserUserEvent::class, * arguments="[object, added_value()]" * ) */ private $follows; }
  81. 81. Event dependencies
  82. 82. class User { /** * @OnAdd( * UserEvents::FOLLOW, * before=ActivityEvents::CREATE * ) */ private $follows; }
  83. 83. kriswallsmith/state-events
  84. 84. (coming soon)
  85. 85. Merci!

×