How to access service container in symfony2 global helper function (service)? - php

This question started out with me not understanding why I couldn't pass variables to a symfony2 global helper function (service), but thanks to people brighter than I, I realized my error was about trying to use the security_context from within a class that didn't have it injected so...
This is the final result, the code that works. I found no better way of making this helpful to the comunity.
This is how you can get the user and other data from security_context from within a global function or helper function in symfony2.
I have the following class and function:
<?php
namespace BizTV\CommonBundle\Helper;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class globalHelper {
private $container;
public function __construct(Container $container) {
$this->container = $container;
}
//This is a helper function that checks the permission on a single container
public function hasAccess($container)
{
$user = $this->container->get('security.context')->getToken()->getUser();
//do my stuff
}
}
...defined as a service (in app/config/config.yml) like this...
#Registering my global helper functions
services:
biztv.helper.globalHelper:
class: BizTV\CommonBundle\Helper\globalHelper
arguments: ['#service_container']
Now, in my controller I call on this function like this...
public function createAction($id) {
//do some stuff, transform $id into $entity of my type...
//Check if that container is within the company, and if user has access to it.
$helper = $this->get('biztv.helper.globalHelper');
$access = $helper->hasAccess($entity);

I assume that the first error (undefined property) happened before you added the property and the constructor. Then you got the second error. This other error means that your constructor expects to receive a Container object but it received nothing. This is because when you defined your service, you did not tell the Dependency Injection manager that you wanted to get the container. Change your service definition to this:
services:
biztv.helper.globalHelper:
class: BizTV\CommonBundle\Helper\globalHelper
arguments: ['#service_container']
The constructor should then expect an object of type Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class globalHelper {
private $container;
public function __construct(Container $container) {
$this->container = $container;
}

An approach that always works, despite not being the best practice in OO
global $kernel;
$assetsManager = $kernel->getContainer()->get('acme_assets.assets_manager');‏

Another option is to extend ContainerAware:
use Symfony\Component\DependencyInjection\ContainerAware;
class MyService extends ContainerAware
{
....
}
which allows you to call setContainer in the service declaration:
foo.my_service:
class: Foo\Bundle\Bar\Service\MyService
calls:
- [setContainer, [#service_container]]
You can then reference the container in your service like this:
$container = $this->container;

Maybe it's not the best way but what I do is I pass container to the class so I have it every time I need it.
$helpers = new Helpers();
or
$helpers = new Helpers($this->container);
/* My Class */
class Helpers
{
private $container;
public function __construct($container = null) {
$this->container = $container;
}
...
}
Works every time for me.

You should not inject the service_container in your services. In your example you should rather inject the old security.context or the more recent security.token_storage instead. See for example the "Avoiding your Code Becoming Dependent on the Container" section of http://symfony.com/doc/current/components/dependency_injection.html.
Ex:
<?php
namespace BizTV\CommonBundle\Helper;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class globalHelper {
private $securityTokenStorage;
public function __construct(TokenStorage $securityTokenStorage) {
$this->securityTokenStorage= $securityTokenStorage;
}
public function hasAccess($container)
{
$user = $this->securityTokenStorage->getToken()->getUser();
//do my stuff
}
}
app/config/config.yml:
services:
biztv.helper.globalHelper:
class: BizTV\CommonBundle\Helper\globalHelper
arguments: ['#security.token_storage']
Your controller:
public function createAction($id) {
$helper = $this->get('biztv.helper.globalHelper');
$access = $helper->hasAccess($entity);

Related

Correct way to extend classes with Symfony autowiring

I'm wondering if this is the correct way to extend and use classes with Symfonies autowiring.
For example, I have a BaseClass that instantiates and auto wires the entity manager.
class BaseClass
{
protected $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
protected function someMethodIWantToUse(Entity $something)
{
// Do something there
$this->entityManager->persist($something);
$this->entityManager->flush();
}
}
Then I have a subclass that extends the BaseClass and needs access that method. So I let it autowire again and pass it to the parent constructor.
class SubClass extends BaseClass
{
private $handler;
public function __construct(EntityManagerInterface $em, SomeHandler $handler)
{
parent::__construct($em);
$this->handler = $handler;
}
public function SubClassMethod()
{
// Get some data or do something
$entity = SomeEntityIGot();
$this->someMethodIWantToUse($entity);
}
}
Now I'm wondering if this is actually the correct way to do this or there's something I'm missing and the parent class should be able to autowire the entitymanager by itself?
To summarize the comments, yes your way is correct. Depending on your use case there are alternatives.
This are the ways you can go about it:
1. Extending Class and using Constructor Injection (what you do)
class BaseClass {
protected $some;
public function __construct(SomeInterface $some)
{
$this->some = $some;
}
}
class SubClass extends BaseClass {
private $other;
public function __construct(SomeInterface $some, OtherInterface $other)
{
parent::__construct($some);
$this->other = $other;
}
}
2. Setter Injection
class BaseClass {
protected $some;
public function __construct(SomeInterface $some)
{
$this->some = $some;
}
}
class SubClass extends BaseClass {
private $other;
public function setOther(OtherInterface $other)
{
$this->other = $other;
}
}
Now setOther won't automatically be called, you have to "manually" call it by either specifying a calls property in your services.yaml file, as described here: https://symfony.com/doc/current/service_container/calls.html. This would then look something like this:
// services.yaml
App\SubClass:
calls:
- [setOther, ['#other']]
Or
// services.yaml
app.sub_class:
class: App\SubClass
calls:
- [setOther, ['#other']]
assuming, an implementation of OtherInterface is available as #other in the service container.
A more elegant solution if you're using autowiring, simply add a #required annotation to the function as described here: https://symfony.com/doc/current/service_container/autowiring.html#autowiring-calls, which would look like this:
/**
* #required
*/
public function setOther(OtherInterface $other)
{
$this->other = $other;
}
3. Property Injection
class BaseClass {
protected $some;
public function __construct(SomeInterface $some)
{
$this->some = $some;
}
}
class SubClass extends BaseClass {
public $other;
}
As with the Setter Injection, you'll need to tell Symfony to populate this property, by specifying it in your services.yaml file like this:
// services.yaml
App\SubClass:
properties:
other: '#other'
Or
// services.yaml
app.sub_class:
class: App\SubClass
properties:
other: '#other'
assuming, an implementation of OtherInterface is available as #other in the service container.
Conclusion:
Since there are different ways to solve this, it's up to you to determine the correct way for your use case. I personally go with either option 1 (Constructor Injection) or option 2 (Setter Injection) using the annotation. Both of them allow you to use typehints and thus allowing your IDE to help you write clean code.
In 90% of cases, I'd go with option 1, as then it's clear for every one reading your code, what services are available with one glance at the __constructor function.
One use case for Setter Injection would be a base class offering all the setXXX functions but then sub classes not needing all of them. You could have a constructor in each sub class, requesting the needed services and then calling the setXXX methods of the base class.
Note: this is kind of an edge case and you probably won't run into this.
You can find a list of advantages and disadvantages of each method directly in the Symfony documentation about the Service Container -> Types of Injection
This way too :
class BaseClass
{
protected Environment $twig;
#[Required]
public function setTwig(Environment $twig): void
{ $this->twig = $twig; }
}
class ChildClass extends BaseClass
{
public function __construct(
private EntityManagerInterface $entityManager
) { }
public function test()
{
$this->twig->render(......);
}
}

Symofny 3 - parent::__construct() cant be injected

I am trying to inject one service into another to call function from it but it throws an error:
Type error: Argument 1 passed to App\Base\Service\Shop\ShopService::__construct() must be an instance of App\Base\Service\AccountService, instance of ContainerSgqv3vy\appDevDebugProjectContainer given
And I think I implement everything correctly:
use App\Base\Service\AccountService;
use App\Base\Service\BaseService;
class ShopService extends BaseService
{
/**
* #param AccountService $accountService
*/
public function __construct(AccountService $accountService)
{
parent::__construct();
$this->accountService = $accountService;
}
And calling in my function from it:
this->accountService->getMyFunction();
And my instantiated class :
class BaseService
{
/** #var ContainerInterface */
var $container;
var $em;
public function __construct(ContainerInterface $container, EntityManagerInterface $em)
{
$this->container = $container;
$this->em = $em;
}
service.yml
app.shop:
class: App\Base\Service\ShopService
arguments: ["#service_container", "#doctrine.orm.default_entity_manager"]
public: true
When you extend a class that has constructor arguments, you should keep those arguments and add any extra argument after.
Example:
class BaseClass
{
public function __construct(Foo $foo, Bar $bar) {
// ....
}
}
To instantiate this class, you would need to pass the container and the entity managaer:
$base = new BaseClass($fooInstance, $barInstance);
If you want to extend this class, it will most likely still need those dependencies + our new dependency:
class ChildClass extends BaseClass
{
public function __construct(Foo $foo, Bar $bar, Duck $duck)
{
// The parent (BaseClass) requires Foo and Bar,
// or it will throw an error
parent::__construct($foo, $bar);
$this->duck = $duck;
}
}
To instantiate our new ChildClass, we would need to pass three arguments:
$child = new ChildClass($fooInstance, $barInstance, $duckInstance);
Then, when you define our ChildClass in the service.yml, you need to have define all three dependencies:
app.childclass:
class: ChildClass
arguments: ["Foo", "Bar", "Duck"]
...
Since I'm not using Symfony myself, you must excuse for not knowing the exact syntax of service.yml, but the concept is the same.

Symfony : Reading from parameter.yml from outside a controller

I Want to read a parameter from parameters.yml.
The solution is to extend the Controller but I got this error :
Error: Call to a member function getParameter() on null
I know that the container is null, but I don't know how to get the container.
class Configuration extends Controller
{
public function __construct()
{
$this->tempFolderPath = sys_get_temp_dir();
$parameter = $this->container->getParameter('a');
}
}
Are there any solutions?
In classes which extend Controller calling $this->container->getParameter('a') is possible because of Container being injected into such classes. When you are in other class you need to inject parameter you want by defining your class as a service.
Your service definition will look something like:
services:
class: App\Your\FooClass
arguments:
- %a%
Note % - it's special character to inject parameters
Then your class will look like:
class FooClass
{
protected $a;
public function __construct($a)
{
$this->a = $a;
//you have your "a" parameter value injected into constructor
}
}
This is not a good practice, but for me this is the only solution because in my code I have a static method that instantiates the whole class.
The solution is
public function __construct()
{
$this->tempFolderPath = sys_get_temp_dir();
global $kernel;
$this->host = $kernel->getContainer()->getParameter('ws_host');
}

Confusion about adapter pattern and inheritance (PHP)

I'm having some confusion with the adapter pattern and am wondering if it is the right tool for what I'm trying to accomplish.
Basically, I'm trying to get a class written by another developer to conform to an interface that I've written while also retaining the other methods from that class.
So I've written the following interface for a container object:
interface MyContainerInterface
{
public function has($key);
public function get($key);
public function add($key, $value);
public function remove($key);
}
I've also written an adapter that implements that interface:
class OtherContainerAdapter implements MyContainerInterface
{
protected $container;
public function __construct(ContainerInteface $container) {
$this->container = $container;
}
public function has($key) {
$this->container->isRegistered($key);
}
...
}
And am using it in my class as follows:
class MyClass implements \ArrayAccess
{
protected $container;
public function __construct(MyContainerInterface $container) {
$this->setContainer($container);
}
public function offsetExists($key) {
$this->container->has($key);
}
...
}
Then my application uses the class as so:
$myClass = new MyClass(new OtherContainerAdapter(new OtherContainer));
The issue I'm having is that in order to use the methods from the adapter I have to write the following:
$myClass->getContainer()->getContainer()->has('some_key');
When ideally it would just be:
$myClass->getContainer()->has('some_key');
$myClass->getContainer()
should return an instance of MyContainerInterface and that has a has() function. It shouldn't have a getContainer() function.
I don't think you need the Adapter Pattern for this. It looks to me like you're after a polymorphic solution, which can be accomplished by simply using an abstract class. No adapter needed.
The interface
interface MyContainerInterface
{
public function has($key);
public function get($key);
public function add($key, $value);
public function remove($key);
}
Then the abstract base class:
class MyContainerBaseClass implements MyContainerInterface, \ArrayAccess
{
public function offsetExists($key) {
$this->has($key);
}
...
}
Then, the sub-class from the other developer:
class ClassByOtherDeveloper extends MyContainerBaseClass
{
public function has($key) {
$this->isRegistered($key);
}
//you also need to implement get(), add(), and remove() since they are still abstract.
...
}
You can use it in your application like this:
$object = new ClassByOtherDeveloper();
$x = $object->has('some_key');
I'm assuming the isRegistered method lives in the implementation from the other developer.
To make it truly polymorphic you wouldn't hard-code the class name, but you'd use a variable that could come from a config file, database, or a Factory.
For example:
$className = "ClassByOtherDeveloper"; //this could be read from a database or some other dynamic source
$object = new $className();
$x = $object->has('some_key');

Laravel facade __constructor() usage

I have made a class. The class's constructor assigns a value to a class property.
Here is the definition:
class myClass
{
private $_value;
function __construct ($input)
{
$this->_value = $input;
}
function echoThat ()
{
echo $this->_value;
}
}
And the regular usage is:
$x = new myClass("SimpleString");
$x->echoThat();
But I have turned the class into a facade type of class in Laravel so it is used:
myClass::echoThat();
How should I utilize the __construct() method in Laravel Facades like the above example?
You should only create a Laravel facade if you really want to use your class like Laravel facades are used. Laravel facades provide a static interface to a class.
This means either rewrite your class so that you can pass your string in anyother way like:
MyClassFacade::echoThat('SimpleString');
Of course you can also modify the underlying class to use for example another method to pass the string:
class MyClass
{
private $_value;
public function setValue($val)
{
$this->_value = $val;
return $this;
}
public function echoThat()
{
echo $this->_value;
}
}
And then call it like:
MyClassFacade::setValue('SimpleString')->echoThat();
You can call the non static methods of you class "statically" if you instantiate your class wherever the Laravel facade accessor is resolved, for example in the service provider:
use Illuminate\Support\ServiceProvider;
class MyClassServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('myclass', function()
{
return new MyClass();
});
}
}
Laravel will create an instance of MyClass whenever a method on your class is called statically using __callStatic.
Or don't use the Laravel facade and instantiate your class manually like you did it:
$x = new myClass("SimpleString");
$x->echoThat();
You have first to understand that a Facade, in Laravel, is not really your class, you must look at it, as the name says, a forefront or a frontage to your class. Facade is, acually, a Service Locator to your class.
To use this Service Locator you have to create a Service Provider, which will provide the service used by the Facade:
<?php namespace App\MyApp;
use Illuminate\Support\ServiceProvider;
class MyClassServiceProvider extends ServiceProvider {
protected $defer = true;
public function register()
{
$this->app['myclass'] = $this->app->share(function()
{
return new MyClass('the values you need it to instantiate with');
});
}
public function provides()
{
return array('myclass');
}
}
Note that your class is instantiated in the method register() and now is available to your application via the IoC container, so you can do things like:
App::make('myclass')->echoThat();
And now you can also create your Facade to use it:
<?php namespace App\MyApp;
use Illuminate\Support\Facades\Facade as IlluminateFacade;
class ExtendedRouteFacade extends IlluminateFacade {
protected static function getFacadeAccessor()
{
return 'myclass';
}
}
The Facade has only one method and its only purpose is to return the name of your class instance in the IoC container.
Now you can open your app/config/app.php and add the ServiceProvider:
'App\MyApp\MyClassServiceProvider',
also the actual Facade alias:
'MyClass' => 'App\MyApp\MyClassFacade',
and you should be good to use your Facade:
echo MyClass::echoThat();
But note that the way it is made your class __constructor will be always called with the same parameters, in your ServiceProvider, that's how a Facade works, but you have some options:
Use a setter to set a new value for the data in your class instance.
public function setValue($value)
{
$this->_value = $value;
}
Use the Laravel automatic class resolution to provide parameters for your class dinamically:
function __construct (MyFooClass $foo)
{
$this->_foo = $foo;
}
And alter your ServiceProvider to provide no parameters to your class:
$this->app['myclass'] = $this->app->share(function()
{
return new MyClass();
});
Laravel will instantiante MyFooClass automatically for you, if it can locate it in the available source code of your application or if it's bound to the IoC container.
Note that all this assumes that you're not just passing a string in your constructor, the automatic resolution of the IoC container assumes your class has other class dependencies and inject those dependencies automatically (Dependency Injection).
If you really need to just pass strings or single values to constructors of classes that just do some calculations, you don't really need to create Facades for them, you can just:
$x = (new myClass("SimpleString"))->echoThat();
using a simple setter:
$x = new myClass();
$x->setValue("SimpleString");
$x->echoThat();
or, as you were already doing, which is acceptable:
$x = new myClass("SimpleString");
$x->echoThat();
And you can also use the IoC container to instantiate that class for you inside your other classes:
<?php
class Post {
private $myClass;
public function __construct(MyClass $myClass)
{
$this->myClass = $myClass;
}
public function doWhateverAction($value)
{
$this->myClass->setValue($value);
// Do some stuff...
return $this->myClass->echoThat();
}
}
Laravel will automatically pass an instance of your class and you can use it the way you need:
$post = new Post;
echo $post->doWhateverAction("SimpleString");
I found some goode information about model classes in the Facede Lavarel FW in this page
http://laravel.com/docs/eloquent
I assume here is the main explanation about your ploblem, the way to create a enter in database...
// Create a new user in the database...
$user = User::create(array('name' => 'John'));
// Retrieve the user by the attributes, or create it if it doesn't exist...
$user = User::firstOrCreate(array('name' => 'John'));
// Retrieve the user by the attributes, or instantiate a new instance...
$user = User::firstOrNew(array('name' => 'John'));
And here the way to create a simple new instance and initialize its variables:
$user = new User;
$user->name = 'John';
$user->save();
Based on that I assume there isn´t a clear way to use a constructor in Lavarel.

Categories