Silex - Controller como Classe, indo além Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 2 anos, 4 meses, 4 dias atrás.

Um dos empecilhos que mais me incomodaram quando comecei com Silex é a forma como ele trabalha com controllers, da até pra criar um arquivo separado pra organizar, mas para quem gosta dos controllers em objetos fica tudo muito estranho (por falta de definição melhor).

A solução, mas nem tanto

Logo encontrei uma pequena página da documentação sobre o assunto e meu código mudou disso:

$helloWord = $app['controllers_factory'];
$helloWord->get('/', function () {
    return 'Hello World';
});
$helloWord->get('/{name}', function ($name) {
    return 'Hello '.$name;
});

Para isso:

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

$app->get('/', 'App\Controller\HelloWorld::index');
$app->get('/{name}', 'App\Controller\HelloWorld::index');

E a classe App\Controller\HelloWorld:

namespace App\Controller;

class HelloWorld
{
    public function index($name = "World")
    {
        return 'Hello '.$name;
    }
}

Já fiquei muito feliz. E se eu precisar de uma instância do Silex? Sem problemas, a injeção de dependências já resolve tudo.

namespace App\Controller;

use Silex\Application;

class HelloWorld
{
    public function index(Application $app, $name = "World")
    {
        return 'Hello '.$name;
    }
}

Olha o Silex injetando ele mesmo na variável $app, isso funciona para outros objetos também, como o Symfony\Component\HttpFoundation\Request, ou o que você precisar.

Mas ai tive outra dificuldade.

Outro problema

Eu passei a ter problemas com alguns métodos que ficavam grandes quando eu usava o Silex e o Request do symfony/http-foundation, vou mostrar, imagine um action que precise de dois parâmetros.

public function index(Request $request, Application $app, $name = "World", $lastname="Figueiredo")
{
    //...
}

Agora imagine 20 métodos, não é prático, é repetitivo, isso deveria ficar em um construtor talvez, seria muito mais prático, mas o Silex não faz essa injeção automáticamente (no controller) e ainda dificulta se eu quiser fazer por conta própria

A solução

A primeira coisa que pensei foi em usar controller como serviço, mas vi como deviam ser feitas as injeções de dependência por controller e desanimei, então pensei: Cara, o Silex ainda é PHP, bora criar um objeto para injetar isso.

namespace WebDevBr\Mvc;

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;

class Loader
{
    public function getController(Request $request, Application $app)
    {
        extract($this->defaultsParams($request->attributes->all()));

        $controller = 'App\Mvc\Controllers\\' . ucfirst($controller) . "Controller";
        return call_user_func([new $controller($app, $request), $action], $params);
    }

    public function defaultsParams(Array $params)
    {
        $params = $params['_route_params'];

        if (empty($params['controller']))
            $params['controller'] = 'index';

        $data['controller'] = $params['controller'];
        unset($params['controller']);

        if (empty($params['action']))
            $params['action'] = 'index';

        $data['action'] = $params['action'];
        unset($params['action']);

        $data['params'] = $params;

        return $data;
    }
}

Pronto, agora eu tenho um carregador de controllers, o método defaultsParams() verifica se foi informado um controller e um action, se não ele informa index como valor padrão para ambos.

O método getController() retorna o controller (com Symfony\Component\HttpFoundation\Request e Silex\Application já incluidos no construtor) e o action prontinho pra usar. Note na linha 12 que eu passei um namespace (respeite o autoload), ainda posso facilitar um pouco na hora de informar o namespace (você pode querer usar outro), veja como ficou.

namespace WebDevBr\Mvc;

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;

class Loader
{
    protected $namespace = 'App\Mvc\Controllers\%sController';

    public function getController(Request $request, Application $app)
    {
        extract($this->defaultsParams($request->attributes->all()));

        $controller = sprintf($this->namespace, ucfirst($controller));
        return call_user_func([new $controller($app, $request), $action], $params);
    }
    /.../
}

Agora é só editar o atributo $namespace para se adequar a sua necessidade.

E como ficariam as rotas agora? Vamos ver alguns exemplos

//retorna App\Mvc\Controllers\IndexController::index()
$app->get('/', 'WebDevBr\Mvc\Loader::getController');

//retorna App\Mvc\Controllers\{controller}Controller::index()
$app->get('/{controller}', 'WebDevBr\Mvc\Loader::getController');

//retorna App\Mvc\Controllers\{controller}Controller::{action}()
$app->get('/{controller}/{action}', 'WebDevBr\Mvc\Loader::getController');

//retorna App\Mvc\Controllers\{controller}Controller::{action}({id})
$app->get('/{controller}/{action}/{id}', 'WebDevBr\Mvc\Loader::getController');

Sempre que um controller não for informado usando {controller} ele vai tentar carregar um IndexController, quando um action não for informado usando {action} ele carrega um método index(), ou seja, se nada for informado, ele vai carregar o IndexController::index() e você ainda pode receber um atributo $params, que é um array no seguinte formato.

array (size=3)
  'controller' => string 'index' (length=7)
  'action' => string 'index' (length=4)
  'params' => 
    array (size=1)
      'id' => string '1' (length=1)

E o nosso Hello World?

Rota

$app>get('/hello/{name}', 'App\Controller\HelloWorld::getController');

Controller

namespace App\Controller;

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;

class IndexController
{

    protected $app;
    protected $request;

    public function __construct(Request $request, Application $app)
    {
        $this->app = $app;
        $this->request = $request;
    }

    public function index(Array $params = array())
    {
        return 'Hello ' . $params['params']['name'];
    }
}

Muito mais bacana, né!

Conclusão

Você ainda pode contar com todos os outros recursos e integrações do Silex, afinal, ainda tem uma instancia dele em $this->app, mas ainda organiza seus controllers em classes muitos mais limpas e organizadas, não esqueça, qualquer dúvida ou sugestão é só postar nos comentários.


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!