Compondo estruturas com PHP

como ser produtivo sem criar (muita) desordem

/me

William Correa

@wilcorrea

PHP é muito dinâmica

mas nem tudo que reluz é ouro

recursos

exemplos de como o PHP é poderoso

dinâmicos

<?php

$variable = 'value';
$value = 100;
echo $$variable; // 100
ㅤ

variable variables

# RECURSOS DINÂMICOS
<?php

class Bar
{
    function foo() {
        echo "This is bar->foo()", PHP_EOL;
    }
}
$function = 'foo';
(new Bar)->{$function}(); // This is bar->foo()
ㅤ

variable functions

# RECURSOS DINÂMICOS

magic methods

 __construct() __destruct() __call() __callStatic() __get() __set() __isset() __unset() __sleep() __wakeup() __serialize() __unserialize() __toString() __invoke() __set_state() __clone() __debugInfo()
# RECURSOS DINÂMICOS

show me the code

# RECURSOS DINÂMICOS
<?php

abstract class DataTransferObject
{
    private array $data;
    
    private function __construct(array $data)
    {
        $this->data = $data;
    }
    
    public static function create(array $data): static
    {
        return new static($data);
    }

    public function __set(string $name, mixed $value)
    {
        $this->data[$name] = $value;
    }

    public function __get(string $name)
    {
        return $this->data[$name] ?? null;
    }
}
ㅤ

visibilidade de construtores

# RECURSOS DINÂMICOS
<?php

abstract class DataTransferObject
{
    // ...
    
    private function __construct(array $data)
    {
        $this->data = $data;
    }
    
    final public static function create(array $data): static
    {
        return new static($data);
    }

    // ...
}
ㅤ

magic methods

# RECURSOS DINÂMICOS
<?php

abstract class DataTransferObject
{
    // ...

    public function __set(string $name, mixed $value)
    {
        $this->data[$name] = $value;
    }

    public function __get(string $name)
    {
        return $this->data[$name] ?? null;
    }
}
ㅤ

dynamic access

# RECURSOS DINÂMICOS
<?php

final class UserDTO extends DataTransferObject {}

$userDTO = UserDTO::create(['name' => 'William']);
echo $userDTO->name; // William
ㅤ

access via magic method

# RECURSOS DINÂMICOS

traceability

# RECURSOS DINÂMICOS
<?php

abstract class DataTransferObject
{
    // ...

    public function set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }

    public function get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}
ㅤ

instance method

# RECURSOS DINÂMICOS
<?php

final class UserDTO extends DataTransferObject {}

$userDTO = UserDTO::create(['name' => 'William']);
echo $userDTO->get('name'); // William
ㅤ

indo além

# RECURSOS DINÂMICOS
<?php

final class UserDTO extends DataTransferObject
{
    protected array $validations = [
        'name' => 'required|string|min:3|max:40',
        'email' => 'required|email',
        'password' => 'required|password',
    ];
}
ㅤ

immutability

# RECURSOS DINÂMICOS
<?php

final class UserDTO extends DataTransferObjectImutable
{
    protected array $validations = [
        'name' => 'required|string|min:3|max:40',
        'email' => 'required|email',
        'password' => 'required|password',
    ];
}
ㅤ

importante

# RECURSOS DINÂMICOS

protected

public

private

static

abstract

final

Ou seja

# RECURSOS DINÂMICOS

amole o machado

antes da lida

contratos

o poder precisa de organização

definindo

contratos explícitos

# DEFININDO CONTRATOS
<?php

interface DataTransferObjectContract
{
    public function get(string $property): mixed;
    
    public function set(string $property, mixed $value): void;
}
ㅤ

interfaces

# DEFININDO CONTRATOS
<?php

namespace Asgard;

use Midgard\Other;

interface Foo extends Other {}
ㅤ
<?php

namespace Midgard;

interface Other {}
ㅤ
namespace Cybertron;

use Asgard\Foo;

class Bar extends Foo
{
    protected function awesome(): bool
    {
        return true;
    }
}
ㅤ

class abstraction

# DEFININDO CONTRATOS
namespace Asgard;

abstract class Foo
{
    abstract protected function awesome(): bool;
}
ㅤ

type hint / return type

# DEFININDO CONTRATOS
<?php

namespace Asgard;

interface Foo {}
ㅤ
<?php

namespace Midgard;

interface Other {}
ㅤ
<?php

class Bar implements Foo, Other {}

function magic(Foo $variable): Other
{
    return $variable;
}
echo get_class(magic(new Bar));
// Cybertron\Bar
ㅤ

type hint / return type

# DEFININDO CONTRATOS
<?php

namespace Cybertron;

use Asgard\Foo;
use Midgard\Other;

class Bar implements Foo, Other {}
ㅤ
function ragnarok(Foo $variable): Other
{
    return $variable;
}
ragnarok(new \stdClass);

