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?
Compondo estruturas com PHP
By William Correa
Compondo estruturas com PHP
PHP é uma linguagem de script muito flexível, o que acaba criando um universo muito grande de possibilidades sobre como criar e gerir estruturas para seus objetos e coleções. Neste material vamos trazer à tona formas de criar e gerenciar estruturas com contratos inteligentes e ágeis que podem ser usadas para gerenciar diversos tipos de dados.
- 657