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

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.

Related

Test Method of class calls method of another class passed to it?

To state the problem simply:
Say I have two abstract classes, Foo and Baz.
And say Baz defines a method, biz(), which expects an instance of Foo.
public function biz(Foo $foo): void {...}
Is it possible to test that Baz->biz() calls a specific method of the Foo instance passed to it?
My hunch after much struggle is that it may not be possible to write such a test...
Below is an example of what I am trying to accomplish:
abstract class Foo ()
{
abstract public function bar(): void;
...
}
abstract class Baz()
{
public function biz(Foo $foo): void
{
$foo->bar();
}
abstract public function buz(): void;
...
}
final class BazTest extends TestCase
{
public function testBazzerCallsFooInstancesBarMethod() {
$mockFoo = $this->getMockBuilder(Foo::class)
->setMethods(['bar'])
->getMock();
$mockBaz = $this->getMockBuilder(Baz::class)
->setMethods(['biz', 'buz'])
->getMock();
/**
* Problem: How do I test that $mockBaz->biz() calls $mockFoo->bar() internally,
* or is this not possible? The following approach does not work...
*/
$mockFoo->expects($this->once())
->method('bar');
$mockBaz->biz($mockFoo);
}
}

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(......);
}
}

Phpunit - How add another object type to mocking object

I need to add Mailable type to my mocked ActivationMail. Maybe you have better idea to mock my ActivationMail class?
Tested class:
class MailService
{
public function sendActivationEmail(User $user): void
{
$this->sendEmail(new ActivationMail($user));
}
//content
protected function sendEmail(Mailable $mailable): void
{
//content
}
}
ActivationMail:
class ActivationMail extends Mailable{}
Do you have idea how can I test it?
My test:
public function test_sendActivationEmail()
{
$user = Mockery::mock(User::class);
$mailable = Mockery::mock(Mailable::class);
Mockery::mock('overload:App\Mail\ActivationMail');
$mailService = new MailService();
$mailService->sendActivationEmail($user);
}
And my error:
TypeError: Argument 1 passed to App\Services\MailService::sendEmail()
must be an instance of Illuminate\Mail\Mailable, instance of
App\Mail\ActivationMail given

Symfony2, acess/use the custom class in another custom class

