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

Streamlining Your Laravel Codebase

Melvin Lee
February 08, 2017

Streamlining Your Laravel Codebase

Presented during Singapore PHP User Group Feb 2017 meetup.

Melvin Lee

February 08, 2017
Tweet

More Decks by Melvin Lee

Other Decks in Technology

Transcript

  1. About Me Web Engineer at Tech in Asia • Github:

    @zyml • Facebook: @zyml89 • Twitter: @zyml Melvin Lee
  2. Your basic controller (from Laravel docs) <?php namespace App\Http\Controllers; use

    App\User; use App\Http\Controllers\Controller; class UserController extends Controller { public function show($id) { return view('user.profile', ['user' => User::findOrFail($id)]); } }
  3. But in reality... Usual workflow for handling a POST request:

    1. Validate the input. 2. Check if logged in user can create a resource. 3. Create the resource in the database. 4. Send email to affected parties. 5. Send notification to affected parties. 6. Create / update relations / log audits. 7. ...
  4. Put everything together... public function postEdit($id) { if (\Input::get('action') ==

    'Post Comment') { $validationRules = [ 'comment' => 'required', ]; $validator = \Validator::make(\Input::all(), $validationRules); if ($validator->fails()) { return \Redirect::back()->withInput()->withErrors($validator); } $comment = new ModeratorComment([ 'content' => \Input::get('comment'), 'user_id' => \Auth::user()->getAuthIdentifier(), 'created_at' => new \DateTime(), ]); $confession = Confession::with('moderatorComments')->findOrFail($id); $confession->moderatorComments()->save($comment); return \Redirect::back()->withMessage('Comment successfully added.') ->with('alert-class', 'alert-success'); } else { $validationRules = [ 'content' => 'required', 'categories' => 'array', 'status' => 'in:Featured,Pending,Approved,Rejected', ]; $validator = \Validator::make(\Input::all(), $validationRules); if ($validator->fails()) { return \Redirect::back()->withInput()->withErrors($validator); } try { $data = [ 'content' => \Input::get('content'),
  5. “There are only two hard things in Computer Science: cache

    invalidation and naming things.” -Phil Karlton
  6. Step 1: Write events for each state class ConfessionWasApproved {

    public $confession; public $user; public function __construct(Confession $confession, User $user) { $this->confession = $confession; $this->user = $user; } }
  7. Step 2: Trigger the event at controller public function approve($id)

    { $confession = Confession::findOrFail($id); $confession->update(['status' => 'approved']); event(new ConfessionWasApproved($confession)); // Trigger event. return response()->json($confession); }
  8. Step 3: Write each task as its listener class PostConfessionToFacebook

    { protected $fb; public function __construct(Facebook $fb) { $this->fb = $fb; } public function handle(ConfessionWasApproved $event) { $confession = $event->confession; $this->fb->post($confession->content); } }
  9. Step 4: Attach those listeners class EventServiceProvider extends ServiceProvider {

    protected $listen = [ 'NUSWhispers\Events\ConfessionWasApproved' => [ 'NUSWhispers\Listeners\PostConfessionToFacebook', ], 'NUSWhispers\Events\ConfessionWasRejected' => [ 'NUSWhispers\Listeners\DeleteConfessionFromFacebook', ], ]; }
  10. Background Tasks • Sending an API call to a third-party

    service can be slow (10 - 15s). • Users can’t wait that long! • Laravel provides an API to defer listeners as a queued task. • Supported providers: ◦ AWS SQS ◦ Redis
  11. (Optional) Step 5: Queue the listeners class ConfessionWasApproved { use

    SerializesModels; // Serialize before queuing. public $confession; public $user; public function __construct(Confession $confession, User $user) { $this->confession = $confession; $this->user = $user; } }
  12. (Optional) Step 5: Queue the listeners class PostConfessionToFacebook implements ShouldQueue

    { protected $fb; public function __construct(Facebook $fb) { $this->fb = $fb; } public function handle(ConfessionWasApproved $event) { $confession = $event->confession; $this->fb->post($confession->content); } }
  13. Notifications (Laravel 5.3) class AppliedJobNotification extends Notification { protected $application;

    public function __construct(JobApplication $application) { $this->application = $application; } public function toMail($notifiable) { return (new MailMessage) ->subject('You have a new job application!') ... } // Add other delivery channel functions. }
  14. Notifications (Laravel 5.3) class NotifyAppliedJob implements ShouldQueue { use SerializesModels;

    public function handle(AppliedJobApplication $event) { $application = $event->getTarget(); Notification::send( $this->getCompanyAdmins($application), new AppliedJobNotification($application) ); } }
  15. Testing is easier now! class ConfessionServiceControllerTest extends TestCase { public

    function testStore() { $this->expectsEvents(ConfessionWasCreated::class); // You might want to assert the JSON structure too. $this->post('/api/confessions', ['content' => 'Foo Bar']) ->assertStatus(200); // Laravel 5.4 assertion. } }
  16. Testing listeners class PostConfessionToFacebookTest extends TestCase { protected $fb; public

    function setUp() { parent::setUp(); $this->fb = Mockery::mock(Facebook::class); } public function testHandle() { $confession = factory(Confession::class)->create(); $this->fb->shouldNotReceive('post'); $this->listener->handle(new ConfessionWasApproved($confession)); } }
  17. Testing notifications class NotifyAppliedJobTest extends TestCase { // Setup stuff

    here. public function testHandle() { Notification::fake(); $application = factory(JobApplication::class); $this->listener->handle(new AppliedJobApplication($application)); Notification::assertSentTo( $application->user, AppliedJobNotification::class ); } }
  18. Tracing back the steps... 1. Write events for each state.

    2. Trigger the event at the controller. 3. Write each task as an event listener. 4. Attach these listeners to the event service provider. 5. Queue the listeners (optional). 6. Use notifications as an additional layer (optional).
  19. A little cheat sheet... • Filter users / modify request

    payloads / rate throttling? Middleware • Control who can have perform an action on something? Policies • Access control based on policies? Gates • Sending to Slack / Mail / Notification Services? Notifications • Need to validate your requests? Form Requests • User can subscribe to events and listen to them? Broadcasting • Control how the model should present on response? Transformers
  20. Final Tip • When working on a new feature: ◦

    Laravel Docs is your best friend. ◦ Research other great third-party libraries. ▪ https://laravel-news.com/category/laravel-packages ▪ Curated list @ GitHub: https://github.com/chiraggude/awesome-laravel