Antes de Framework sua ferramenta é PHP
Não tem sido incomum a turma começar a aprender a programar usando ferramentas pré-programadas. Não sou do tipo que julga isso e acho que ninguém deveria ser. Na verdade concordo tanto com não julgar que ajudei na tradução deste artigo (leia se puder).
É importante salientar que todo framework PHP é essencialmente PHP. Sim, isso mesmo, isso tudo lindo que você tanto admira é apenas o bom e velho PHP. Só um monte de código PHP junto!
A maioria dos frameworks em PHP trazem alguma qualidade para o código dos desenvolvedores de um modo geral, e, embora não fosse preciso a adoção de ferramentas para tanto vou dar algumas dicas de como não ficar no arroz com feijão com essas ferramentas.
Nesta série de artigos vou tentar responder o porquê de eu gostar tanto de PHP e o que a faz ser uma linguagem tão legal de ser usada : )
Laravel como exemplo
Das ferramentas da categoria é uma das que mais me agrada. Embora seja perceptível, que, em muitos momentos ela abre mão da performance para oferecer um ambiente mais agradável e simples para o desenvolvedor, o Laravel não decepciona. Mesmo para quem não é fã de ActiveRecord é possível usá-lo sem qualquer problema e se sentir satisfeito com o resultado final.
Obviamente que o “laravel/laravel” é apenas um boilerplate, então vou dar algumas dicas de como eu uso a ferramenta (correndo o risco de ter opiniões pessoais perdidas por aqui).
Estrutura de pastas
Nenhum projeto merece ter todos os arquivos misturados numa mesma pasta. Então tente organizar seu projeto de alguma forma que faça sentido.
A estrutura acima está sendo usada em dois projetos e, claro, que ela não é perfeita, nem é um padrão do “certo” a se fazer, inclusive, pode já ter mudado enquanto você estiver lendo esse artigo. Pense nessa imagem como uma amostra de como outra pessoa faz e tente chegar (usando outras fontes de pesquisa) a algo que se adeque ao seu fluxo de trabalho ou do seu time.
Rotas limpas
Evite rotas rocambolescas e repleta de lógicas. Você pode estender o “facade” Router para poder incutir seus comportamentos preferidos.
<?php
namespace App\Infra;
use Illuminate\Support\Facades\Route as Facade;
/**
* Class Router
* @package App\Infra
*/
class Router extends Facade
{
/**
* @param string $uri
* @param string $controller
*/
public static function api($uri, $controller)
{
static::post($uri, "{$controller}@post");
static::get($uri, "{$controller}@get");
static::put("{$uri}/{id}", "{$controller}@put");
static::delete("{$uri}/{id}", "{$controller}@destroy");
static::get("{$uri}/{id}", "{$controller}@search");
}
}
Com esse tipo de recurso você pode tratar suas rotas de API dessa forma.
<?php
use App\Infra\Router;
use App\Http\Controllers\Api\Content\Category;
Router::api('/category', Category::class);
E como sabemos que a coisa complica facilmente em aplicações web, não descarte a possibilidade de fazer algo assim.
<?php
use App\Infra\Router;
$restricted = ['jwt.auth'];
$error = function () {
return ['error' => '404'];
};
Router::prefix('v1')->group(function () use ($restricted) {
Router::prefix('content')->middleware($restricted)->group(__DIR__ . '/api/content.php');
}
Router::any('{any}', $error)->where('any', '.*');
Você se repete sem saber
Procurei pela internet exemplos básicos de cadastros e consegui abstrair algo que soa como um exemplo tradicional de CRUD com Laravel.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Article;
/**
* http://itsolutionstuff.com/post/laravel-55-crud-example-from-scratchexample.html
*/
class ArticleController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$articles = Article::latest()->paginate(5);
return view('articles.index',compact('articles'))
->with('i', (request()->input('page', 1) - 1) * 5);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('articles.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
request()->validate([
'title' => 'required',
'body' => 'required',
]);
Article::create($request->all());
return redirect()->route('articles.index')
->with('success','Article created successfully');
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$article = Article::find($id);
return view('articles.show',compact('article'));
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$article = Article::find($id);
return view('articles.edit',compact('article'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
request()->validate([
'title' => 'required',
'body' => 'required',
]);
Article::find($id)->update($request->all());
return redirect()->route('articles.index')
->with('success','Article updated successfully');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
Article::find($id)->delete();
return redirect()->route('articles.index')
->with('success','Article deleted successfully');
}
}
Não vou entrar nos detalhes da implementação desse Controller.php, mas te convido a pensar de outra forma. E se, apenas… e se, ao invés de fazer desse jeito você fizesse um Controller desse jeito aqui.
<?php
namespace App\Http\Content\Controllers;
use App\Common\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
/**
* Class ResourceController
* @package App\Http\Content\Controllers
*/
class ResourceController extends Controller
{
/**
* @var string
*/
protected $domain = '';
/**
* @var Model
*/
protected $model;
/**
* @var int
*/
protected $offset = 5;
/**
* ResourceController constructor.
* @param Model $model
*/
public function __construct(Model $model)
{
$this->model = $model;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$data = $this->model->latest()->paginate($this->offset);
return view("{$this->domain}.index", compact('data'))
->with('i', (request()->input('page', 1) - 1) * $this->offset);
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
return view("{$this->domain}.create");
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
request()->validate($this->model->validates());
$this->model->create($request->all());
return redirect()->route("{$this->domain}.index")
->with('success', $this->model->message('store.success'));
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($id)
{
$data = $this->model->find($id);
return view("{$this->domain}.show", compact('data'));
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($id)
{
$data = $this->model->find($id);
return view("{$this->domain}.edit", compact('data'));
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($id)
{
request()->validate($this->model->validates());
$this->model->find($id)->update(request()->all());
return redirect()->route("{$this->domain}.index")
->with('success', $this->model->message('update.success'));
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy($id)
{
$this->model->find($id)->delete();
return redirect()->route("{$this->domain}.index")
->with('success', $this->message('destroy.success'));
}
}
E, em seguida, apenas fizesse algo assim para usar ele.
<?php
namespace App\Http\Api\Content;
use App\Http\Controllers\ResourceController;
use App\Domains\Content\Article;
/**
* Class ArticleController
* @package App\Http\Api\Content
*/
class ArticleController extends ResourceController
{
/**
* ArticleController constructor.
* @param Article $article
*/
public function __construct(Article $article)
{
parent::__construct($article);
$this->domain = 'content.article';
}
}
Sim, é isso mesmo. De agora em diante quando eu quiser fazer um novo Controller só preciso criar algo dessa forma que ele já tem a capacidade de fazer tudo o que era feito com todas aquelas linhas. Ao invés de copiar e colar e substituir o nome das classes eu construo estruturas que representem os domínios e reaproveitem todo o código que já fora escrito.
Desacople-se
Não importa o que digam, seu código é seu código e o da ferramenta é o da ferramenta. Evite estender diretamente as classes base que o framework te entrega e monte os fluxos de forma a não ficar totalmente dependente de coisas que variam, por exemplo, recuperar dados do http ou interagir com uma base de dados.
Se você ficou esperto viu que foram chamados métodos do model no Controller “automatizado” que o Eloquent não entrega. Algo simples como o trecho abaixo te permite fazer coisas mais elaboradas que o que o framework (não importa qual) te entrega como base.
<?php
namespace App\Common;
use App\Common\Model\File;
use App\Common\Model\Hook;
use App\Common\Model\Order;
use App\Common\Model\Relationship;
use App\Common\Model\Search;
use App\Utils\Instance;
use Illuminate\Database\Eloquent\Model as Eloquent;
/**
* Class Model
* @package App\Common
*/
abstract class Model extends Eloquent
{
/**
* @trait
*/
use File, Instance, Search, Order, Relationship, Hook;
}
O lema é centralizar e distribuir, mas sem sobrecarregar. Evite sempre engordar demais uma classe com coisas desnecessárias. Algo como essas traits poderiam ser usadas em separado quando você precisasse delas. Apenas quando precisasse! Um exemplo do que podem ser essas traits a seguir.
<?php
namespace App\Common\Model\Hook;
use ErrorException;
/**
* Trait HookSave
* @package App\Common\Model\Hook
*/
trait HookSave
{
/**
* @param array $options
* @return bool
* @throws ErrorException
*/
public function save(array $options = [])
{
$this->trigger('beforeSave', $options);
/** @noinspection PhpUndefinedClassInspection */
$saved = parent::save($options);
$this->trigger('afterSave', $options);
return $saved;
}
/**
* @param string $hook
* @param array $options
* @throws ErrorException
*/
protected function trigger($hook, $options)
{
if (method_exists($this, $hook)) {
$triggered = call_user_func_array([$this, $hook], $options);
if ($triggered === false) {
throw new ErrorException("Fail to resolve event `{$hook}` on `" . static::class . "`");
}
}
}
}
Este é um exemplo de uma estratégia para executar comandos depois de chamar os métodos básicos do ORM. Claro que o Laravel tem sua API de eventos, e, por isso reforço que estes são exemplos sobre possibilidades. Eu, particularmente, curto ter hooks síncronos acessíveis de uma forma simples. Talvez seja apenas um velho hábito, mas fica claro como é simples de se ter esse tipo de abordagem usando o nosso bom e velho PHP.
Bom, era só isso que eu queria mostrar nesse “Ato I”. Nos próximos darei dicas como:
caminhos para desacoplar seu projeto do framework, ou;
formas de mapear relacionamentos dinamicamente, ou;
formas de usar Model’s que escalam facilmente, ou;
estratégias para ter projetos refatoráveis e evolutivos, e;
sempre ideias para escrever cada vez menos código com cada vez mais resultado!
Não se esqueça nunca que cada linha de código escrita precisa ser pensada e que um trecho de código copiado e colado é um convite à dificuldade de manutenção e, provavelmente, uma impossibilidade de uma refatoração rápida.
Espero que tenha sido de algum uso para você e dúvidas, sugestões, xingamentos e etc são sempre bem vindos.
Se quiser saber mais sobre Laravel pode usar a documentação oficial ou dar uma olhada no grupo do Telegram, onde tem uma galera que manja pacas da ferramenta. Laravel - The PHP Framework For Web Artisans Laravel - The PHP framework for web artisans.laravel.com Laravel Brasil Grupo de discussão de brasileiros sobre o Laravel Frameworkt.me