I am having some trouble with Symfony2. Namely in how to use the __construct() function. the Official Documentation is shockingly bad!
I want to be able to use the following:
public function __construct()
{
parent::__construct();
$user = $this->get('security.context')->getToken()->getUser();
}
How ever I get the following error:
Fatal error: Cannot call constructor in /Sites/src/DEMO/DemoBundle/Controller/Frontend/HomeController.php on line 11
Line 11 is "parent::__construct();"
I removed it and got the following, new error
Fatal error: Call to a member function get() on a non-object in /Sites/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php on line 242
I think I might need to set up the ContainerInterface DIC, but I have no idea how to do this (I tried and failed, miserably)
Any ideas folks?
Update - Tried changing to extend ContainerAware and got this error:
Fatal error: Class DEMO\DemoBundle\Controller\Frontend\HomeController cannot extend from interface Symfony\Component\DependencyInjection\ContainerAwareInterface in /Sites/src/DEMO/DemoBundle/Controller/Frontend/HomeController.php on line 43
Using the following code in the controller:
<?php
namespace DEMO\DemoBundle\Controller\Frontend;
use Symfony\Component\DependencyInjection\ContainerAware;
class HomeController extends ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
I'm assuming you are extending the default Symfony controller? If so, a look at the code will reveal the answer:
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware;
class Controller extends ContainerAware
{
Notice that there is no Controller::__construct defined so using parent::__construct will not get you anywhere. If we look at ContainerAware:
namespace Symfony\Component\DependencyInjection;
class ContainerAware implements ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
Again, no constructor and the container is not available until setContainer is called. So override setContainer and put your logic there. Or else just make a stand alone controller that does not extend the base controller class and inject your dependencies directly into the constructor.
Update Aug 2017
Still getting a few hits on this. If you really want to execute something before each controller then use a kernel controller listener. If all you need is the user then of course use getUser(). And please don't override setContainer(). In some cases it would work but it would just convolute your code.
I also frequently want an instance of the current User in most of my controllers. I find it is easiest to just do something like this:
class SomeController extends Controller
{
protected $user;
public function getUser()
{
if ($this->user === null) {
$this->user = $this->get('security.context')->getToken()->getUser();
}
return $this->user;
}
}
However, this is an overly simplistic example case. If you want to do more work before a Controller action is started, I suggest you define your Controller as a Service.
Also take a look at this article: Moving Away from the Base Controller
I have to retrieve the 'facade' manager for my rest api's resource. Not using the constructor and using a private function seems the easiest and simplest for me.
/**
* Class ExchangesController
* #RouteResource("Exchange")
*/
class ExchangesController extends Controller
{
/**
* Get exchange manager
* #return ExchangeManager
*/
protected function getExchangeManager()
{
return $this->get('exchange_manager');
}
/**
* #ApiDoc(
* description="Retrieve all exchanges",
* statusCodes={
* 200="Successful"
* }
* )
*/
public function cgetAction()
{
return $this->getExchangeManager()->findAll();
}
PS It's ok for me to use private/protected functions in my controller as long as it contains zero conditionals
You cannot call getUser() or get() for services in controller constructors. If you remember that, you will save lots of debugging time.
I know the question is very old, but I didn't found an answer until now. So I'll share it.
The goal here, is to execute a code everytime a action in our controller is called.
The __construct method doesn't work, because it's called before anything else, so you can't access the service container.
The trick is to overload each method automatically when they are called :
<?php
namespace AppBundle\DefaultController;
class DefaultController extends Controller {
private function method1Action() {
return $this->render('method1.html.twig');
}
private function method2Action() {
return $this->render('method2.html.twig');
}
public function __call($method, $args) {
$user = $this->get('security.tokenStorage')->getToken()->getUser();
// Do what you want with the User object or any service. This will be executed each time before one of those controller's actions are called.
return call_user_func_array(array($this, $method), $args);
}
}
Warning ! You have to define each method as a private method ! Or the __call magic method won't be called.
There are only two solutions to this problem:
Use a private method as pointed out by #Tjorriemorrie here. But this is a dirty method for purists. (I'm using this! :D );
Define the controller as a service, but this way you will lose all the shortcuts provided by Symfony\Bundle\FrameworkBundle\Controller\Controller. Here is the article that shows how to do this.
As told, personally, in my situation, I prefere a solution like this:
class MyController extends Controller
{
/** #var AwesomeDependency */
private $dependency;
public function anAction()
{
$result = $this->getDependency();
}
/**
* Returns your dependency.
*/
private function getDependency()
{
if (null === $this->dependency)
$this->dependency = $this->get('your.awesome.dependency');
return $this->dependency;
}
}
This is typically a class that I call MyManager where I put the code that I use in more than one action in the controller or that unusefully occupies lines (for example the code to create and populate forms, or other code to do heavy tasks or tasks that require a lot of code).
This way I mantain the code in the action clear in its purposes, without adding confusion.
Maybe the use of a property to store the dependency is an overoptimization, but... I like it :)
As i see, Controller extends ContainerAware, and if we take a look of ContainerAware it implements ContainerAwareInterface. So, ContainerAware must have declared the exact methods in it's interface. Add this line
public function __construct();
to the ContainerAwareInterface definition and it will be solved.
Related
I am using $this->id = $this->get('session')->get('id'); in my __construct() method and I get this error:
Call to a member function get() on null
I also tried it with $this->id = $this->container->get('session')->get('id'); but I get the same error.
The code will work if I use it in another method but not in __construct().
This is how the code looks like:
class ProfileDao extends AbstractController {
private $id;
private $em;
function __construct() {
$this->id = $this->get('session')->get('id');
$this->em = $this->getDoctrine()->getManager();
}
}
What am I doing wrong?
The functions ControllerTrait::get($id) (to fetch a service) as well as ControllerTrait::getDoctrine() (to fetch doctrine, which is also a service) are both done by accessing the container (see refs in case of doubt), which is set on AbstractController via AbstractController::setContainer($container) after it has been created (this used to be done because it implemented ContainerAwareInterface, which signaled to symfony's dependency injection component, it should get a container set, I don't know why/when this is done now... tbh).
And since a non-static method on an object (the AbstractController in this case) can only be called (from the outside) after the object has been created from its constructor, and since setContainer is a non-static method on an object, the AbstractController only can have a container after the constructor is done, but not while the constructor is running.
So that is the reason why those both method calls don't work.
The solution to your problem is quite simple, because what absolutely works is properly dependency injecting the classes you need:
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Doctrine\ORM\EntityManagerInterface;
class ProfileDao extends AbstractController {
private $id;
private $em;
function __construct(EntityManagerInterface $em, SessionInterface $session) {
$this->id = $session->get('id');
$this->em = $em;
}
}
In general, I avoid the container, because it absolutely hides the dependencies a controller has. Some dependencies I tend to use without injecting them explicitly (usually Twig and some HttpKernel/HttpFoundation stuff) because they are very commonly found/used in Controllers.
Another idea, even if I like the one about autowiring better: have you checked whether the parent constructor is of any help? If you extend a class (like you're doing with extends AbstractController), you should not forget calling parent::__construct(), maybe as the first thing in your own construct method. This ensures that everything that the parent class needs to work properly is instantiated.
This solution dosen't work in Symfony 6.x
I tested below solution in my Symfony 6.1 project.
At the my project I stored my default language in session as "lang"
private $lang;
public function __construct()
{
$session = new Session(); //use Symfony\Component\HttpFoundation\Session\Session;
$this->lang = $session->get('lang');
}
How you can $lnag value in whole your controler ?
public function index(): Response
{
echo $this->lang;
return ...
}
I am relatively new to Symfony (version 4) and trying to implement the __construct method for dependency injection.
Currently, I am "injecting" dependencies via my own implementation (before I was aware of the __construct method) like so:
routes.yaml
fetch:
path: /fetch/{req}
controller: App\Controller\Fetch::init
requirements:
req: ".+"
/fetch route calls the init() method, which serves as the constructor.
Controller Class
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use App\Services\Utilities; // a bunch of useful functions
class Fetch extends BaseController {
private $u;
public function init(Utilities $u) {
$this->u = $u; // set the $u member with an instance of $u
}
private function do_fetch(){
$this->u->prettyprint('hello service'); // use one of $u's methods
}
}
If you would indulge me, I came up with this ad-hoc scheme before reading the docs, which detail this almost exactly (I get a cookie).
The one difference is that the docs use __construct() in place of my init() method. The following is an example from the doc page linked above:
// src/Service/MessageGenerator.php
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function getHappyMessage()
{
$this->logger->info('About to find a happy message!');
// ...
}
}
But when I swap init() for __construct(), and update the routes.yaml, I get an error.
// .....
class Fetch extends BaseController {
private $u;
public function __construct(Utilities $u) {
$this->u = $u; // set the $u member with an instance of $u
}
// ....
fetch:
path: /fetch/{req}
controller: App\Controller\Fetch::__construct
requirements:
req: ".+"
Its asking me to provide an argument to __construct since that method takes one ($u) but this was not the case when init() was acting as the constructor.
Moreover, I feel like since the __construct() method is a built-in hook, Symfony should know to use it without my having to explicitly tell it to in routes.yaml. However, excluding it throws an error as well.
routes.yaml (__construct not explicitly indicated)
fetch:
path: /fetch/{req}
controller: App\Controller\Fetch
requirements:
req: ".+"
What am I missing here?
__construct is a magic method in PHP. The problem with your init method is that it does not enforce that the object must have an instance of the object you need in order to be built. Sometimes an object property will not be needed. In this case, I recommend creating a setter as a way to optional set that property.Try to make your class properties private, and only allow them to be mutated or retrieved through setters and getters...this will provide a standard API to your obejct, and avoid random state manipulation.
You can use the DIC in Symfony's router to construct your controller instead of extending the base controller class by registering your controllers as services. This greatly decouples you code and allows all kinds of additional flexibility. You should always favor composition over inheritance.
I want to swap out my client call or better i try to make a wrapper around this package, so i dont have to write this everytime, so i made a new ServiceProvider which should call
// Create a new client,
// so i dont have to type this in every Method
$client = new ShopwareClient('url', 'user', 'api_key');
on every request i make.
// Later after the Client is called i can make a Request
return $client->getArticleQuery()->findAll();
SwapiServiceProvider
<?php
namespace Chris\Swapi;
use Illuminate\Support\ServiceProvider;
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
}
/**
* Register any package services.
*
* #return void
*/
public function register()
{
$this->app->singleton(ShopwareClient::class, function () {
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
});
}
}
My Class
...
use LeadCommerce\Shopware\SDK\ShopwareClient as Shopware;
class Swapi
{
public function fetchAllArticles(Shopware $shopware)
{
return $shopware->getArticleQuery()->findAll();
}
}
Testing
I just call it in my routes.php for testing
use Chris\Swapi\Swapi;
Route::get('swapi', function () {
// Since this is a package i also made the Facade
return Swapi::fetchAllArticles();
});
But i get everytime the error
FatalThrowableError in Swapi.php line 18: Type error: Argument 1
passed to Chris\Swapi\Swapi::fetchAllArticles() must be an instance of
LeadCommerce\Shopware\SDK\ShopwareClient, none given, called in
/Users/chris/Desktop/code/swapi/app/Http/routes.php on line 7
So i am asking why this
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
is not called everytime i call a method e.g $shopware->getArticleQuery()->findAll();
Does anyone know why?
I think there might be some confusion here about Laravel's IoC. When you use return Swapi::fetchAllArticles();, Laravel doesn't know what you are doing because you haven't used the container to build out the Swapi class (even though you have registered one with the container) nor do you have a facade built to access it in that manner. Otherwise PHP is going to complain because your function isn't static.
I just wrote this code and verified that it works as far as Laravel putting it all together.
In my service provider, my register function was this...
public function register()
{
$this->app->singleton('swapi', function($app) {
return new SwapiRepository(
new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
)
);
});
}
Keep in mind, swapi is really just a key the container will use to find the actual class. There's no need to pass in the entire qualified class name when you can keep it simple and easy.
My SwapiRepository which is really the wrapper for the Shopware SDK.
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiRepository
{
protected $client;
public function __construct(ShopwareClient $client)
{
$this->client = $client;
}
public function fetchAllArticles()
{
return $this->client->getArticleQuery()->findAll();
}
}
At this point, you are basically done. Just add App\Providers\SwapiServiceProvider::class, in the providers array (which you probably have done already) in app/config.php and use your wrapper like so...
$swapi = app('swapi');
$swapi->fetchAllArticles();
Or you can have Laravel inject it into other classes as long as Laravel is building said class.
If you want to build out a facade for this to save yourself a line of code each time you want to use this or for snytactical sugar...
use Illuminate\Support\Facades\Facade;
class Swapi extends Facade
{
protected static function getFacadeAccessor() { return 'swapi'; }
}
Make sure to update your aliases array in app/config.php so that it contains 'Swapi' => App\Repositories\Swapi::class,
And finally you should be able to use it like so...
Swapi::fetchAllArticles();
Please note your namespaces are different than mine so you may need to replace mine with yours. You should also now be able to easily inject Swapi into other classes and even method injected into your controllers where needed.
Just remember if you do that though, make sure you are grabbing instances of those classes from Laravel's service container using the app() function. If you try to build them out yourself using new SomeClass, then you have the responsibility of injecting any dependencies yourself.
EDITED (Code is updated and working for others)
For the overall idea of what's happening.
I'm trying to access post data from the view in the controller, without refreshing the page.
To do this I am executing the page controller by using a ViewHelper to call the Service below which then forwards back to the controller; afterwards I can manage the posted data in the page controller.
Everything works except the last step which is the forward(), I receive the error Call to undefined method AlbumModule\Service\postAlbumService::forward()
I understand I must implement the ServiceLocatorAwareInterface in order to use the forward() class, but what I've written doesn't seem to work.
<?php
namespace AlbumModule\Service;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class postAlbumService implements
ServiceLocatorAwareInterface
{
protected $services;
public function __construct() {
echo '<script>console.log("postAlbumService is Started")</script>';
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
public function test(){
$cpm = $this->getServiceLocator()->get('controllerpluginmanager');
$fwd = $cpm->get('forward');
echo '<script>console.log("postAlbumService TEST() is Started")</script>';
return $fwd->dispatch('newAlbum', array('action' => 'submitAlbum'));
}
}
It seems as though I'm just having a dependency issue with the forward() class, but I'm not sure what the issue is.
EDIT-
Here is how I am calling the postAlbumService from the viewHelper
<?php
namespace AlbumModule\View\Helper;
use Zend\View\Helper\AbstractHelper;
class invokeIndexAction extends AbstractHelper
{
protected $sm;
public function test()
{
$this->sm->getServiceLocator()->get('AlbumModule\Service\postAlbumService')->test();
}
public function __construct($sm) {
$this->sm = $sm;
}
}
Is there any way to call a specific class in the service being requested, after the dependencies are injected into the service?
You're doing a couple of things wrong and you're misunderstanding some things...
First of all, forward() is a ControllerPlugin. You'll gain access to this method by accessing said manager via the ServiceLocator. An example could be this:
$cpm = $serviceLocator->get('controllerpluginmanager');
$fwd = $cpm->get('forward');
return $fwd->dispatch('foo/bar');
Now, to get the ServiceLocator into any of your Service-Classes you need Dependency Injection. One of the ways is to implement the ServiceLocatorAwareInterface. The ServiceManager of ZF2 has so called Listeners. These Listeners check for implemented interfaces and stuff like this. Whenever it finds a match, it injects the required dependencies via the interfaces given functions. The workflow looks like this:
ServiceManager get('FooBar');
$ret = new FooBar();
foreach (Listener)
if $ret instanceof Listener
doInjectDependenciesInto($ret)
end
end
return $ret
Now what does this tell you. This tells you, that within the __construct() of any of your classes NONE of your required dependencies are actually there. They only get injected AFTER the class/service has been instantiated.
On a last side-note, the given code example doesn't really make much sense ;) No matter what ServiceAction i'd like to access, you'd always return me to the "newAlbum" action...
Intro
I'm developing an MVC framework, and I've run into a problem. It seems what I was trying to accomplish is known as the Singleton Design method -- initializing classes only once. Remember that I'm trying to put as less code in the controller "acontroller" as possible.
With that said, a final question remains: how can I add objects to an object that has already been instantialized?
It may help to have or at least see actual source instead of just example source, so I have pushed my source to my github. You can find that here: https://github.com/derekmaciel/uMVC
Code explanation
What's happening "under the hood" is first,
The Controller class loads a controller located in /application/controller, in this case "acontroller".
After, the acontroller class loads a model (called "amodel") using the Load class, using $this->load->model("amodel"), which was instantialized in the Controller __construct.
The final outcome of $this->load->model("amodel") is: $controller->amodel =& new Amodel(), where $controller is the Controller instance (not acontroller, because the controller loading the model will vary).
Step 4: Allow acontroller access to models that were loaded (amodel).
Code result
A copy of the current output of these scripts can be found here: http://pastebin.com/EJxuXaki
The first thing you'll notice is that I'm given a warning for using a deprecated assignment. I'm going to focus on the error for now.
The second thing you'll notice is that I first print_r()'d the Controller instance. Inside there is an amodel object, which is want to add to acontroller.
After that, I print_r()'d the $this (acontroller) object. It has everything it got from __construct(), but not amodel.
If I can get acontroller to "see" amodel, then my problem will be solved.
Also:
Is there anyway for me to remove "parent::init()" from the controller acontroller? I only did that so acontroller could have access to both the Load and Model class, but I'm trying to put as less code as possible in acontroller, so having the acontroller have access to Load and Model automatically would help a lot.
I hope I was clear. Thanks for any help
I personally do not think that singleton methods belong within an MVC Framework, the reason for this is because the main objects that are loaded are Models,Libraries and controllers, everything else such as the Router is usually hard coded.
The structure that i would do is create the following classes:
ModelLoader
LibraryLoader
and have them included during system boot, then within your main controller do the following:
class Controller
{
public $library;
public $model;
public function __construct()
{
$this->library = new LibraryLoader();
$this->model = new ModelLoader();
}
}
this would expose the 2 loaders to the child controller, your model/library should hold a private array storing the loaded objects, a little something like this:
class LibraryLoader extends ObjectLoader
{
protected $_path = "/app/library/";
protected $_ext = '.php';
}
class ModelLoader extends ObjectLoader
{
protected $_path = "/app/models/";
protected $_ext = '.php';
}
the object loader would look like so:
class ObjectLoader
{
protected $_path = "/app/";
protected $_ext = '.php';
public function __get($item)
{
/*
* Load item here, the paths above would be overwritten
* store the object in an array, make sure you check if its already loaded
*/
}
}
this is pretty basic, but within your child controllers such as index / home etc you can do the following:
class indexController extends Controller
{
public function index()
{
$this->model->users->getUser(22);
$this->library->security->validateInput("get","key");
//As the objectLoader manages whats loaded, any further calls to the above would
//use the same objects initiated as above.
}
}
This should get you started, its more streamline them using the singleton approach.
I guess you need to include Model.php in your controller.php to be able to use model class.
include 'Model.php';
include 'Load.php';
Since PHP 5.3 you can use the static keyword to instantiate a class
abstract class singleton
{
/**
* Holds an insance of self
* #var $instance
*/
protected static $instance = NULL;
/**
* Prevent direct object creation
*/
final private function __construct() { }
/**
* Prevent object cloning
*/
final private function __clone() { }
final public static function getInstance()
{
if(null !== static::$instance){
return static::$instance;
}
static::$instance = new static();
return static::$instance;
}
}
class myclass extends singleton
{
}
$myclass = myclass::getInstance();