matters of state

87
Matters of State Using Symfony’s Event Dispatcher to turn your application into a simple state machine

Upload: kris-wallsmith

Post on 12-Jan-2017

9.203 views

Category:

Internet


0 download

TRANSCRIPT

Page 1: Matters of State

Matters of StateUsing Symfony’s Event Dispatcher to

turn your application into a simple state machine

Page 2: Matters of State

Photo by jeff_golden

Bon anniversaire Symfony!

Page 3: Matters of State

About me

• Portland

• twitter.com/kriswallsmith

• github.com/kriswallsmith

• kriswallsmith.net

Page 4: Matters of State
Page 5: Matters of State

React

Nicolas Raymond

Page 6: Matters of State
Page 7: Matters of State

var HelloWorld = React.createClass({ render: function() { return ( <div>Hello World</div> ) } })

Page 8: Matters of State

var HelloWorld = React.createClass({ render: function() { return ( <div>Hello World</div> ) } })

JSX

Page 9: Matters of State

Just the view

Page 10: Matters of State

(there is no MVC to violate)

Page 11: Matters of State

One-way data flow

Page 12: Matters of State

(everything flows from state)

Page 13: Matters of State

Virtual DOM

Page 14: Matters of State

(DOM changesets)

Page 15: Matters of State

“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

Page 16: Matters of State

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> ) } })

Page 17: Matters of State

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> ) } })

Page 18: Matters of State

<button id="click_button"> 0 click(s) </button>

Page 19: Matters of State

$('#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);})

Page 20: Matters of State

$('#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…

Page 21: Matters of State

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> ) } })

Page 22: Matters of State

Paradigm shift

Page 23: Matters of State

Imperative vs. DeclarativePhoto by

Page 24: Matters of State

Imperative programming

Page 25: Matters of State

(code that explains how to reach your goal)

Page 26: Matters of State

Declarative programming

Page 27: Matters of State

(code that describes what your goal is)

Page 28: Matters of State

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

Page 29: Matters of State

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

Page 30: Matters of State

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

Page 31: Matters of State

$('#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);})

Page 32: Matters of State

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> ) } })

Page 33: Matters of State

How vs. What

Page 34: Matters of State

SELECT * FROM users WHERE username="kriswallsmith";

Page 35: Matters of State

PHP controllers

Page 36: Matters of State

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()] );}

Page 37: Matters of State

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()] );}

Page 38: Matters of State

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!

Page 39: Matters of State

Why stop there?

Page 40: Matters of State

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()] );}

Page 41: Matters of State

Excuse me Kris, but what about the email?

Page 42: Matters of State

Photo by

Page 43: Matters of State

Event Dispatcher

Photo by rhodesj

Page 44: Matters of State

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

Page 45: Matters of State

public function addListener( $eventName, $listener, $priority = 0);

public function dispatch( $eventName, Event $event = null);

Page 46: Matters of State

$dispatcher->dispatch('user.create');

Page 47: Matters of State

$dispatcher->dispatch( 'user.create', new UserEvent($user));

Page 48: Matters of State

$dispatcher->dispatch( UserEvents::CREATE, new UserEvent($user));

Page 49: Matters of State

$dispatcher->addListener( UserEvents::CREATE, function(UserEvent $event) { echo "Welcome, {$event->getUserName()}!"; });

Page 50: Matters of State

$dispatcher->addListener( UserEvents::CREATE, [$listener, 'onUserCreate']);

Page 51: Matters of State

Something just happened or is about to happen

Page 52: Matters of State

Photo by Simon Law

Reactive Listeners

Page 53: Matters of State

class UserListener{ public function onUserCreate(UserEvent $event) { $user = $event->getUser();

$this->mailer->sendWelcomeEmail($user); }}

Page 54: Matters of State

class ActivityFeedListener{ public function onUserCreate(UserEvent $event) { $user = $event->getUser();

$this->feed->logRegistration($user); }}

Page 55: Matters of State

class ActivityFeedListener{ public function onFollow(UserUserEvent $event) { $user = $event->getUser(); $otherUser = $event->getOtherUser();

$this->feed->logFollow($user, $otherUser); }}

Page 56: Matters of State

Testable

Page 57: Matters of State

Reusable

Page 58: Matters of State

Readable

Page 59: Matters of State

State Events

Page 60: Matters of State

Where do they come from?

Page 61: Matters of State

Option 1: Controller dispatch

Page 62: Matters of State

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()] );}

Page 63: Matters of State

Option 2: Service dispatch

Page 64: Matters of State

class UserManager{ public function persistUser(User $user) { $this->em->persist($user);

$this->dispatcher->dispatch( UserEvents::CREATE, new UserEvent($user) ); }}

Page 65: Matters of State

Option 3: Flush dispatch

Page 66: Matters of State

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) ); } } } } }}

Page 67: Matters of State

a simple state machine

Page 68: Matters of State

Cool, but…

Page 69: Matters of State

That flush listener is ugly…

Page 70: Matters of State

…and imperative!

Page 71: Matters of State

What if…

Photo by Jared Cherup

What if…

Page 72: Matters of State

/** @On\Create(UserEvents::CREATE) */class User{}

Page 73: Matters of State

/** @On\Change(UserEvents::CHANGE) */class User{}

Page 74: Matters of State

/** @On\Destroy(UserEvents::DESTROY) */class User{}

Page 75: Matters of State

/** * @On\Create(UserEvents::CREATE) * @On\Update(UserEvents::UPDATE) * @On\Destroy(UserEvents::DESTROY) */class User{}

Page 76: Matters of State

Property events

Page 77: Matters of State

class User{ /** @On\Change(UserEvents::USERNAME_CHANGE) */ private $username;}

Page 78: Matters of State

class User{ /** * @On\Add(UserEvents::FAVORITE) * @On\Remove(UserEvents::UNFAVORITE) */ private $favorites;}

Page 79: Matters of State

Conditional events

Page 80: Matters of State

class User{ /** * @On\Add( * UserEvents::FIRST_FAVORITE, * unless="old_value()" * ) */ private $favorites;}

Page 81: Matters of State

Custom event objects

Page 82: Matters of State

class User{ /** * @On\Add( * UserEvents::FOLLOW, * class=UserUserEvent::class, * arguments="[object, added_value()]" * ) */ private $follows;}

Page 83: Matters of State

Event dependencies

Page 84: Matters of State

class User{ /** * @On\Add( * UserEvents::FOLLOW, * before=ActivityEvents::CREATE * ) */ private $follows;}

Page 85: Matters of State

kriswallsmith/state-events

Page 86: Matters of State

(coming soon)

Page 87: Matters of State

Merci!