Slim3 Container good practice? - php

Hello im learning PHP and i'am Building a REST API with the Slim3 Framework. I Create Routes Like this:
$container['HomeController'] = function () {
return new HomeController();
};
$currentContainer = CurrentContainer::getInstance();
$currentContainer->setContainer($container);
$app->get('/', 'HomeController:index')->setName("index");
My Problem was i had to pass the $container to every Single Controller Class iv'e created, because i need the container context in the Controller for routing etc.
then im build a Singleton Container Class like this:
class CurrentContainer
{
private static $instance;
private $container;
private function __construct()
{
}
private function __clone()
{
}
public static function getInstance()
{
if (self::$instance == null) {
self::$instance = new CurrentContainer();
}
return self::$instance;
}
public function setContainer($container)
{
$this->container = $container;
}
/**
* #return mixed
*/
public function getContainer()
{
return $this->container;
}
}
so now its possible to create a "MainController" like this:
class Controller
{
/**
* #var mixed
*/
protected $view;
/**
* #var
*/
protected $router;
public function __construct()
{
$container = CurrentContainer::getInstance()->getContainer();
$this->view = $container->view;
$this->router = $container->router;
}
}
now all of my Controllers extends from the Controller class...
my question is now ... its that a good idea or is there a reason to not do it like that?
im thankful for every input

I've built some APIs with Slim Framework, and also tried so many method to get it done (of course in right way). I implemented MVC pattern on Slim Framework. The code example below:
For the controller, I created a base controller that injected with container. So the code:
<?php
namespace App\Controller;
use Slim\Container;
class Controller
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function __get($name)
{
return $this->container->get($name);
}
}
I loaded the base controller on dependencies container.
<?php
// controller
$container['controller'] = function ($c) {
return new App\Controller\Controller($c);
};
So I can get the container from the controller.
<?php
namespace App\Controller;
use App\Controller\Controller;
use Slim\Http\Request;
use Slim\Http\Response;
class HomeController extends Controller
{
public function __invoke(Request $request, Response $response, $args)
{
return $this->renderer->render($response, 'home');
}
}
I hope it helps.

Related

Php override construction error in extends controller mvc structure

In php mvc structure I have this base controller class and add the constructor like this :
namespace App\Core;
/**
* Controller class
*/
class Controller
{
/** #var View View The view object */
public $View;
public $templates;
public $app;
/**
* Construct the (base) controller. This happens when a real controller is constructed, like in
* the constructor of IndexController when it says: parent::__construct();
*/
public function __construct()
{
$this->app = \App\Core\System\App::instance();
$this->Language = new Language('en-gb');
$this->templates = new \League\Plates\Engine(Config::get('PATH_VIEW'));
$this->Url = new \App\Core\Url(Config::get('URL'),Config::get('URL'));
}
public function loadModel($name) {
$path = '\App\Catalog\Model\\'.$name;
$this->model = new $path;
return $this->model;
}
public function loadController($name) {
$path = '\App\Catalog\Controller\\'.$name;
$this->controller = new $path;
return $this->controller;
}
}
Now in action (ie edit account) controller i have :
namespace App\Catalog\Controller\Account;
use App\Core\Config;
use App\Core\Csrf;
use App\Core\Response;
use App\Core\Session;
class EditAccount extends \App\Core\Controller
{
public function __construct()
{
parent::__construct();
//Auth::checkAuthentication();
}
public function index()
{
}
public function action()
{
}
}
Now, I work in PhpStorm and see this override error:
How do can in Fix this error?
Note: If I remove extends \App\Core\Controller from EditAccount class, error fixed But I need to extends \App\Core\Controller.

Proper way of initializing models in MVC by Dependency Injection? How to not break pattern

I am using http://container.thephpleague.com as my Dependency Injection Container.
I am making project in MVC and I am trying to not break MVC pattern.
I am loading Database, Json, etc. classes from DI Container (defined before routing, etc.), but I need models for every controller. Is this right way how to load Models with their own dependencies inside Controller?
Controller:
abstract class Controller
{
protected $di;
protected $db;
protected $latte;
protected $json;
//etc...
public function __construct(League\Container\Container $container)
{
$this->di = $container;
$this->db = $container->get('database');
$this->json = $container->get('json');
//etc...
$this->setup();
}
}
class ApiController extends Controller
{
function setup()
{
$this->model = new ApiModel($this->di);
}
public function showGames()
{
$this->latte->render("random-view.latte", $this->model-getGames());
}
}
Model:
class Model
{
protected $db;
protected $json;
public function __construct(League\Container\Container $container)
{
$this->json = $container->get('json');
$this->db = $container->get('database');
}
}
class ApiModel extends Model
{
/**
* Get games
* #return mixed
*/
public function getGames()
{
//db queries etc...
}
}
Something saying me, that this is not right way.

Silex call controller within controller

