I am trying to bind a class into the container via a middleware and it doesn't seem to work because the class that I am binding recieved 2 parameters in the constructor and they aren't recognised.
I am getting the following error message: "Unresolvable dependency resolving [Parameter #0 [ $base_url ]] in class App\Services\Factureaza".
Here is the middleware:
<?php
namespace App\Http\Middleware;
use App\Services\Factureaza;
use Closure;
class InitializeInvoiceProvider
{
public function handle($request, Closure $next)
{
app()->singleton(Factureaza::class, function () {
// get settings by calling a custom helper function
$settings = json_decode(get_setting('invoicing_provider_settings'), true);
$api_url = isset($settings['url']) ? $settings['url'] : null;
$api_key = isset($settings['key']) ? $settings['key'] : null;
return new Factureaza($api_url, $api_key);
});
return $next($request);
}
}
The Factureaza class looks like this:
<?php
namespace App\Services;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException;
class Factureaza
{
protected $client;
protected $base_url;
protected $api_key;
public function __construct($base_url, $api_key)
{
$this->client = new GuzzleClient([
'base_uri' => $base_url,
'verify' => false,
'auth' => [$api_key, 'x'],
]);
$this->base_url = $base_url;
$this->api_key = $api_key;
}
}
I am getting this error when I am trying to resolve the dependency in my controller:
<?php
class InvoicesController extends Controller
{
protected $api;
public function __construct()
{
$this->api = resolve('App\Services\Factureaza');
}
}
Your binding should be in a Service Provider. The middleware you have isn't ran until after that Controller has been instantiated. There is no singleton bound to the container at this point. The bind is too late in the life cycle to be able to be there for a controller that a route is being dispatched to.
Laravel instantiates the Controller before it runs the route middleware. It needs to do this to be able to gather the middleware that a controller can define in its constructor to build the middleware stack.
Update:
Some Possible work arounds (did not test) without refactoring:
1) Use method injection instead of trying to get an instance in the constructor:
public function show(Factureaza $factureaza, ...)
2) Use a closure middleware defined in the constructor of the controller to get an instance and assign it.
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->api = resolve(Factureaza::class);
return $next($request);
});
}
Hopefully the middleware that sets the information that the singleton needs has ran before this controller middleware.
3) Have a middleware set this api on the controller for you ... would require adding a method to the controllers to take this information.
You have access to the controller for the route as it has already be instantiated and assigned to the Route.
$request->route()->getController()->setApi(...);
Related
I am having an issue setting up an injection on both the constructor and the method in a controller.
What I need to achieve is to be able to set up a global controller variable without injecting the same on the controller method.
From below route;
Route::group(['prefix' => 'test/{five}'], function(){
Route::get('/index/{admin}', 'TestController#index');
});
I want the five to be received by the constructor while the admin to be available to the method.
Below is my controller;
class TestController extends Controller
{
private $five;
public function __construct(PrimaryFive $five, Request $request)
{
$this->five = $five;
}
public function index(Admin $admin, Request $request)
{
dd($request->segments(), $admin);
return 'We are here: ';
}
...
When I run the above, which I'm looking into using, I get an error on the index method:
Symfony\Component\Debug\Exception\FatalThrowableError thrown with message "Argument 1 passed to App\Http\Controllers\TestController::index() must be an instance of App\Models\Admin, string given"
Below works, but I don't need the PrimaryFive injection at the method.
class TestController extends Controller
{
private $five;
public function __construct(PrimaryFive $five, Request $request)
{
$this->five = $five;
}
public function index(PrimaryFive $five, Admin $admin, Request $request)
{
dd($request->segments(), $five, $admin);
return 'We are here: ';
}
...
Is there a way I can set the constructor injection with a model (which works) and set the method injection as well without having to inject the model set in the constructor?
One way you could do this is to use controller middleware:
public function __construct()
{
$this->middleware(function (Request $request, $next) {
$this->five = PrimaryFive::findOrFail($request->route('five'));
$request->route()->forgetParameter('five');
return $next($request);
});
}
The above is assuming that PrimaryFive is an Eloquent model.
This will mean that $this->five is set for the controller, however, since we're using forgetParameter() it will no longer be passed to your controller methods.
If you've specific used Route::model() or Route::bind() to resolve your five segment then you can retrieve the instance straight from $request->route('five') i.e.:
$this->five = $request->route('five');
The error is because of you cannot pass a model through the route. it should be somethiing like /index/abc or /index/123.
you can use your index function as below
public function index($admin,Request $request){}
This will surely help you.
Route::group(['prefix' => 'test/{five}'], function () {
Route::get('/index/{admin}', function ($five, $admin) {
$app = app();
$ctr = $app->make('\App\Http\Controllers\TestController');
return $ctr->callAction("index", [$admin]);
});
});
Another way to call controller from the route. You can control what do you want to pass from route to controller
I am trying to access the $container from my middleware, but i am not getting much luck.
In my index.php file I have
require '../../vendor/autoload.php';
include '../bootstrap.php';
use somename\Middleware\Authentication as Authentication;
$app = new \Slim\App();
$container = $app->getContainer();
$app->add(new Authentication());
And then I have a class Authentication.php like this
namespace somename\Middleware;
class Authentication {
public function __invoke($request, $response, $next) {
$this->logger->addInfo('Hi from Authentication middleware');
but i get an error
Undefined property: somename\Middleware\Authentication::$logger in ***
I have also tried adding the following constructor to the class but I also get no joy.
private $container;
public function __construct($container) {
$this->container = $container;
}
Could anyone help please?
Best Practice to Middleware Implementation is Something like this :
Place this code inside your dependency section :
$app = new \Slim\App();
$container = $app->getContainer();
/** Container will be passed to your function automatically **/
$container['MyAuthenticator'] = function($c) {
return new somename\Middleware\Authentication($c);
};
then inside your Authentication class create constructor function like you mentioned :
namespace somename\Middleware;
class Authentication {
protected $container;
public function __invoke($request, $response, $next)
{
$this->container->logger->addInfo('Hi from Authentication middleware');
}
public function __construct($container) {
$this->container = $container;
}
/** Optional : Add __get magic method to easily use container
dependencies
without using the container name in code
so this code :
$this->container->logger->addInfo('Hi from Authentication middleware');
will be this :
$this->logger->addInfo('Hi from Authentication middleware');
**/
public function __get($property)
{
if ($this->container->{$property}) {
return $this->container->{$property};
}
}
}
After inside your index.php add Middleware using name resolution like this:
$app->add('MyAuthenticator');
I disagree with Ali Kaviani's Answer. When adding this PHP __magic function (__get), the code will be a lot more difficult to test.
All the required dependencies should be specified on the constructor.
The benefit is, that you can easily see what dependencies a class has and therefore only need to mock these classes in unit-tests, otherwise you would've to create a container in every test. Also Keep It Simple Stupid
I'll show that on the logger example:
class Authentication {
private $logger;
public function __construct($logger) {
$this->logger = $logger;
}
public function __invoke($request, $response, $next) {
$this->logger->addInfo('Hi from Authentication middleware');
}
}
Then add the middleware with the logger parameter to the container:
$app = new \Slim\App();
$container = $app->getContainer();
$container['MyAuthenticator'] = function($c) {
return new somename\Middleware\Authentication($c['logger']);
};
Note: the above registration to the container could be done automatically with using PHP-DI Slim (but that should be also slower).
I've been reading the documentation up and down now, still not sure what I'm doing wrong. In my opinion the documentation is very difficult to understand for a beginner.
Anyway, I'm trying to make something akin to the Auth::user() method, where it returns additional data about a logged in user that I will be needing for this application.
I have this helper class here:
namespace App\Helpers;
use Auth;
use Illuminate\Http\Request;
use App\Models\Grouping\User;
use App\Models\Grouping\Client;
use App\Models\Grouping\Rank;
class ClientUser {
public function __construct($request) {
$this->request = $request;
}
public function client() {
return Client::find($this->request->session()->get('client_id'));
}
public function auth() {
if (Auth::check()) {
// Get the client
$client = $this->client();
// Get the client's user
$user = $client->users()->find(Auth::user()['id']);
// Get the rank of the logged in user
$rank = Rank::find($user->pivot->rank_id);
return [
'user' => $user,
'rank' => $rank,
'client' => $client
];
}
return null;
}
}
This is responsible for doing what I described, returning additional data that I can't get through Auth::user(). Now I'm trying to register this class in the AuthServiceProvider
public function register()
{
// Register client auth
$request = $this->app->request;
$this->app->singleton(ClientUser::class, function ($app) {
return new ClientUser($request);
});
}
Now what I don't understand is how I'm supposed to make this globally accessible throughout my app like Auth::user() is.
The problem with just making "importing" it is that it needs the request object, which is why I'm passing it through the service container.
Now here's where I'm stuck. I'm not able to access app in my controller or anywhere, and I can't define a Facade because a Facade expects you to return a string of the bound service that it should "alias?"
Change your service provider like this :
$this->app->bind('client.user', function ($app) {
return new ClientUser($app->request);
});
Create another class extended from Illuminate\Support\Facades\Facade.
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ClientUserFacade extends Facade {
public static function getFacadeAccessor(){
return "client.user";
}
}
Add 'ClientUser => ClientUserFacade::class in alias key of app.php
I am using Slim Framework 3. I want to inject $logger defined in dependencies.php into a Router Controller class. Below is what I do, is there a better way?
routes.php
$app->get('/test', function($request, $response, $args){
$controller = new AccountController($this->get('logger'));
return $controller->test($request, $response, $args);
});
AccountController
class AccountController{
private $logger;
function __construct($logger){
$this->logger = $logger;
}
public function test($request, $response, $args){
$this->logger->info('i am inside controller');
return $response->withHeader('Content-Type', 'application/json')->write('test');
}
}
In Slim Framework 3 documentation, the proper way of using a Route Controller should be:
$app->get('/test', 'AccountController:test');
But how do I inject $logger into AccountController when I choose to code my Route Controller in this more "elegant" way?
In terms of making your controller easier to test, you should inject the logger into the controller via the constructor.
AccountController looks like this:
class AccountController
{
protected $logger;
public function __construct($logger) {
$this->logger = $logger;
}
public function test($request, $response, $args){
$this->logger->info('i am inside controller');
return $response->withJson(['foo' => 'bar']);
}
}
Set up in index.php is something like:
$container = $app->getContainer();
$container[Logger::class] = function ($c) {
$logger = new \Monolog\Logger('logger');
return $logger;
};
$container[AccountController::class] = function ($c) {
$logger = $c->get(Logger::class);
return new AccountController($logger);
};
$app->get('/test', 'AccountController:test');
Note that if you make the format route callable be a string of 'class name' colon 'method name', then Slim 3 will call the method for you after extracting the controller class from the DI container. If the class name is not a registered key with the container, then it will instantiate it and pass the container to the constructor.
According to the container resolution docs, you should be able to access your logger through the container, inside your controller:
AccountController
class AccountController
{
protected $ci;
//Constructor
public function __construct(ContainerInterface $ci)
{
$this->ci = $ci;
}
public function test($request, $response, $args)
{
$this->ci->get('logger')->info('i am inside controller');
return $response->withHeader('Content-Type', 'application/json')->write('test');
}
}
When you call $app->get('/test', 'AccountController:test');, Slim should automatically pass the container into AccountController's constructor.
That being said, this is more of a convenience feature than an example of great design. As Rob Allen explains in his answer, you can achieve better modularity, and thus more easily tested code (if you're using unit tests), by injecting the controllers into the application container, rather than injecting the container into each controller.
Take a look at his example Slim application. If you look at, for example AuthorController, you can see how with this design controller classes no longer depend on the magical container providing all the services. Instead, you explicitly state which services each controller will need in the constructor. This means you can more easily mock the individual dependencies in testing scenarios.
I have created a simple aplication in Silex 1.3.4 and I want to have a base controller that will have a __construct method accepting $app and $request. All inheriting controllers then should have their respective constructors and calling the parent controller construct method.
//Use statements here....
class AppController
{
public function __construct(Application $app, Request $request){
$this->app = $app;
$this->request = $request;
}
}
Inheriting controllers would be written as below:
//Use statements here....
class ItemsController extends AppController
{
public function __construct(Application $app, Request $request){
parent::__construct($app, $request);
}
public function listAction()
{
//code here without having to pass the application and request objects
}
}
The approach I have decided on routing is as shown below:
$app->post(
'/items/list', 'MySilexTestDrive\Controller\ItemsController::listAction'
)->bind('list');
I was thinking of using the dispatcher and override some processes there and create my controller instances my own way but I do not have any idea how and if this is a great idea at all.
Anyone who has done something similar to this? Please help.
You can use ServiceControllerServiceProvider to define your controller as a service in the application. But you can't inject a Request in that way. BTW you can have more than one request and the request instance can change if you do sub-request. You can inject RequestStack instead, then call $requestStack->getCurrentRequest() when you need to get the current request.
$app = new Silex\Application();
abstract class AppController
{
protected $app;
protected $requestStack;
public function __construct(Silex\Application $app, Symfony\Component\HttpFoundation\RequestStack $requestStack)
{
$this->app = $app;
$this->requestStack = $requestStack;
}
public function getRequest()
{
return $this->requestStack->getCurrentRequest();
}
}
class ItemsController extends AppController
{
public function listAction()
{
$request = $this->getRequest();
// ...
}
}
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app['items.controller'] = $app->share(function() use ($app) {
return new ItemsController($app, $app['request_stack']);
});
$app->get('/items/list', "items.controller:listAction");
It makes sense to do such a thing? I do not think so. Especially if the framework gives you a request instance thanks to the type hinting. Just do
public function listAction(Application $app, Request $request)
{
// ...
}
and work with that.
You can try this too :
class BaseController
{
protected $app;
protected $request;
public function __call($name, $arguments)
{
$this->app = $arguments[0];
$this->request = $arguments[1];
return call_user_func_array(array($this,$name), [$arguments[0], $arguments[1]]);
}
protected function getSystemStatus(Application $app, Request $request)
{
[...]
}
[...]
}
#Rabbis and #Federico I have come up with a more elegant solution for this where I have created a BeforeControllerExecuteListener that I dispatch with my application instance. This listener accepts the FilterControllerEvent object and then from my base controller I call a method where I inject both the Silex Application and the request from the event.
public function onKernelController(FilterControllerEvent $event)
{
$collection = $event->getController();
$controller = $collection[0];
if($controller instanceof BaseControllerAwareInterface){
$controller->initialize($this->app, $event->getRequest());
}
}
The I simple dispatch this in my bootstrap file as shown below:
$app['dispatcher']->addSubscriber(new BeforeControllerExecuteListener($app));
This allows me to have access to this object without having to add them as parameters on my actions. Below is how one of my actions in the making looks:
public function listAction($customer)
{
$connection = $this->getApplication()['dbs']['db_orders'];
$orders= $connection->fetchAll($sqlQuery);
$results = array();
foreach($orders as $order){
$results[$order['id']] = $order['number'] . ' (' . $order['customer'] . ')';
}
return new JsonResponse($results);
}
If the currently running controller being called honors the BaseControllerAwareInterface interface as I have defined it then it means I should inject that controller with the Application and Request instances. I leave the controllers to deal with how they manage the Response of each action as with my example above I may need the Response object itself of JsonResponse even any other type of response so it entirely depends on the controller to take care of that.
Then the routing remains as simply as:
$app->match('/orders/list/{cusstomer}', 'Luyanda\Controller\OrdersController::listAction')
->bind('list-orders');