I want to be able to call a method inside a controller in the following manner:
App::call('SomeController#method');
I figured it happens like this when defining a route:
Route::get('/route', 'SomeController#method');
So maybe there's an underlying mechanism I can use more generally in my code on other places in the project, turns out there is (App::call()).
The problem I'm running into is that this generates an error:
ReflectionException (-1)
Class SomeController does not exist
## \vendor\laravel\framework\src\Illuminate\Container\Container.php
public function build($concrete)
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
$reflector = new ReflectionClass($concrete);
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface of Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
I'm pretty sure I must include some stuff somewhere but since Laravel is pretty big I'm asking this community.
Try giving the full namespace like:
App::call('App\Http\Controllers\SomeController#method');
Another way is:
app()->call(['\App\Http\Controllers\SomeController', 'index']);
where index is the name of the method. You can add a second parameter to send some data like so:
app()->call(['\App\Http\Controllers\SomeController', 'index'], [
'name' => 'Strawberry'
]);
You can call the method by using the app() helper. Syntax is app($fullControllerClassName)->methodYouWantToCall():
app('App\Http\Controllers\SomeController')->method();
Related
In my laravel project I have following interface, repository and controller.
This is Interface
interface TrainingClassTypeInterfaces
{
public function updateTrainingClassType($id, $request);
}
This is Repository
use App\Models\Trainings\AppTrainingClassType;
class TrainingClassTypeEloquent implements TrainingClassTypeInterfaces
{
protected $model;
public function __construct(AppTrainingClassType $appTrainingClassType)
{
$this->model = $appTrainingClassType;
}
public function updateTrainingClassType($id, $request)
{
$response = false;
$isUpdated = $this->model::where('training_class_id',$id)->update([
'app_id' => $request->app_id
]);
....
}
}
This is controller
class TrainingClassTypesController extends \TCG\Voyager\Http\Controllers\VoyagerBaseController
{
protected $trainingService;
public function __construct(TrainingClassTypeEloquent $trainingClassTypeInterfaces) {
$this->trainingService = $trainingClassTypeInterfaces;
}
public function insertOrUpdate()
{
...
$this->trainingService->updateTrainingClassType($id, $request);
..
}
}
Everything working fine till here
As you can see I am using TrainingClassTypeEloquent's method inside TrainingClassTypesController. But it was returning error something like
Argument 1 passed to ...::__construct() must be an instance of
Basically it was asking me to put instance of Model into TrainingClassTypeEloquent class. Then I did as following
$TCTypes = new AppTrainingClassType();
$TCT = new TrainingClassTypeEloquent($TCTypes);
$TCT->updateTrainingClassType($id, $request);
which was working fine but I was confused that this approach is not proper, there should be some proper way.
After googling I found another solution which is singleton binding, and then I tried following in AppServiceProvider
$this->app->singleton(
\App\Services\Voyager\Eloquent\TrainingClassType\TrainingClassTypeInterfaces::class,
\App\Services\Voyager\Eloquent\TrainingClassType\TrainingClassTypeEloquent::class
);
After adding this singleton binding, I notice script was working without providing model instance into TrainingClassTypeEloquent class.
I would like to know how $this->app->singleton() is working, so in this way my concept would be clear about it. If someone knows then kindly guide me about it.
Thank you so much
It is all about BINDING a service to the service container.
What does $this->app->singleton(); method do?
The singleton method binds a class or interface into the service container so that Laravel can maintain dependency (when using an interface as the constructor parameter).
(Actually Singleton is a design pattern. Singleton implementation always returns the same object on subsequent calls instead of a new instance). So $this->app->singleton(); method returns the same object again and again.
Point to be noted that Laravel doc says:
There is no need to bind classes into the container if they do not
depend on any interfaces. The container does not need to be instructed
on how to build these objects, since it can automatically resolve
these objects using reflection.
But your controller class depends on an interface, so the container needs to be informed and to do this, you need to use this $this->app->singleton(); method but there are other ways around.
Again, at the same time, this TrainingClassTypeEloquent::class has a dependency of AppTrainingClassType::class. But in this case, we do not need to worry about that because Laravel uses Reflection API to maintain its dependency as this class does not use interface as like TrainingClassTypesController::class class.
Once you are done with binding the service to the container, Laravel will then automagically put the service onto the constructor method as an argument where the interface is used.
I hope this would help you. You may find more help from this answer.
You need to register TrainingClassTypeEloquent
$this->app->singleton(TrainingClassTypeInterfaces::class, static function ($app) {
return new TrainingClassTypeEloquent(new AppTrainingClassType());
});
Then you can inject it in your Controller
public function insertOrUpdate(TrainingClassTypeInterfaces $trainingService, $id)
{
$trainingService->updateTrainingClassType($id, request());
}
I can't find my mistake. I am getting the follow TypeError when I try a route in my slim api.
the error is:
Argument 1 passed to
HCC\API\Controllers\FacultyController::__construct() must be an
instance of PDO, instance of Slim\Container given
the constructor for this controller is:
public function __construct(\PDO $db, \MongoDB\Client $mongo, \Monolog\Logger $applogger, \Monolog\Logger $seclogger)
and the DI Factory that I put into the container is:
$container['FacultyController'] = function($c) {
return new FacultyController($c->get('db'), $c->get('mongo'), $c->get('appLogger'), $c->get('secLogger'));
};
I have tried setting each to it's own variable and passing in those variables but same effect. I've ran a successful test that just loads the slim app and checks that the container has an object of class FacultyController and that it has the messages that I have one that controller so I am 99% sure that the controller is actually getting put into the container. I think something might be off with the route. I have both a construct and an invoke method in the controller that are the exam same.
I've found this error in other posts, but what I am finding is issues with not passing something to the construct method and this seems to be the wrong arguments being passed to mine.
I don't want to have to pass the entire container into every controller as these controllers only need set dependencies and there is a lot of unnecessary items in there as far as the controllers are concerned.
This is mostly because Slim can not find HCC\API\Controllers\FacultyController class in dependency container (because you registered it with string 'FacultyController' instead of fully qualified name of class).
When Slim can not find it in dependency container, by default, Slim tries to create HCC\API\Controllers\FacultyController on its own and pass container instance into FacultyController constructor. But because you declare constructor of FacultyController with typehint to PDO class, PHP complain about this type mismatch.
Solution is try to replace 'FacultyController' with full name including namespace to make Slim can find controller in dependency container.
So instead of,
$container['FacultyController'] = function($c) {
return new FacultyController(
$c->get('db'),
$c->get('mongo'),
$c->get('appLogger'),
$c->get('secLogger')
);
};
you should use
$container[\HCC\API\Controllers\FacultyController::class] = function($c) {
return new \HCC\API\Controllers\FacultyController(
$c->get('db'),
$c->get('mongo'),
$c->get('appLogger'),
$c->get('secLogger')
);
};
or
use \HCC\API\Controllers\FacultyController;
$container[FacultyController::class] = function($c) {
return new FacultyController(
$c->get('db'),
$c->get('mongo'),
$c->get('appLogger'),
$c->get('secLogger')
);
};
Then in your route declaration, you may use, for example:
$app->get('/faculty', \HCC\API\Controllers\FacultyController::class);
More information about ::class
Update
If you use code above, FacultyController is considered as invokable class, which means, it is expected to have __invoke() method implemented.
class FacultyController
{
public function __invoke($request, $response, $args)
{
//handle the request
}
}
If you do not want to use invokable class but ordinary method to handle request, include method name when setup route
$app->get('/faculty', \HCC\API\Controllers\FacultyController::class . ':getFacultyCollection');
getFacultyCollection() method will be called to handle request.
class FacultyController
{
public function getFacultyCollection($request, $response, $args)
{
//handle the request
}
}
If getFacultyCollection() call causes application to crash as you said in comment, then it is entirely different problem. Maybe you have unterminated loop?
More information about __invoke() magic method
Looking at Laravel code I found they are passing variable from 'routes' to 'views' using the following method:
$arraysan = ['mike','robert','john']; **//Variable to be passed**
return view('home')->withArraysan($arraysan); **//Variable passed with** name "withArraysan"
In that above syntax they call a function named withArraysan which doesn't exist.
Can somebody explain how its been handled in Laravel?
For a while now, PHP has had the concept of magic methods - these are special methods that can be added to a class to intercept method calls that do not exist.
It appears that Laravel Views implement __call - this then intercepts a call to an undefined method on the object, and is passed both the name of the method being called as well as the arguments. In this way, the View object can then see that the withArraysan call began with and call the concrete method with, passing the second part Arraysan as the first argument, and the argument to withArraysan as the second part.
If I've got your question then in Laravel they had a class View using magic method __call to handle the above function and the code for that function is like as follows
public function __call($method, $parameters)
{
if (Str::startsWith($method, 'with')) {
return $this->with(Str::snake(substr($method, 4)), $parameters[0]);
}
throw new BadMethodCallException("Method [$method] does not exist on view.");
}
And you can find this within
your_project_folder/vendor/laravel/framework/src/Illuminate/View/View.php
$arraysan = ['mike', 'robert', 'john']; // Variable to be passed
return view('home')->with('AnyVariable', $arraysan);
Try this! This will work.
Also check in home.blade.php,
<?php
print_r($AnyVariable);die;
?>
I have several classes handling data validation and API requests preparation for every external API function. An example of those:
class PostProductFunction($user_params)
{
validate()
{
//...
}
invoke($user_params)
{
$this->validate();
//doing request...
}
}
I have an APIAccount class to represent one of several API accounts. It handles auth and it has a method
function invokeFunction($functionClassName, $user_params)
{
// check if the class $functionClassName exists and creates an instance of it
// ... $obj = new $functionClassName();
$obj->invoke($user_params);
}
So, the function class doesn't know about auth stuff and the APIAccount class doesn't know about user data structure.
The question is how to handle this $functionClassName inside the APIAccount class. Do I need to store all names of function classes somewhere? Do I need some kind of enum class? I don't want to simply take a string and then check whether the class with this name exists because the programmer passing this string can easily mistype, and in general he needs documentation to know the proper function name. I want that he somehow see all available options with something like enum. Do you have any ideas how to better implement it?
I'm not sure you need a design pattern for this. The following should satisfy your needs:
function invokeFunction($functionClassName, $user_params)
{
if (! class_exists($functionClassName)) {
throw new Exception("Class {$functionClassName} does not exist.");
}
$obj = new {$functionClassName}();
$obj->invoke($user_params);
}
I figured out how to both keep the invocation method signature and make it easier for programmers to see the hint with possible class names to pass in IDE.
The method itself remains as it is:
function invokeFunction($functionClassName, $user_params)
{
// check if the class $functionClassName exists and creates an instance of it
if (! class_exists($functionClassName)) {
throw new Exception("Class {$functionClassName} does not exist.");
}
$obj = new $functionClassName();
$obj->invoke($user_params);
}
However, in the classes which names can be passed as $functionClassName one needs to add a static method like this:
public static function getFunctionName()
{
$function_name = get_called_class();
// .... some optional operations with the name depending on what part of
// (or the full name) invokeFunction is anticipating
return $function_name
}
Then calling the invokeFunction is as simple as:
invokeFunction (SomeCertainFunctionClass::getFunctionName(), array('some_param'=>'some_val'));
Using this approach you can have your IDE hint when typing 'SomeCertainFunctionClass' and at the same time you can use a class name string as a function argument without any downfalls.
As far as I've understood, I should be able to type-hint my class instance arguments in the constructor of my class that I only instantiate with help of a Service Provider. Unfortunately I'm getting an error about missing arguments.
Tracker.php
function __construct($query, Tracker\Shipment $shipment) {
$this->query = $query;
$this->shipment = $shipment;
}
TrackerServiceProvider.php
class TrackerServiceProvider extends \Illuminate\Support\ServiceProvider {
public function register() {
$this->app->bind('TrackerAPI', function($app, $shipment_number) {
return new Tracker\API($shipment_number); // omitting Shipment argument should resolve on its own?
});
}
}
Class API is extending Tracker, thus using its constructor. Why isn't it resolving without the implied class type hint?
You've confused the PHP new functionality with Laravel's dependency injection via type hinting functionality.
In your TrackerAPI binding, when you specify return new Tracker\API($shipment_number);, there is nothing built into PHP to try to flesh out any missing parameters. To do this, you would need to return $app->make('Tracker\API', [$shipment_number]);
When you use the App::make() method, Laravel will do its magic to create a new object. It first checks for an explicitly defined binding. If it doesn't find an explicit binding, it will fall back to just using a combination of the supplied parameters plus type hints to create objects for any missing parameters.
Take the following statement: App::make('Tracker\API', [$shipment_number]). Assume there is no explicitly defined binding for Tracker\API. The Tracker\API constructor requires two parameters. $shipment_number is passed in in the parameter array, so it will be used as the first parameter. Since only one parameter was supplied, Laravel will App::make() the rest of the parameters using the type hints.
However, when you specifically instantiate the object yourself using the new keyword, none of this happens. It is a plain PHP object instantiation.
Nope. Laravel can't resolve partials like that. You have to be explicit:
use Tracker\API;
use Tracker\Shipment;
use Illuminate\Support\ServiceProvider;
class TrackerServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind('TrackerAPI', function($app, $shipment_number)
{
return new API($shipment_number, $app[Shipment::class]);
});
}
}