When I receive an API request it routes trough the Application.php to the UserController.
The UserController does his thing with the information and I need to call the EmailController, because that is the controller that manages all the emails.
In the EmailController I have a function (its simplified):
class EmailController {
public function getEmail() {
return 1337 ;
}
}
In the UserController I have a function:
class UserController {
public function getUserMail(Request $request, Application $app) {
$number = ???;
return $number;
}
}
What do I have to call within the UserController to get the getEmail function of the EmailController?
If this is not a correct way of doing it, I would love to hear what term I am acutally searching for :)
Edit1:
As #lawrence-cherone pointed out, it should have been in a model.
It was stuck in my head that I had to use the controller for this task.
You could use the dependency injection to share the class that return number.
So your controllers will look like:
class EmailController
{
/**
* #var NumberCalculatorInterface
*/
private $numberCalculator;
/**
* #param NumberCalculatorInterface $numberCalculator
*/
public function __construct(NumberCalculatorInterface $numberCalculator)
{
$this->numberCalculator = $numberCalculator;
}
public function getEmail()
{
return $this->numberCalculator->getNumber();
}
}
and
class UserController
{
/**
* #var NumberCalculatorInterface
*/
private $numberCalculator;
/**
* #param NumberCalculatorInterface $numberCalculator
*/
public function __construct(NumberCalculatorInterface $numberCalculator)
{
$this->numberCalculator = $numberCalculator;
}
public function getUserMail(Request $request, Application $app)
{
$number = $this->numberCalculator->getNumber();
return $number;
}
}
Your class that calculate number or other more complex logic will be
interface NumberCalculatorInterface
{
public function getNumber();
}
class DefaultNumberCalculator implements NumberCalculatorInterface
{
public function getNumber()
{
return 1337;
}
}
Since the number calculation is not a logic proper to your EmailController cause you use the logic in several classes, it make sense to be an external class. You will be able to unit test it properly and to inject in all the classes that need this calculation to be done.
You will be able to declare it as service:
class NumberCalculatorProvider implements ServiceProviderInterface {
public function register(Container $pimple)
{
$pimple['number_calculator'] = function () {
return new DefaultNumberCalculator();
};
}
}
And inject it inside your controller easily (in the following example is use the ServiceControllerServiceProvider to declare controller as services):
class ControllerProvider implements ServiceProviderInterface {
public function register(Container $pimple)
{
$pimple['controller.user'] = function ($pimple) {
return new UserController($pimple['number_calculator']);
};
$pimple['controller.email'] = function ($pimple) {
return new EmailController($pimple['number_calculator']);
};
}
}
note: In my example i use silex 2., since its not specified in your question, you may need to adapt it if you use an older version but the logic remain the same.*
I think you need to make UserController inherit the function getEmail() from EmailController
class UserController extends EmailController {
public function getUserMail(Request $request, Application $app) {
$number = ???;
return $number;
}
}

DI, ServiceProvider, abstract parent and Laravel 5.3

I have some problem and little misunderstanding Laravel SP (ServiceProvider). I have abstract class Repository and her Interface:
abstract class Repository implements RepositoryInterface {
private $model;
private $parser;
public function __construct() {
$this->model = new $this->model_name();
} }
interface RepositoryInterface {
public function create(array $attributes);
public function update($id, array $attributes);
public function delete($id);
public function all();
public function find($id);
public function filter(array $parameters, $query=null);
public function query(array $parameters, $query=null); }
and some child UserRepository for example:
class UserRepository extends Repository implements UserRepositoryInterface {
protected $model_name = "App\Models\User";
public function __construct() {
parent::__construct();
}
public function activation($user_id) {
return "user";
}
public function deactivation($user_id) {
return "user";
} }
and simple ModelParser class:
class ModelParser {
protected $parameters;
protected $model;
public function __construct($model) {
$this->model = $model;
} }
This work fine, but I would pass ModelParser as DI in my construct of abstract Repository with parameter $model. I dont have idea. How should I do it ?
I use it like this:
class UserController extends Controller {
private $repository;
public function __construct(UserRepository $repository) {
$this->repository = $repository;
} }
Well it's kinda complicated since your ModelParser requires a $model as it's parameter. And because this $model may vary depends on its repository, it will be too complicated if we're trying to resolve it using Laravel service container binding.
There's an easier approach, we can make the ModelParser class's constructor receive an optional $model parameter. Then we can add an additional method to set this $model property like so:
namespace App\Models;
class ModelParser
{
protected $parameters;
protected $model;
// Make $model parameter optional by providing default value.
public function __construct($model = null) {
$this->model = $model;
}
// Add setter method for $model.
public function setModel($model)
{
$this->model = $model;
return $this;
}
}
And now you can inject the ModelParser into your abstract Repository class. Laravel will easily resolve this ModelParser parameter
namespace App\Models;
use App\Models\ModelParser;
use App\Models\RepositoryInterface;
abstract class Repository implements RepositoryInterface
{
private $model;
private $parser;
// Pass ModelParser instance to your constructor!
public function __construct(ModelParser $parser)
{
$this->model = new $this->model_name();
// Set the parser's model property.
$this->parser = $parser->setModel($this->model);
}
// Rest of your code.
}
And if you're extending the abstract Repository class, you still have to pass this ModelParser to the constructor like so:
namespace App\Models;
use App\Models\ModelParser;
use App\Models\UserRepositoryInterface;
class UserRepository extends Repository implements UserRepositoryInterface
{
protected $model_name = "App\Models\User";
public function __construct(ModelParser $parser)
{
parent::__construct($parser);
}
}
Actually, if you're not planning to pass another parameter or perform something else during the class instantiation, you can simply remove the __construct() method from UserRepository and rely on its parent (the abstract Repository).
Hope this help!

PHP/Laravel - Can't initiate extend of abstract class

I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/

Categories