How to load/access a custom class in the custom class in Symfony 2 not using service container?
If i try to use the custom logger as shown below i am getting the error:
Catchable Fatal Error: Argument 1 passed to MeetingBundle\Components\LogWriter::__construct() must implement interface Symfony\Component\HttpKernel\Log\LoggerInterface, none given, called in C:\Bitnami\wampstack-5.6.20-0\apache2\htdocs\sym\just2\src\MeetingBundle\Components\Serializer\Diff\EventDiff.php on line 23 and defined
Stack Trace:
in src\MeetingBundle\Components\LogWriter.php at line 12 -
private $logger;
public function __construct( LoggerInterface $logger ) // here they show the error
{
$this->logger = $logger;
}
src\MeetingBundle\Components\LogWriter.php
<?php
namespace MeetingBundle\Components;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
class LogWriter
{
private $logger;
public function __construct( LoggerInterface $logger )
{
$this->logger = $logger;
}
public function log($msg)
{
$this->logger->log($msg);
}
}
src\MeetingBundle\Components\Serializer\Diff\Diff.php
<?php
namespace MeetingBundle\Components\Serializer\Diff;
//use MeetingBundle\Components\LogWriter;
class Diff
{
private $diffCluesArr;
private $name;
private $logWriter;
public function __construct ($logger) {
$this->name= "";
$this->diffCluesArr = [];
$this->logWriter = $logger;
//$this->logWriter = new $logWriter; //changed here
}
public function array_diff_str_o ( $arr1, $arr2, $str ) {
$this->logWriter("<br> In Diff function array_diff_str_o ");
//...
src\MeetingBundle\Components\Serializer\Diff\EventDiff.php
<?php
namespace MeetingBundle\Components\Serializer\Diff;
use MeetingBundle\Components\Serializer\Diff\Diff;
use MeetingBundle\Components\LogWriter;
/**
* Event normalizer
*/
class EventDiff extends Diff
{
private $diffCluesArr;
private $name;
private $logw;
// does not work
// public function __construct (LogWriter $logger) {
// $this->logw= $logger;
// parent::__construct($this->logw);
public function __construct () {
$this->logw = new LogWriter();
parent::__construct($this->logw);
$this->logw("<br> In constructor of EventDiff");
$this->name= "event";
$this->diffCluesArr = array(
//1 means compare normally
//2 means compare the values of the keys
'id' => 1,
// ..
app\config\services.yml
services:
meeting.logw:
class: MeetingBundle\Components\LogWriter
arguments: ["#logger"]
meeting.diff.diff:
class: 'MeetingBundle\Components\Serializer\Diff\Diff'
arguments: ["#meeting.logw"]
meeting.diff.event:
class: 'MeetingBundle\Components\Serializer\Diff\EventDiff'
parent: meeting.logw
#the same error: parent: meeting.diff.diff
src\MeetingBundle\Controller\EventMapController.php
//..
$diffentiator = $this->get('meeting.diff.event');
$diffentiator->array_diff_str_o( $arrEventOld, $arrEventNew, $msg );
//..
//**** THE OLD VERSION OF THE QUESTION
If i try to use the custom logger as shown below i am getting the error:
Catchable Fatal Error: Argument 1 passed to MeetingBundle\Components\Serializer\Diff\Diff::__construct() must be an instance of MeetingBundle\Components\Serializer\Diff\LoggerInterface, none given, called in C:\Bitnami\wampstack-5.6.20-0\apache2\htdocs\sym\just2\src\MeetingBundle\Components\Serializer\Diff\EventDiff.php on line 16 and defined
Where is the mistake? The code is as follow:
just2\src\MeetingBundle\Components\LogWriter.php
<?php
namespace MeetingBundle\Components;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
class LogWriter
{
private $logger;
public function __construct(LoggerInterface $logger) // the place of error
{
$this->logger = $logger;
}
public function log($msg)
{
$this->logger->log($msg);
}
}
just2\src\MeetingBundle\Components\Serializer\Diff\Diff.php
<?php
namespace MeetingBundle\Components\Serializer\Diff;
use MeetingBundle\Components\LogWriter;
class Diff
{
private $logWriter;
public function __construct (LoggerInterface $logger) {
// the first mistake
$this->logWriter = $logger;
}
public function array_diff_str_o ( $arr1, $arr2, $str ) {
$this->logWriter("<br> In Diff function array_diff_str_o ");
//..
}
// src\MeetingBundle\Components\Serializer\Diff\EventDiff.php
<?php
namespace MeetingBundle\Components\Serializer\Diff;
use MeetingBundle\Components\Serializer\Diff\Diff;
/** Provides clue how to calculate the differences between entities instances */
class EventDiff extends Diff
{
private $diffCluesArr;
private $name;
public function __construct () {
parent::__construct();
$this->logWriter("<br> In constructor of EventDiff");
$this->name= "event";
$this->diffCluesArr = array(
//1 means compare normally
//2 means compare the values of the keys
'id' => 1,
//...
just2\src\MeetingBundle\Controller\EventMapController.php
/** * #Route("/edit/{id}", name="event_jsMap_edit")
* #Method("GET|POST")
* #Template("MeetingBundle:Event:ev_jsMap_edit.html.twig")
*/
public function editAction($id, Request $request)
{
...
$diffentiator = $this->get('meeting.diff.event');
$diffentiator->array_diff_str_o( $arrEventOld, $arrEventNew, $msg );
...
I also made logwrite to be a service, but maybe it is not necessary and i do not want it to be a service. I would like to use it as a individual class not as a part of a service container:
app\config\services.yml
services:
events.logger:
class: MeetingBundle\Components\LogWriter
arguments: ["#logger"]
meeting.diff.event:
class: 'MeetingBundle\Components\Serializer\Diff\EventDiff'
#class Diff is not a service. Class EventDiff extends from Diff.
The problem is most likely in the way you instantiate Diff in EventDiff class as the error message suggests. LogWriter seems allright.
Do you define EventDiff as a service as well with correct dependencies?
Edit: In EventDiff you're calling parent::__construct(); without any parameter. However the parent Diff class takes one parameter. You probably want to inject a service to EventDiff that'll be passed to its parent in the constructor.
You could Manage Common Dependencies with Parent Services. Try defining the service defining the parent attribute in the following manner:
services:
events.logger:
class: MeetingBundle\Components\LogWriter
arguments: ["#logger"]
meeting.diff.event:
class: 'MeetingBundle\Components\Serializer\Diff\EventDiff'
parent: events.logger
Hope this help

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

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);

Categories