// Fatal error: Uncaught TypeError: Cybertron\magic():
// Argument #1 ($variable) must be of type Asgard\Foo, stdClass given
ㅤ

not in my watch!

# DEFININDO CONTRATOS
function ragnarok(mixed $variable): Other
{
    return $variable;
}
ragnarok(new \stdClass);

// Fatal error: Uncaught TypeError: Cybertron\magic(): 
// Return value must be of type Midgard\Other, stdClass returned
ㅤ
<?php

namespace Asgard;

interface Foo {}
ㅤ

contratos implícitos

# DEFININDO CONTRATOS
<?php

namespace Midgard;

interface Other {}
ㅤ
<?php

echo new Bar instanceof Foo  ? 'yes' : 'no';
// yes
ㅤ

instanceof

# DEFININDO CONTRATOS
<?php

namespace Cybertron;

use Asgard\Foo;
use Midgard\Other;

class Bar implements Foo, Other {}
ㅤ

instanceof

# DEFININDO CONTRATOS
<?php

// ...
class Handler extends ExceptionHandler
{
    // ...
    public function render($request, Throwable $e): Response
    {
        // ...
        $data = ['error' => $e->getMessage()];
        if ($e instanceof ErrorContract) {
            $data['details'] = $e->getDetails();
        }
        // ...
    }
}
ㅤ
echo method_exists(Bar::class, 'awesome') ? 'yup' : 'nope';
// yup
ㅤ

method_exists

# DEFININDO CONTRATOS
<?php

namespace Cybertron;

use Asgard\Foo;
use Midgard\Other;

class Bar implements Foo, Other
{
    public function awesome(): void {}
}
ㅤ

method_exists

# DEFININDO CONTRATOS
<?php

// ...
class Handler extends ExceptionHandler
{
    // ...
    public function render($request, Throwable $e): Response
    {
        // ...
        $status = 500;
        if (method_exists($e, 'getStatusCode')) {
            $status = $e->getStatusCode();
        }
        return response()->json($data, $status);
    }
}
ㅤ
<?php

$implements = class_implements(Bar::class);

echo implode(', ', array_keys($implements)), PHP_EOL;
// Asgard\Foo, Midgard\Other

echo isset($implements[Foo::class]) ? 'yes' : 'no';
// yes
ㅤ

class_implements

# DEFININDO CONTRATOS
<?php

namespace Cybertron;

use Asgard\Foo;
use Midgard\Other;

class Bar implements Foo, Other {}
ㅤ
<?php

$reflection = new ReflectionClass(Bar::class);
$interfaces = array_keys($reflection->getInterfaces());
echo in_array(Foo::class, $interfaces)  ? 'yes' : 'no';
// yes
ㅤ

reflection

# DEFININDO CONTRATOS
<?php

namespace Cybertron;

use Asgard\Foo;
use Midgard\Other;

class Bar implements Foo, Other {}
ㅤ

PHP

Standard Recomendation

link
# DEFININDO CONTRATOS

aquilo que o mundo me pede

# DEFININDO CONTRATOS

não é mundo que me dá

comportamentos

manda quem pode, obedece quem tem juízo

gerindo

template method

# GERINDO COMPORTAMENTOS
interface MapperContract
{
    public function parse(array $data): array;
}
ㅤ
abstract class Decorator implements DecoratorContract
{
    abstract protected function handler(string $text): string;

    final public function parse(string $text): string
    {
        return "Decorated: {$this->handler($text)}";
    }
}
ㅤ

template method

# GERINDO COMPORTAMENTOS
final class BoldDecodator extends Decorator
{
    protected function handler(string $text): string
    {
        return "<strong>{$text}</strong>";
    }
}
ㅤ
final class ItalicDecorator extends Decorator
{
    protected function handler(string $text): string
    {
        return "<i>{$text}</i>";
    }
}
ㅤ

template method

# GERINDO COMPORTAMENTOS
$text = 'William';

echo (new BoldDecodator)->parse($text), PHP_EOL;
// Decorated: <strong>William</strong>

echo (new ItalicDecorator)->parse($text), PHP_EOL;
// Decorated: <i>William</i>
ㅤ

usando traits

# GERINDO COMPORTAMENTOS
interface GossipContract
{
    public function gossip(): void;
}
ㅤ
trait SpillTheBeans
{
    abstract protected function secret(): string;

    public function gossip(): void
    {
        echo "Their secret is: {$this->secret()}";
    }
}
ㅤ

usando traits

# GERINDO COMPORTAMENTOS
abstract class Gossipy implements GossipContract
{
    use SpillTheBeans;
}
ㅤ
final class Whisperous extends Gossipy
{
    protected function secret(): string
    {
        return 'PHP';
    }
}

