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

Laravel Design Patterns 2.0

Laravel Design Patterns 2.0

Design patterns are commonly defined as solutions to recurring design problems. Frameworks like Laravel use Design Patterns throughout the codebase to keep structure and maintainability. Last year I talked about the factory, manager, strategy and provider design pattern. The patterns are all used within the framework. In this talk we will explore another set of Design Patterns that are used in Laravel. Once we have a basis of the design patterns we go into some practical examples.

Bobby Bouwmann

July 24, 2019
Tweet

More Decks by Bobby Bouwmann

Other Decks in Programming

Transcript

  1. What are design pa.erns? • Common occurring problems • Building

    blocks • Generic reusable solu6on for a problem
  2. Laravel Design Pa.erns • Factory Pa+ern • Builder (Manager) Pa+ern

    • Strategy Pa+ern • Provider Pa+ern • Repository Pa+ern • Iterator Pa+ern • Singleton Pa+ern • Presenter Pa+ern • Bridge Pa+ern
  3. Laravel Design pa.erns 1.0 • Factory Pa+ern • Builder (Manager)

    Pa+ern • Strategy Pa+ern • Provider Pa+ern
  4. Singleton Pa,ern Singleton is a crea0onal design pa,ern that lets

    you ensure that a class has only one instance, while providing a global access point to this instance.
  5. class PizzaOven { private static $instance = null; private $oven;

    private function __construct() { $this->oven = new Oven(); } public static function getInstance() { if(!self::$instance) { self::$instance = new PizzaOven(); } return self::$instance; } public function getOven() { return $this->oven; } }
  6. $pizzaOvenOne = PizzaOven::getInstance(); $pizzaOvenTwo = PizzaOven::getInstance(); $this->assertSame( $pizzaOvenOne, $pizzaOvenTwo );

    // true $this->assertSame( $pizzaOvenOne->getOven(), $pizzaOvenTwo->getOven() ); // true
  7. // Illuminate\Database\DatabaseServiceProvider.php public function register() { // The connection factory

    is used to create the actual connection instances on // the database. We will inject the factory into the manager so that it may // make the connections while they are actually needed and not of before. $this->app->singleton('db.factory', function ($app) { return new ConnectionFactory($app); }); // The database manager is used to resolve various connections, since multiple // connections might be managed. It also implements the connection resolver // interface which may be used by other components requiring connections. $this->app->singleton('db', function ($app) { return new DatabaseManager($app, $app['db.factory']); }); }
  8. // Illuminate\Database\DatabaseManager.php public function __construct($app, ConnectionFactory $factory) { $this->app =

    $app; $this->factory = $factory; $this->reconnector = function ($connection) { $this->reconnect($connection->getName()); }; }
  9. // Illuminate\Container\Container.php public function singleton($abstract, $concrete = null): void {

    $this->bind($abstract, $concrete, true); } public function bind($abstract, $concrete = null, $shared = false): void { if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } // IMPORTANT LINE HERE $this->bindings[$abstract] = compact('concrete', 'shared'); if ($this->resolved($abstract)) { $this->rebound($abstract); } }
  10. protected function resolve($abstract, $parameters = [], $raiseEvents = true) {

    $abstract = $this->getAlias($abstract); // if this is an instance managed as singleton we return it right away if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; } $concrete = $this->getConcrete($abstract); // Instantiate an instance of the concrete type registered for the binding $object = $this->isBuildable($concrete, $abstract) ? $this->build($concrete) : $this->make($concrete); // If the type is registered as a singleton we'll want to cache the instance in memory if ($this->isShared($abstract)) { $this->instances[$abstract] = $object; } return $object; }
  11. // Illuminate\Log\LogServiceProvider $this->app->singleton('log', function () { return new LogManager($this->app); });

    $this->assertSame( app()->make('log'), app()->make('log') ); // true // Some random controller public function index(ChannelManager $channelManager): void { $this->assertSame( $channelManager, app()->make(ChannelManager::class) ); // true }
  12. Singleton Pa,ern Singleton is a crea0onal design pa,ern that lets

    you ensure that a class has only one instance, while providing a global access point to this instance.
  13. Observer Pa*ern Define a one-to-many dependency between objects so that

    when one object changes state, all its dependents listeners (subscribers) are no4fied and updated automa>cally.
  14. • Observable: • The subject • The thing that can

    be observed • The thing that undergoes the changes • Keeps a collec6on of observers to no/fy • Observer: • The listener • The thing that can observe • The thing that is interested in the changes • Accepts the observable to handle the state change
  15. class StartProcessOrder implements Observable { /** @var Collection */ private

    $observers; public function add(Observer $observer): void { $this->observers->push($observer); } public function notify(): void { foreach ($this->observers as $observer) { $observer->update($this); } } }
  16. class SendDeliveryTime implements Observer { public function update(Observerable $observable): void

    { $this->smsService->send($observable->getOrder()); } } class NotifyBaker implements Observer { public function update(Observerable $observable): void { $this->ticketPrinter->print($observable->getOrder()); } }
  17. class SendDeliveryTime implements Observer { public function update(StartProcessOrder $startProcessOrder): void

    { echo 'Send SMS with delivery time'; $this->smsService->send($startProcessOrder->getDeliveryTime()); } } class NotifyBaker implements Observer { public function update(StartProcessOrder $startProcessOrder): void { echo 'Start printing order for baker'; $this->ticketPrinter->print($startProcessOrder->getOrder()); } }
  18. // Create an observable $orderProces = new StartProcessOrder(); // Add

    observer to the observable $orderProces->add(new SendDeliveryTime()); $orderProces->add(new NotifyBaker()); // Notify all observers $orderProces->notify(); // Output: Send SMS with delivery time // Output: Start printing order for baker
  19. // Illuminate\Foundation\Support\Providers\EventServiceProvider.php class EventServiceProvider extends ServiceProvider { protected $listen =

    []; public function boot(): void { $events = $this->listens(); foreach ($events as $event => $listeners) { foreach (array_unique($listeners) as $listener) { Event::listen($event, $listener); } } } public function listens(): array { return $this->listen; } }
  20. // Illuminate\Events\Dispatcher public function listen($events, $listener) { foreach ((array) $events

    as $event) { if (Str::contains($event, '*')) { $this->setupWildcardListen($event, $listener); } else { $this->listeners[$event][] = $this->makeListener($listener); } } }
  21. // Illuminate\Events\Dispatcher public function listen($events, $listener) { foreach ((array) $events

    as $event) { if (Str::contains($event, '*')) { $this->setupWildcardListen($event, $listener); } else { $this->listeners[$event][] = $this->makeListener($listener); } } }
  22. // Illuminate\Foundation\helpers.php function event(...$args) { return app('events')->dispatch(...$args); } // Fire

    of the event somewhere in your code // e.g controllers, jobs, etc. event(new Registered($user));
  23. event(new Registered($user)); // Illuminate\Events\Dispatcher.php public function dispatch($event, $payload = [])

    { // When the given "event" is actually an object we will assume it is an event // object and use the class as the event name and this event itself as the // payload to the handler, which makes object based events quite simple. [$event, $payload] = $this->parseEventAndPayload( $event, $payload ); foreach ($this->getListeners($event) as $listener) { $listener($event, $payload); } }
  24. Observer Pa*ern Define a one-to-many dependency between objects so that

    when one object changes state, all its dependents listeners (subscribers) are no4fied and updated automa>cally.
  25. class Product { private $promotion; private $price; private $name; public

    function __construct(Promotion $promotion, float $price, string $name) { $this->promotion = $promotion; $this->price = $price; $this->name = $name; } public function getPrice(): float { return $this->promotion->applyDiscount($this->price); } }
  26. class Pizza extends Product { public function getPricePerPiece(): float {

    return round($this->getPrice() / 8, 2); } } class ChickenWings extends Product { public function getPriceWithSauce(): float { return round($this->getPrice() + 0.65, 2); } public function getPriceWithoutSauce(): float { return round($this->getPrice(), 2); } }
  27. abstract class Promotion { public function applyDiscount(float $price): float }

    class BlackFriday extends Promotion { public function applyDiscount(float $price): float { // Apply 40% discount + make sure price ends with .99 return ceil($price * 0.6) - 0.01; } } class ValentinesDay extends Promotion { public function applyDiscount(float $price): float { return $price - 0.25; } }
  28. // Using product as a base $product = new Product(new

    BlackFriday(), 6.00, 'Pizza'); $product->getPrice(); // 3.99 $product = new Product(new ValentinesDay(), 6.00, 'Pizza'); $product->getPrice(); // 5.75 // Using individual products (abstractions) $product = new ChickenWings(new BlackFriday(), 12.50, 'Wings'); $product->getPriceWithSauce(); // 8.64 $product->getPriceWithoutSauce(); // 7.99 $product = new ChickenWings(new ValentinesDay(), 12.50, 'Wings'); $product->getPriceWithSauce(); // 12.90 $product->getPriceWithoutSauce(); // 12.25
  29. class CreateUsersTable extends Migration { public function up() { Schema::create('users',

    function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('users'); } }
  30. // database/migrations/2014_10_12_000000_create_users_table.php Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique();

    }); // Illuminate\Database\Schema\Builder.php public function __construct(Connection $connection) { $this->connection = $connection; $this->grammar = $connection->getSchemaGrammar(); }
  31. // Illuminate\Database\Schema\Builder public function dropAllTables() { throw new LogicException('This database

    driver does not support dropping all tables.'); } // Illuminate\Database\Schame\MySqlBuilder public function dropAllTables() { $tables = $this->getAllTables(); $this->disableForeignKeyConstraints(); $this->connection->statement( $this->grammar->compileDropAllTables($tables) ); $this->enableForeignKeyConstraints(); }
  32. // Illuminate\Database\Schema\SQLiteBuilder public function dropAllTables() { if ($this->connection->getDatabaseName() !== ':memory:')

    { return $this->refreshDatabaseFile(); } $this->connection->select($this->grammar->compileEnableWriteableSchema()); $this->connection->select($this->grammar->compileDropAllTables()); $this->connection->select($this->grammar->compileDisableWriteableSchema()); $this->connection->select($this->grammar->compileRebuild()); }
  33. // Illuminate\Database\Schema\Grammars\MySqlGrammar public function compileDropAllTables($tables) { return 'drop table '.implode(',',

    $this->wrapArray($tables)); } // Illuminate\Database\Schema\Grammars\SQLiteGrammar public function compileDropAllTables() { return "delete from sqlite_master where type in ('table', 'index', 'trigger')"; } // Illuminate\Database\Schema\Grammars\PostgresGrammar public function compileDropAllTables($tables) { return 'drop table "'.implode('","', $tables).'" cascade'; }
  34. Looks like Adapter Pa0ern right? • Bridge pa+ern is designed

    up-front to let the abstrac7on and the implementa7on vary independently • Adapter makes things work a>er they're designed, make unrelated classes work together