Slide 1

Slide 1 text

Laravel Design Pa.erns 2.0

Slide 2

Slide 2 text

@bobbybouwmann

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Let's talk design pa/erns

Slide 6

Slide 6 text

What are design pa.erns? • Common occurring problems • Building blocks • Generic reusable solu6on for a problem

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Laravel Design pa.erns 1.0 • Factory Pa+ern • Builder (Manager) Pa+ern • Strategy Pa+ern • Provider Pa+ern

Slide 9

Slide 9 text

Laravel Design pa.erns 2.0 • Singleton Pa-ern • Observer Pa-ern • Bridge Pa-ern

Slide 10

Slide 10 text

Singleton Pa,ern Crea%onal Pa+ern

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

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.

Slide 14

Slide 14 text

But Why Singleton Pa1ern? • Save system resources • Share data

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

$pizzaOvenOne = PizzaOven::getInstance(); $pizzaOvenTwo = PizzaOven::getInstance(); $this->assertSame( $pizzaOvenOne, $pizzaOvenTwo ); // true $this->assertSame( $pizzaOvenOne->getOven(), $pizzaOvenTwo->getOven() ); // true

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

// Illuminate\Database\DatabaseManager.php public function __construct($app, ConnectionFactory $factory) { $this->app = $app; $this->factory = $factory; $this->reconnector = function ($connection) { $this->reconnect($connection->getName()); }; }

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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.

Slide 23

Slide 23 text

Observer Pa*ern Behavioral Pa,ern

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

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.

Slide 27

Slide 27 text

• 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

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

// 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

Slide 33

Slide 33 text

// App\Providers\EventServiceProvider.php use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { protected $listen = [ Registered::class => [ SendEmailVerificationNotification::class, ], ]; }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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.

Slide 40

Slide 40 text

Bridge Pa*ern Structural Pa*ern

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

Bridge Pa*ern Decouple an abstrac-on from its implementa-on so that the two can vary independently.

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

// 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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

// 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'; }

Slide 57

Slide 57 text

Bridge Pa*ern Decouple an abstrac-on from its implementa-on so that the two can vary independently.

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Wrapping up

Slide 60

Slide 60 text

Singleton Pa,ern

Slide 61

Slide 61 text

Observer Pa*ern

Slide 62

Slide 62 text

Bridge Pa*ern

Slide 63

Slide 63 text

The silver bullet

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

THANK YOU

Slide 66

Slide 66 text

THANK YOU