How to structure route controllers in slim framework 3 (mvc pattern) - php

I am working on a Slim Framework based API. Following the mvc pattern, I want my routes to be controller driven with logger and renderer injected to every controller.
As a start point I checked out a number of example mvc slim skeletons, and decided to base my structor on one particular tutorial and sample project ( http://jgrundner.com/slim-oo-004-controller-classes/ )
In this setup, injection is done by adding the router controllers to the app container like this:
$container = $app->getContainer();
$container['\App\Controllers\DefaultController'] = function($c){
return new \App\Controllers\DefaultController(
$c->get('logger'),
$c->get('renderer')
);
};
This then allow for a nice clean route and controller:
route e.g.:
$app->get('/[{name}]', '\App\Controllers\DefaultController:index');
controller e.g.:
namespace App\Controllers;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class DefaultController{
private $logger;
private $renderer;
public function __construct(LoggerInterface $logger, $renderer){
$this->logger = $logger;
$this->renderer = $renderer;
}
public function index(RequestInterface $request, ResponseInterface $response, $args){
// Log message
$this->logger->info("Slim-Skeleton '/' route");
// Render index view
return $this->renderer->render($response, 'index.phtml', $args);
}
public function throwException(RequestInterface $request, ResponseInterface $response, array $args){
$this->logger->info("Slim-Skeleton '/throw' route");
throw new \Exception('testing errors 1.2.3..');
}
}
Extending the default controller keeps the controllers neat, but requires adding every new controller object to the app container first, which seems inefficient and messy when you have a lot of classes.
Is there a better way?

You can make basecontroller that contain the acontainer.
<?php
namespace App\Controller;
use Slim\Container;
class Controller
{
var $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function __get($var)
{
return $this->container->{$var};
}
}
and the container:
<?php
$container['App\Controller\Controller'] = function ($c) {
return new App\Controller\Controller($c);
};
And the controller
<?php
namespace App\Controllers;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use App\Controller\Controller;
class DefaultController extends Controller{
}

Related

Automatically load modules or libraries in Laravel controller or model

In my Laravel project, many Controllers and Models have the same headers.
For example, they all include
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use DB;
So, each time I ever create a new Controller, I have to insert the same header part. (like above)
Is there any way to autoload the above libraries in all controllers and models?
You could create a base class which accepts the dependencies in the constructor.
You could also create a Container class which reduces the amount of direct dependencies you have in a controller :
class Container
{
public function __construct(/* Your dependencies */) {
/* Set dependencies */
}
/* Dependency getters */
}
Controller:
class MyController
{
public function __construct(Container $container)
{
$this->container = $container;
}
public function index() {
/* Access dependencies on container */
}
}
But to be honest if you depend on request in a model class you have done something wrong. Typically in MVC the request info is passed from within the controller to the model, so the model does not know about the Request object, the values from Request are passed through as primitive values or value objects.
Update to explain my answer
You can reduce the amount of dependencies in your model classes (which is the best option) by simply following these rules, as explained here: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
So for example instead of writing:
//Note this is not a laravel specific example
class MyModel
{
private $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function getInfo()
{
return /* find info on $request->get('id'); */
}
}
You can write:
class MyController
{
public function __construct(MyModel $myModel)
{
$this->myModel = $myModel;
}
public function index()
{
$info = $this->myModel->getInfo($this->getRequest()->get('id'));
}
}
Where Request dependency is now removed from the Model:
//Note this is not a laravel specific example
class MyModel
{
public function getInfo(int $id)
{
return /* find info on $id; */
}
}
This was the simplest explanation, it will get more complex, so the best idea is to read and understand the article.

Laravel: Is there a way to reuse a method that is using a Request variable as parameter?

I want to reuse my method store that is in generar\productoController
public function store(Request $request){}
and I want to reuse it in this class adquisicion\ComprasController, I know that I have to import the class to use the method i want, but the problem is the $request variable, should I create a new object of it with $request = new Request(), adding the data I want with this and sending it as parameter?
Thx for the help I'm really new with laravel
you can try it like this $this->store(request(),$otherData)
use the helper to get the current object of request
You can pass Request data to other method
productoController(Request $request){
// anything here
return redirect('your route name')->with('data', $request->all());
}
Here are two ways that can make methods reusable in laravel application:
Make a helper method
Create a Helpers folder in app folder, and create all static methods inside a helper.php
Helper.php
namespace App\Helpers;
class Helper {
public static function store() {
$request = request();
// ....
}
}
YourController.php
namespace App\Repositories;
use App\Helpers\Helper;
use Illuminate\Http\Request;
class YourController extends Controller
{
public function store(Request $request) {
// call the store method as
Helper::store();
}
}
The downside here is you will mix up all the non-related helper methods here and may difficult to organize.
Repository
You can use a Repository Pattern to architect your application, for example, if you store a foo object to your datastore, then you can first create Repositories folder in app folder, and create FooRepository.php in Repositories folder:
FooRepository.php
namespace App\Repositories;
class FooRepository {
public function store() {
$request = request();
// ...
}
}
YourController.php
namespace App\Http\Controllers;
use App\Repositories\FooRepository;
use Illuminate\Http\Request;
class YourController extends Controller
{
private $fooRepository = null;
public function __construct(FooRepository $fooRepository) {
parent::__construct();
$this->fooRepository = $fooRepository;
}
public function store(Request $request) {
// call the method as
$this->fooRepository->store();
}
}

Slim Model db instance

sorry for my english.
stack: Slim 3 framework + Eloquent ORM.
Eloquent works as expected with Slim.
I want to use sort of a MVC pattern where thin controllers and fat models(all db queries and other heavy logic).
All I found is how to use it from routes like this:
$app->get('/loans', function () use ($app) {
$data = DB::table('loan_instalment')->get(); // works
$data = $this->db->table('loan_instalment')->get(); // works
...
}
What I want is ability to call public methods from choosen model, something like this:
use \src\models\Instalment;
$app->get('/loans', function () use ($app) {
$data = $this->model('Instalment')->getSomething(12);
...
}
and Model class is:
namespace src\models;
use Illuminate\Database\Eloquent\Model as Model;
use Illuminate\Database\Capsule\Manager as DB;
class Instalment extends Model
{
protected $table = 'loan_instalment';
public function getSomething($id)
{
return $this->table->find($id);
}
// bunch of other methods
}
My app looks like basic Slim skeleton, Eloquent settings:
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($container['settings']['db']);
$capsule->setAsGlobal();
$capsule->bootEloquent();
$container['db'] = function ($container) use ($capsule){
return $capsule;
};
Is it possible ?
If you want to use MVC pattern, you need to make base controller.
<?php
namespace App\Controller;
use Slim\Container;
class BaseController
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getContainer()
{
return $this->container;
}
public function __get($name)
{
return $this->container->{$name};
}
public function __set($name, $value)
{
$this->container->{$name} = $value;
}
}
And the container:
// Base Controller
$container[App\Controller\BaseController::class] = function ($c) {
return new App\Controller\BaseController($c);
};
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($container['settings']['db']);
$capsule->setAsGlobal();
$capsule->bootEloquent();
$container['db'] = function ($container) use ($capsule){
return $capsule;
};
Highly recommended to use static function on models
<?php
namespace App\models;
use Illuminate\Database\Eloquent\Model as Model;
use Illuminate\Database\Capsule\Manager as DB;
class Instalment extends Model
{
protected $table = 'loan_instalment';
public static function getSomething($id)
{
return Instalment::find($id);
}
}
And now you code become:
<?php
use App\models\Instalment;
$app->get('/loans', function ($request, $response, $args) {
$data = Instalment::getSomething(12);
...
}
The controller:
<?php
namespace App\Controller;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use App\models\Instalment;
class HomeController extends BaseController
{
public function __invoke(Request $request, Response $response, Array $args)
{
$data = Instalment::getSomething(12);
// load the template
return $response;
}
}
And the route for the controller
<?php
$app->get('/', App\Controller\HomeController::class);
It looks cleaner, isn't it?
More tutorial:
My Blog
Rob Allen's Blog
You could use the abbility of Slim to use controllers.
Make a basic controller:
// BasicController.php
<?php
namespace src\Controllers;
class BasicController
{
public function model(string $model)
{
return new $model();
}
}
and then in your controllers extend this class and add it to the slim container
//SomeController.php
<?php
namespace src\Controllers;
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use \src\models\Instalment as Instalment;
class SomeController extends BasicController
{
public function index(Request $request, Response $response, $args)
{
$this->model(Instalment::class)->getSomethingOutOfDB;
//do something else
}
}
...
//container.php
use \Slim\Container as Container;
$container[\src\Controllers\HomeController::class] = function(Container $container) {
return new \src\Controllers\Homecontroller();
}
...
...
//routes.php
$app->get('/someroute', \src\Controllers\HomeController::class . ':index');
...
Another possibility is to extend your \Slim\App by:
//newApp.php
namespace scr\App
class newApp extends \Slim\App
{
public function model(string $model)
{
return new $model();
}
}
I actually would advice against these both methods, and not load your models in this way, since this is considered bad practice.
It is better just to use:
//routes.php
...
use src\Models\Instalment;
...
...
$app->get('/someroute', function() {
$instalment = new Instalment();
// do something with it...
});

Slim controller issue : must be an instance of ContainerInterface, instance of Slim\\Container given

I am trying to use controller in Slim however keep getting the error
PHP Catchable fatal error: Argument 1 passed to
TopPageController::__construct() must be an instance of ContainerInterface,
instance of Slim\Container given
My index.php
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../vendor/autoload.php';
require 'settings.php';
spl_autoload_register(function ($classname) {
require ("../classes/" . $classname . ".php");
});
$app = new \Slim\App(["settings" => $config]);
$app->get('/', function (Request $request, Response $response) {
$response->getBody()->write("Welcome");
return $response;
});
$app->get('/method1', '\TopPageController:method1');
$app->run();
?>
My TopPageController.php
<?php
class TopPageController {
protected $ci;
//Constructor
public function __construct(ContainerInterface $ci) {
$this->ci = $ci;
}
public function method1($request, $response, $args) {
//your code
//to access items in the container... $this->ci->get('');
$response->getBody()->write("Welcome1");
return $response;
}
public function method2($request, $response, $args) {
//your code
//to access items in the container... $this->ci->get('');
$response->getBody()->write("Welcome2");
return $response;
}
public function method3($request, $response, $args) {
//your code
//to access items in the container... $this->ci->get('');
$response->getBody()->write("Welcome3");
return $response;
}
}
?>
Thanks. I am using Slim 3.
Your code seems to be based on the Slim 3 documentation at http://www.slimframework.com/docs/objects/router.html which does not contain all the required code to avoid the Exception being thrown.
You basically have two options to make it work.
Option 1:
Import the namespace in your index.php, just like it is being done for Request and Response:
use \Interop\Container\ContainerInterface as ContainerInterface;
Option 2:
Change the constructor of the TopPageController to:
public function __construct(Interop\Container\ContainerInterface $ci) {
$this->ci = $ci;
}
TL;DR
The reason the Exception is thrown is that the Slim\Container class is using the Interop\Container\ContainerInterface interface (see the source code):
use Interop\Container\ContainerInterface;
Since Slim\Container is extending Pimple\Container, the following should all be valid (working) type declarations for your controller's method:
public function __construct(Pimple\Container $ci) {
$this->ci = $ci;
}
...or even...
public function __construct(ArrayAccess $ci) {
$this->ci = $ci;
}
Based on a later change in Slim3 (from version 3.12.2 to 3.12.3) a slightly different ContainerInterface is required. This changes \Interop\ to \Psr\. Add the following on top of you code or change the existing use:
use Psr\Container\ContainerInterface;
Or change the constructor:
public function __construct(\Psr\Container\ContainerInterface $container)
{
$this->container = $container;
}
Just paste the below code in your controller
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use \Interop\Container\ContainerInterface as ContainerInterface;
Your controller's construct should look like the one below
public function __construct(ContainerInterface $container) {
parent::__construct($container);
}
I think you are making mistake in giving namespace in controller for ContainerInterface.
Since container-interop/container-interop is deprecated, simply replace it with psr/container (Psr\Container\ContainerInterface).

Inject Application into Controller

I have the following DemoController
class DemoController {
public function test() {
return new Response('This is a test!');
}
}
I want to bind this controller to $app ['demo.controller']
$app ['demo.controller'] = $app->share ( function () use($app) {
return new DemoController ();
} );
Inside the DemoController i want to have the Application $app object to work with registered services. What is the right way? Currently i am using __construct($app) for the DemoController and pass $app. This looks like
$app ['demo.controller'] = $app->share ( function () use($app) {
return new DemoController ($app);
} );
What is the best-practice for that?
That is certainly one way to do it. I want to show two alternatives.
One is to get the application injected into the action method directly using a type hint:
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
class DemoController
{
public function test(Request $request, Application $app)
{
$body = 'This is a test!';
$body .= ':'.$request->getRequestUri();
$body .= ':'.$app['foo']->bar();
return new Response($body);
}
}
The advantage of this option is that you don't actually need to register your controller as a service.
The other possibility is to inject the specific service instead of injecting the whole container:
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
class DemoController
{
private $foo;
public function __construct(Foo $foo)
{
$this->foo = $foo;
}
public function test()
{
return new Response($this->foo->bar());
}
}
Service definition:
$app['demo.controller'] = $app->share(function ($app) {
return new DemoController($app['foo']);
});
The advantage of this option is that your controller no longer depends on silex, the container or any particular service name. This makes it more isolated, re-usable and easier to test in isolation.

Categories