I'm using Symfony DI, Http, Kernel, Routing in my project following Create your own PHP Framework (https://symfony.com/doc/current/create_framework/index.html). It's fine and ok with is_leap_year App demo purpose.
I'm trying to figure out how I can inject services and container, into a custom controller defined in routes using only Symfony components, not framework bundle.
container.php
// add demo service into the service container
$containerBuilder->register('demo.service', '\Demo\DemoService');
// add dependent service into the controller container
$containerBuilder->register('dependent.controller', '\Demo\DemoController')
->setArguments([new Reference('demo.service')]);
// fetch service from the service container
// Echoing Works fine! But no service in controller
//echo $containerBuilder->get('dependent.controller')->helloWorld();
App.php
// nok with dependency
$routes->add('hello', new Routing\Route('/hello', [
'_controller' => 'Demo\DemoController::helloWorld',
]));
// ok with no dependency
$routes->add('hello2', new Routing\Route('/hello2', [
'_controller' => 'Demo\DemoService::helloWorld',
]));
And DemoController.php
<?php declare(strict_types=1);
namespace Demo;
class DemoController
{
private $demo_service;
public function __construct(\Demo\DemoService $demoService)
{
$this->demo_service = $demoService;
}
public function helloWorld()
{
return $this->demo_service->helloWorld();
}
}
Returns
Fatal error: Uncaught ArgumentCountError: Too few arguments to function Demo\DemoController::__construct(), 0 passed
How can I get this working? Or, how can i inject container into the Controller Constructor ?
Example here https://github.com/Monnomcjo/symfony-simplex
You actually got pretty close so well done there. I think you mixed ContainerAwareInterface with the ContainerControllerResolver suggestion. Two different concepts really. Looks like you also tried making your own ControllerResolver class but you did not update container.php to use it. In any event, there is no need for a custom resolver at this point.
I also accidentally misled you with the suggestion that there was a service called 'container'. It is actually called 'service_container'.
# container.php
$containerBuilder->register('container_controller_resolver', HttpKernel\Controller\ContainerControllerResolver::class)
->setArguments([new Reference('service_container')]);
$containerBuilder->register('framework', Framework::class)
->setArguments([
new Reference('dispatcher'),
new Reference('container_controller_resolver'), // Changed
new Reference('request_stack'),
new Reference('argument_resolver'),
//new Reference('demo.service'), // No need for these
//new Reference('dependent.controller'),
])
;
Also, by convention, some of the framework services use id's like 'framework' or what not. But most of the time your application level stuff should just use the class name as the service id. In particular your need to use the controller class name to allow the ContainerControllerResolver to find it. It will also be useful when you venture into using the automatic wiring capabilities of the container.
// add demo service into the service container
$containerBuilder->register(\Demo\DemoService::class, \Demo\DemoService::class);
// add dependent service into the controller container
$containerBuilder->register(\Demo\DemoController::class,\Demo\DemoController::class)
->setArguments([new Reference(\Demo\DemoService::class)]);
And that should do it. Enjoy.
Related
i searh and read a lot of same question but ever i got the same error :/
I create a service:
parameters:
tbackend.report.class: T\BackendBundle\Entity\Report
services:
tbackend.entity.report:
class: %tbackend.report.class%
arguments: ["%kernel.root_dir%"]
And i has this in T\BackendBundle\Entity\Report:
public function __construct($rootDir){
$this->rootDir = $rootDir;
}
When i try to create new Report(); i receive this msg:
Warning: Missing argument 1 for T\BackendBundle\Entity\Report::__construct(), called in /var/www/test/t_study/src/T/BackendBundle/Entity/ReportRepository.php on line 62 and defined
Considerations: i know the services.yml is called, i has more services in this file and all work ok (Loginhandlers, etc), i only add one more (tbackend.entity.report)
What is wrong with that? :( I dont know if need more for know about the problem. I follow symfony2 service container guide
http://symfony.com/doc/master/book/service_container.html
Basically I try not to use DIR in the Entity when moving files
Ty
When instantiating a class, you use normal PHP. Symfony isn't some magic that hooks into the instantiating process of PHP to automatically inject things in the constructor.
If you want to get a service, you either have to inject the service in the class you need it or you have the containe rin the class (for instance, in the controller) and retrieve the service from the container.
$report = $this->container->get('tbackend.entity.report');
or: (which is a much better practice in all cases except from controllers)
class WhereINeedTheReport
{
private $report;
public function __construct(Report $report)
{
$this->report = $report;
}
}
services:
# ...
where_i_need_the_report:
class: ...
arguments: ["#tbackend.entity.report"]
I have a question! I have a library, whenever I need to call, I'll include it into & new Class() like the link below.
Now, I want to include it to use with Lumen framework & call usually inton Controller, then how to register service, class in Lumen to make it comfortable so that when needing, just call new FileMaker();
http://laravel.io/bin/E3d9x
Thank you so much!
What you're looking for is a Service Provider. Instead of including files in your Controllers, and then new'ing up instances of a class it is better to register the class within a service provider and then resolve the object out of the IoC container.
An example of how you can register a provider:
public function register()
{
$this->app->singleton('Full\Vendor\Namespace\FileMaker', function($app) {
return new FileMaker('someparameters');
});
}
Doing it this way means that you can inject dependencies into your Controllers and Laravel, or Lumen in this case will automatically resolve the object without you needing to instantiate the object.
For example, in your controller:
public function someControllerMethod(FileMaker $filemaker)
{
// The $filemaker instance is available to use
}
I would like to override the default View::make() method in Laravel, which can be used to return a view-response to the user.
(I think) I've already figured out that this method is stored inside Illuminate\View\Factory.php, and I've been reading about IoC container, while trying to make it work using some similar tutorials, but it just won't work.
I've already created a file App\Lib\MyProject\Extensions\View\Factory.php, containing the following code:
<?php namespace MyProject\Extensions\View;
use Illuminate\View\Factory as OldFactory;
class Factory extends OldFactory {
public static function make() {
// My own implementation which should override the default
}
}
where the MyProject folder is autoloaded with Composer. But I don't know how to use this 'modified' version of the Factory class whenever a static method of View (in particular View::make()) is being called. Some help would be great!
Thanks!
The call to View::make is, in Laravel speak, a call to the View facade. Facades provide global static access to a service in the service container. Step 1 is lookup the actual class View points to
#File: app/config/app.php
'aliases' => array(
'View' => 'Illuminate\Support\Facades\View',
)
This means View is aliased to the class Illuminate\Support\Facades\View. If you look at the source of Illuminate\Support\Facades\View
#File: vendor/laravel/framework/src/Illuminate/Support/Facades/View.php
class View extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'view'; }
}
You can see the facade service accessor is view. This means a call to
View::make...
is (more or less) equivalent to a call to
$app = app();
$app['view']->make(...
That is, the facade provides access to the view service in the service container. To swap out a different class, all you need to do is bind a different object in as the view service. Laravel provides an extend method for doing this.
App::extend('view', function(){
$app = app();
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new \MyProject\Extensions\View($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
Notice this is more than just instantiating an object. We need to instantiate it the same way as the original view service was instantiated and bound (usually with either bind or bindShared). You can find that code here
#File: vendor\laravel\framework\src\Illuminate\View\ViewServiceProvider.php
public function registerFactory()
{
$this->app->bindShared('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new Factory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
You can test if your binding worked with code like this
var_dump(get_class(app()['view']));
You should see your class name. Once your sure the binding has "taken", you're free to redefine whatever methods you want.
I'm trying to extend Laravel's Auth Guard class by one additional method, so I'm able to call Auth::myCustomMethod() at the end.
Following the documentation section Extending The Framework I'm stuck on how to exactly do this because the Guard class itself doesn't have an own IoC binding which I could override.
Here is some code demonstrating what I'm trying to do:
namespace Foobar\Extensions\Auth;
class Guard extends \Illuminate\Auth\Guard {
public function myCustomMethod()
{
// ...
}
}
Now how should I register the extended class Foobar\Extensions\Auth\Guard to be used instead of the original Illuminate\Auth\Guard so I'm able to call Auth::myCustomMethod() the same way as e.g. Auth::check()?
One way would be to replace the Auth alias in the app/config/app.php but I'm not sure if this is really the best way to solve this.
BTW: I'm using Laravel 4.1.
I would create my own UserProvider service that contain the methods I want and then extend Auth.
I recommend creating your own service provider, or straight up extending the Auth class in one of the start files (eg. start/global.php).
Auth::extend('nonDescriptAuth', function()
{
return new Guard(
new NonDescriptUserProvider(),
App::make('session.store')
);
});
This is a good tutorial you can follow to get a better understanding
There is another method you could use. It would involve extending one of the current providers such as Eloquent.
class MyProvider extends Illuminate\Auth\EloquentUserProvider {
public function myCustomMethod()
{
// Does something 'Authy'
}
}
Then you could just extend auth as above but with your custom provider.
\Auth::extend('nonDescriptAuth', function()
{
return new \Illuminate\Auth\Guard(
new MyProvider(
new \Illuminate\Hashing\BcryptHasher,
\Config::get('auth.model')
),
\App::make('session.store')
);
});
Once you've implemented the code you would change the driver in the auth.php config file to use 'nonDescriptAuth`.
Only way to add (and also replace existing functions) is to create copy of Guard.php file in your project and in app/start/global.php add:
require app_path().'/models/Guard.php';
Of course it's ugly method, but I spent over hour to test all possibilities [how to change things stored in Session by Auth] and it always end with error:
... _contruct of class HSGuard requires first parameter to be 'UserProviderInterface' and get 'EloquentUserProvider' ...
In a Symfony2 project, when you use a Controller, you can access Doctrine by calling getDoctrine() on this, i.e.:
$this->getDoctrine();
In this way, I can access the repository of such a Doctrine Entity.
Suppose to have a generic PHP class in a Symfony2 project. How can I retrieve Doctrine ?
I suppose that there is such a service to get it, but I don't know which one.
You can register this class as a service and inject whatever other services into it. Suppose you have GenericClass.php as follows:
class GenericClass
{
public function __construct()
{
// some cool stuff
}
}
You can register it as service (in your bundle's Resources/config/service.yml|xml usually) and inject Doctrine's entity manager into it:
services:
my_mailer:
class: Path/To/GenericClass
arguments: [doctrine.orm.entity_manager]
And it'll try to inject entity manager to (by default) constructor of GenericClass. So you just have to add argument for it:
public function __construct($entityManager)
{
// do something awesome with entity manager
}
If you are not sure what services are available in your application's DI container, you can find out by using command line tool: php app/console container:debug and it'll list all available services along with their aliases and classes.
After checking the symfony2 docs i figured out how to pass your service
in a custom method to break the default behavior.
Rewrite your configs like this:
services:
my_mailer:
class: Path/To/GenericClass
calls:
- [anotherMethodName, [doctrine.orm.entity_manager]]
So, the Service is now available in your other method.
public function anotherMethodName($entityManager)
{
// your magic
}
The Answer from Ondrej is absolutely correct, I just wanted to add this piece of the puzzle to this thread.