I am creating an application with Silex and was wondering if it is possible to somehow get the instance of the Silex\Application in a place where I can't do method_name(Application $application) in the method parameters?
For example, say I have a private method on a controller that is not an action. If I put Application $application as the parameter, it throws an error saying I need to pass it in.
I would rather not have to manually pass that method in if I don't have to.
There's really only two ways to do it.
a) Pass Silex\Application as an argument to the constructor of your class and assign it as an instance variable.
b) Pass the Silex\Application to your private method as an argument by hand.
Are you sure you need the full app in your class? The point of dependency injection is to inject dependencies directly, instead of injecting the container (yes, Silex\Application extends \Pimple, which is a Dependency Injection Container.
From your comment on the other answer, your goal of getting at the Silex/Application is to get at the Twig service there. The way that I've solved getting at the current application in another function for my Silex projects is:
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
class myController {
private $a;
// Route defined as:
// $app->get('/foo', 'myController::showPage');
public function showPage(Application $a) {
$this->a = $a;
return $this->doAwesome();
}
private function doAwesome() {
$twig = $this->a['twig'];
return $twig->render('awesomePage.twig');
}
}
Every function that is an endpoint for a route would then save the Application passed to it as a class property, for other functions to get at. It means you have to remember to do $this->a = $a; in every function that uses doAwesome() (before calling doAwesome()), but that's the cleanest way I've come up with to tackle that.
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());
}
Consider this code:
<?php namespace App\Services;
use App\Services\AnotherService;
class SomeService {
public function someMethod() {
$anotherService = \App::make(AnotherService::class);
}
}
My intention is to get a class object with all its dependencies resolved. But in this specific case, I would like that the object's __construct has been executed as well. I have tried and by code above, the $anotherService object's __construct method is not executed.
Therefore I can achieve what I need by doing -
$anotherService = \App::make(AnotherService::class);
$anotherService->__construct();
Can it be done with single line instead of redundantly each time call construct method after instantiating the object? - Because that is what __construct method is made for - executing automatically. But I have noticed that for some reason Laravel's automatic dependency resolving skips the __construct execution.
Note, that new AnotherService() is not an option for me, as well as using the __construct method of SomeService class. I would like to make an object inside the someMethod method.
Class AnotherService currently does not have any dependencies. It has just some random variable updates inside the __construct, like:
public function __construct() {
$this->varA = true;
$this->varB = 'Some Value';
}
Why do I need to resolve this class (instead of using new AnotherClass())? - Simply because I want to test this call in unit tests by mocking it. For that I use this code:
$this->mock(AnotherService::class, function ($mock) {
$mock->shouldReceive('anotherMethod')->andReturn(false);
});
And then finally I have to call the method to check the response:
$anotherService = \App::make(AnotherService::class);//<-- In this moment $varA and $varB are not set because the __construct did not execute!
$response = $anotherService->anotherMethod();
// Assert...
So, in this case, for example, if anotherMethod would use any of those variables, the tests would be incorrect because their values are not set.
I believe there is either a mistake somewhere in your code or some misunderstanding.
The problem here is not in Laravel. The key here is the usage of Mockery. When creating a simple Mock for a class, its constructor is not invoked, and it's just a mock. When using $this->mock(...), you just bind a concrete instance (mock) in the container. Thus, when calling its make, that ready mock is returned. Meaning, that __construct is neither invoked by Mockery, nor by Container.
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
I have 2 classes: User and Router
In my script, class User is instantiated first to set user data, then class Router is instantiated to set page data.
$user = new User();
$router = new Router();
Inside one of Router's methods, I need to invoke $user->getSuperPrivileges(). This function queries the DB and sets extra parameters in the $user object, then returns them.
I could pass $user as a parameter of Router($user) and save it as a property in the construct function, but I believe this would only create a clone of the real object. Problem with this is that the values set by $this->user->getSuperPrivileges() would only be accessible by the clone, and not in the global script by the real object. In other words, I would need to invoke the getSuperPrivileges() method once again in the real object to set these properties again, which is counterproductive.
What is the best way to achieve what I want (access the real object $user and its methods from inside $router, without having to create a clone passed as a function parameter)?
As pointed out below by #hek2mgl, in php5 every object variable is a reference. The __construct magic method would not work at all prior to that anyway so we can assume that OPs example should work regardless.
http://3v4l.org/6dKL0
The following lines are really pointless given the above example.
have you tried passing the $user object as a reference?
class Router{
function __contruct(&$user){
$this->user=$user;
}
}
new Router($user);
in that case how about a singleton?
function user(&$userO){
static $user;
if(!is_array($user)) $user=array();
if(is_object($userO)) $user[$userO->uid]=$userO;
if(is_string($userO)) return $user[$userO];
}
class Router{
function __construct($user){
$this->uid=$user->uid;
}
function __get($k){if($k=='user') return user($this->uid);}
}
To explain a little more, the user function stored the user objects, keyed by a unique identifier in a static array, the __get magic method allows you to intercept calls to the user property on the router object and return the statically saved object from the singleton function.
You can create the $user object and inject it into $router object using constructor injection. But what you are doing should be just fine. You should be able to use that object for whatever you need within your router class. Especially if the database maintains the privilege state.
If you must use only one instance of the class check out the section on Singleton patterns at: http://www.phptherightway.com/pages/Design-Patterns.html and you can get an idea of how to achieve this.
I'd try and apply the Dependency Injection pattern. The point is that methods should be passed all they need to operate.
Meaning the method in your router which operates on a user should be passed said user.
class Router {
method privilegiateUser(User $user) {
// notice the typehint
// php will enforce that your method receives a User
$user->getSuperPrivileges();
}
}
I'd disapprove passing the User to your Router's __construct() if it's to be used only once and not with each script run. Think about it that way:
Is a User a property of a Router in the same way than a Name is a property of a User?
In PHP laravel, we have codes like
$user = User::find(1);
var_dump($user->name);
I am not concerning how to use the find method, I am concerning why laravel use a static method? Shouldn't the use of static method make the method hard to test?
Will it be better if they designed using singleton?
e.g.
$user = User::getInstance()->find(1);
var_dump($user->name);
In fact, your example is very similar to what Laravel does behind the scene. When you do User::find(), you are actually asking for a new instance, either an instance of Collection or a QueryBuilder.
Illuminate\Database\Eloquent\Model (reference):
public static function find($id, $columns = array('*'))
{
if (is_array($id) && empty($id)) return new Collection;
$instance = new static;
return $instance->newQuery()->find($id, $columns);
}
As a side note, you'll also see another way of using static methods in Laravel e.g. Input::get(). These are called Facades.
Facades provide a "static" interface to classes that are available in the application's IoC container ... Laravel "facades" serve as "static proxies" to underlying classes in the IoC container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.
When a user references any static method on the ... facade, Laravel resolves the cache binding from the IoC container and runs the requested method (in this case, get) against that object.
You can read more about Larave's Facades at: http://laravel.com/docs/facades
Unnawut has a very good answer, however I felt it necessary to add in further explanation.
In your example
$user = User::find(1);
var_dump($user->name);
Laravel isn't using a static method, you are. Another way to do this which you are probably looking for is to use dependency injection, which Laravel makes very easy because it can be done automatically. So in whatever class you are using your User model in, you should be setting up something like this in the constructor...
public function __construct(User $user)
{
$this->user = $user;
}
And then you can modify your code to not use the static bindings.
$user = $this->user->find(1);
var_dump($user->name);
This would restrict the system from only having one User. Whilst the find method may be static, the User class will have other methods and properties that aren't, a likely example is in your example: $user->name
A method that does not rely upon any instance variables, I.e variables who's value is specific to the particular object instance, but instead provides generic functionality that applies to all instances, can, and probably should, be static. This is why the $this operator is illegal within static methods, as it can make no reference to a particular object instance.
#unnawut reference link to the source code of Model.php is no longer using static find function as the class has been refactored. The function that get called eventually is in Builder.php (source code),
The logic still same
- magic function `__callStatic` of Model class get called
- a new instance of Builder is created and return
- call the find method of the Builder instance
Model.php
public function newEloquentBuilder($query)
{
return new Builder($query);
}
Builder.php
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
return $this->whereKey($id)->first($columns);
}