(new Whisperous)->gossip();
// Their secret is: PHP
ㅤ

herança

herança sempre dá dor de cabeça?

composição

vs

inheritance vs composition 

# COMPOSIÇÃO vs HERANÇA
class Animal
{
    function eat() {}

    function walk() {}
}

class Dog extends Animal {}

class Cat extends Animal {}
ㅤ

inheritance for exposure

<?php

use Illuminate\Database\Migrations\Migration;

# Expor uma classe que atua na borda da estrutura.
# Note que muitas vezes a classe que extende pode
# (ou deve) ser final.

final class CreateProductsTable extends Migration
{
    // ...
}
# COMPOSIÇÃO vs HERANÇA

inheritance for exposure

namespace Slim;

use Psr\Log\AbstractLogger;

# Expor uma classe que atua na borda da estrutura
# forçando algum método abstrato

class Logger extends AbstractLogger
{
    public function log($level, $message, array $context = []): void
    {
    	// ...
    }
}
namespace Psr\Log;

// ...
abstract class AbstractLogger implements LoggerInterface
{
    use LoggerTrait;
}
# COMPOSIÇÃO vs HERANÇA

composition 

namespace Symfony\Bundle\WebProfilerBundle\Controller;
// ...
class RouterController
{
    // ...
    public function __construct(
        Profiler $profiler,
        Environment $twig,
        UrlMatcherInterface $matcher,
        RouteCollection $routes,
        iterable $expressionLanguageProviders = []
    ) {
        $this->profiler = $profiler;
        $this->twig = $twig;
        $this->matcher = $matcher;
        $this->routes = (null === $routes && $matcher instanceof RouterInterface) ? $matcher->getRouteCollection() : $routes;
        $this->expressionLanguageProviders = $expressionLanguageProviders;
    }
    // ...
}
# COMPOSIÇÃO vs HERANÇA

inheritance vs composition 

class Fish
{
    protected Animal $animal;
    protected Swimable $swimmable;

    final public function __construct(
        AnimalContract $animal,
        SwimmableContract $swimable
    ) {
        $this->animal = $animal;
        $this->swimmable = $swimable;
    }
}
# COMPOSIÇÃO vs HERANÇA

modelar

é sempre sobre comportamentos

práticos

compondo estruturas práticas

exemplos

tudo por minha conta

evitar bater seco no framework

# EXEMPLOS PRÁTICOS
use Illuminate\Database\Eloquent\Model;

abstract class AbstractModel extends Model
{
    abstract public function construct(): void;

    final public function __construct(array $attributes = [])
    {
        $this->construct();

        parent::__construct($attributes);
        // ....
    }
}
ㅤ

trait ftw

# EXEMPLOS PRÁTICOS
abstract class AbstractManagerController extends AbstractController
{
    use Permission;
    use Persistence;
    use Answer;

    use Create;
    use Read;
    use Update;
    use Destroy;

    use Search;
    use Restore;
}
ㅤ
final class CategoryController extends AbstractManagerController
{
    protected function model(): ModelContract
    {
        return $this->model;
    }
}
ㅤ

conveniência

# EXEMPLOS PRÁTICOS
final class CategoryController extends AbstractManagerController
{
    public function __construct(CategoryService $service)
    {
        parent::__construct($service);
    }
}
ㅤ

garantindo contratos

# EXEMPLOS PRÁTICOS
trait ManagerControllerContracts
{
    abstract protected function getData(RequestContract $request): array;
}
ㅤ
trait Create
{
    use ManagerControllerContracts;
    use AnswerContracts;

    public function create(RequestContract $request): JsonResponse
    {
        $this->grant($this->repository()->domain(), Levels::LEVEL_ADD);
        $data = $this->getData($request);
        $created = $this->repository()->create($data);
        return $this->answer(['subject' => $created]);
    }
}
ㅤ

ninguém é de ferro

# EXEMPLOS PRÁTICOS
final class CategoryController extends AbstractController
{
    use Permission;
    use Persistence;
    use Answer;

    use Create;
    use Read;
    
    use Search;

    public function __construct(CategoryService $service)
    {
        parent::__construct($service);
    }
}
ㅤ

ninguém é de ferro

# EXEMPLOS PRÁTICOS
class Router extends Facade
{
    public static function provide(string $uri, string $controller): void
    {
        $actions = [
            // ...
            ['verb' => 'post', 'path' => $uri, 'method' => 'create'],
        ];
        
        foreach ($actions as $action) {
            ['verb' => $verb, 'path' => $path, 'method' => $method] = $action;
            if (method_exists($controller, $method)) {
                static::$verb($path, "{$controller}@{$method}");
            }
        }
}
ㅤ
Router::provide('/general/category', CategoryController::class);
ㅤ

evite ficar apenas na superfície

as linguagens de programações são ferramentas

hora das palmas

perguntas?