Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Separating Responsibilities: Clean Controllers ...

Separating Responsibilities: Clean Controllers = Better Code

Learn from a real-life example on how to clean up your controller files.

Avatar for David Hemphill

David Hemphill

November 07, 2013

More Decks by David Hemphill

Other Decks in Programming

Transcript

  1. Hi My name is David, ! I am a graphic

    designer, web developer, and all-around good guy. ! @davidhemphill
  2. Let’s take a look at a really bad controller I’ve

    written (Please keep your judgements to yourself :)
  3. An Invite Controller Method Handles the posting of form data,

    validation, redirection, user creation, view preparation and email notifications ! (That’s way too much code for a simple method. 60 lines in fact) public function post_index() { // Get the input $input = Input::all(); ! // Set the rules $rules = array( 'email' => 'required|email', ); ! // Make a validation object $validation = Validator::make($input, $rules); ! // Has the validation ran? if ($validation->fails()) // It failed, deal with it { return Redirect::to('invite')->with_input()->with_errors($validation); } else // Invite the user & send an email { // Check if the email is in the system already if ($invite = Invite::where('email', '=', Input::get('email'))->first()) { $invite->invite_code = Str::random(48); $invite->save(); } else { $invite = Invite::create(array( 'email' => Input::get('email'), 'used' => 0, 'invite_code' => Str::random(48) )); } ! // Get the email view $body = View::make('emails.user_invite_confirmation') ->with('invitee', Auth::user()) ->with('invite_link', URL::to('beta/register/?invite_code='.$invite->invite_code.'&email='.$invite->email)) ->render(); ! // Notify the person of their new invite $message = Message::to($invite->email) ->from('[email protected]', 'RigShare') ->subject('Your friend '.Auth::user()->full_name.' invited you to RigShare!') ->body($body) ->send(); ! if ($message->was_sent()) { Log::info('Email was sent.'); } ! if ( $invite == false ) { die('didn\'t work'); } ! return Redirect::to('beta/create_invite')->with('success', true); } }
  4. Validation Questions: What if we want to create invites from

    another controller? Code duplication is bad and hard-to-maintain // Set the rules $rules = array( 'email' => 'required|email', ); ! // Make a validation object $validation = Validator::make($input, $rules); ! // Has the validation ran? if ($validation->fails()) // It failed, deal with it { return Redirect::to('invite')->withInput()->withErrors($validation); } else {} // Invite the user, send an email, and redirect !
  5. Invite User Creation We should be doing validation in the

    validation code. The uniqueness check is built-in to Laravel’s validator. Again, what if we needed to create users elsewhere? // Check if the email is in the system already if ($invite = Invite::where('email', '=', Input::get('email'))->first()) { $invite->invite_code = Str::random(48); $invite->save(); } else { $invite = Invite::create(array( 'email' => Input::get('email'), 'used' => 0, 'invite_code' => Str::random(48) )); }
  6. Email View Compilation Could this go somewhere else? What about

    that nasty invite link? Could we store that in a configuration file? // Get the email view $body = View::make('emails.user_invite_confirmation') ->with('invitee', Auth::user()) ->with('invite_link', URL::to('beta/register/?invite_code='. $invite->invite_code.'&email='.$invite->email)) ->render();
  7. Sending Email to the User Should a Controller care if

    an email is sent? NO! It just needs to handle HTTP Traffic. Also, what about those hardcoded emails? Why do you hate me, me? // Notify the person of their new invite $message = Message::to($invite->email) ->from('[email protected]', 'RigShare') ->subject('Your friend '.Auth::user()->full_name.' invited you to RigShare!') ->body($body) ->send();
  8. Error Checking/Redirection LOOK AT THAT DIE() STATEMENT. LOOK AT IT.

    Two, count em, two checks for things that would have Exceptions thrown. if ($message->wasSent()) { Log::info('Email was sent.'); } ! if ( $invite == false ) { die('didn\'t work'); } ! return Redirect::to('beta/create_invite')->with('success', true);
  9. First things first. Delete your models folder Free yourself from

    the tyranny of misplaced, overstuffed, and confused classes.
  10. public function postIndex() { // Get the email from post

    $input = Input::all(); $validation = $this->validator->validate($input); if ($validation->passes()) { // Save the invite to the database $invite = $this->invite->create($input['email']); ! // Show a success message to the user return View::make('invites.signup_thank_you'); } return Redirect::to('/')->withErrors($validation->errors())->withInput(); }
  11. Let’s dive into it Validation Service, Invite Repository. <?php use

    LDFW\Services\InviteValidator; use LDFW\Repositories\InviteRepositoryInterface; ! class InviteController extends BaseController { public function __construct(InviteValidator $validator, InviteRepositoryInterface $invite) { $this->validator = $validator; $this->invite = $invite; }
  12. <?php namespace LDFW\Services; use Validator as V; abstract class Validator

    { protected $errors; protected $passes; public function validate($input) { $validator = V::make($input, static::$rules); if ($validator->fails()) { $this->errors = $validator->messages(); return $this; } $this->passes = true; return $this; } public function passes() { return $this->passes; } public function errors() { return $this->errors; } }
  13. <?php namespace LDFW\Repositories; ! use Event; use LDFW\Invite; use LDFW\Repositories\InviteRepositoryInterface;

    ! class DbInviteRepository implements InviteRepositoryInterface { /** * Create an invite in the database */ public function create($email) { Invite::create(['email' => $email]); ! // Fire off the invite creation event Event::fire('invite.create', [$email]); } }
  14. <?php namespace LDFW\Handlers; use Log; use LDFW\Mailers\InviteMailer; ! class InviteEventHandler

    { public function __construct(InviteMailer $mailer) { $this->mailer = $mailer; } public function onInviteCreate($email) { $this->mailer->welcome($email); $this->mailer->notifyAdmin($email); } public function subscribe($events) { $events->listen('invite.create', 'LDFW\Handlers \InviteEventHandler@onInviteCreate'); } }
  15. <?php namespace LDFW\Mailers; use Mail; ! abstract class Mailer {

    public function sendTo($email, $subject, $view, $data = []) { Mail::queue($view, $data, function($message) use ($email, $subject) { $message->to($email); $message->from('[email protected]', 'David Hemphill'); $message->subject($subject); }); } }
  16. <?php namespace LDFW\Mailers; use LDFW\Mailers\Mailer; ! class InviteMailer extends Mailer

    { public function welcome($email) { $view = 'emails.invites.welcome_user'; $data = ['email' => $email]; $subject = 'Welcome to the Laravel DFW Community!'; ! return $this->sendTo($email, $subject, $view, $data); } public function notifyAdmin($email) { $view = 'emails.invites.notify_of_creation'; $data = ['email' => $email]; $subject = 'A New Invite has been created'; ! return $this->sendTo('[email protected]', $subject, $view, $data); } }
  17. Don’t Repeat Yourself Make small little bits of reusable logic.

    PHP 5 gives you several tools to do that. Namespaces, Abstract Classes, Traits (Mixins) & Interfaces
  18. Ask yourself: Who’s responsible? Who should know about what’s going

    on in this class? A controller should only handle HTTP requests. It shouldn’t send emails, know how validation is handled, etc…