How to give container as argument to services - php

in my services constructor
public function __construct(
EntityManager $entityManager,
SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
$this->entityManager = $entityManager;
I pass entityManager and securityContext as argument.
also my services.xml is here
<service id="acme.memberbundle.calendar_listener" class="Acme\MemberBundle\EventListener\CalendarEventListener">
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="security.context" />
but now,I want to use container in services such as
$this->container->get('router')->generate('fos_user_profile_edit')
how can I pass the container to services?

It's easy, if service extends ContainerAware
use \Symfony\Component\DependencyInjection\ContainerAware;
class YouService extends ContainerAware
{
public function someMethod()
{
$this->container->get('router')->generate('fos_user_profile_edit')
...
}
}
service.yml
your.service:
class: App\...\YouService
calls:
- [ setContainer,[ #service_container ] ]

Add:
<argument type="service" id="service_container" />
And in your listener class:
use Symfony\Component\DependencyInjection\ContainerInterface;
//...
public function __construct(ContainerInterface $container, ...) {

It's 2016, you can use trait which will help you extend same class with multiple libraries.
<?php
namespace iBasit\ToolsBundle\Utils\Lib;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Component\DependencyInjection\ContainerInterface;
trait Container
{
private $container;
public function setContainer (ContainerInterface $container)
{
$this->container = $container;
}
/**
* Shortcut to return the Doctrine Registry service.
*
* #return Registry
*
* #throws \LogicException If DoctrineBundle is not available
*/
protected function getDoctrine()
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application.');
}
return $this->container->get('doctrine');
}
/**
* Get a user from the Security Token Storage.
*
* #return mixed
*
* #throws \LogicException If SecurityBundle is not available
*
* #see TokenInterface::getUser()
*/
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application.');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
return $user;
}
/**
* Returns true if the service id is defined.
*
* #param string $id The service id
*
* #return bool true if the service id is defined, false otherwise
*/
protected function has ($id)
{
return $this->container->has($id);
}
/**
* Gets a container service by its id.
*
* #param string $id The service id
*
* #return object The service
*/
protected function get ($id)
{
if ('request' === $id)
{
#trigger_error('The "request" service is deprecated and will be removed in 3.0. Add a typehint for Symfony\\Component\\HttpFoundation\\Request to your controller parameters to retrieve the request instead.', E_USER_DEPRECATED);
}
return $this->container->get($id);
}
/**
* Gets a container configuration parameter by its name.
*
* #param string $name The parameter name
*
* #return mixed
*/
protected function getParameter ($name)
{
return $this->container->getParameter($name);
}
}
Your object, which will be service.
namespace AppBundle\Utils;
use iBasit\ToolsBundle\Utils\Lib\Container;
class myObject
{
use Container;
}
Your service settings
myObject:
class: AppBundle\Utils\myObject
calls:
- [setContainer, ["#service_container"]]
Call your service in controller
$myObject = $this->get('myObject');

If all your services are ContainerAware, I suggest to create a BaseService class that will contain all common code with your other services.
1) Create the Base\BaseService.php class:
<?php
namespace Fuz\GenyBundle\Base;
use Symfony\Component\DependencyInjection\ContainerAware;
abstract class BaseService extends ContainerAware
{
}
2) Register this service as abstract in your services.yml
parameters:
// ...
geny.base.class: Fuz\GenyBundle\Base\BaseService
services:
// ...
geny.base:
class: %geny.base.class%
abstract: true
calls:
- [setContainer, [#service_container]]
3) Now, in your other services, extends your BaseService class instead of ContainerAware:
<?php
namespace Fuz\GenyBundle\Services;
use Fuz\GenyBundle\Base\BaseService;
class Loader extends BaseService
{
// ...
}
4) Finally, you can use the parent option in your services declaration.
geny.loader:
class: %geny.loader.class%
parent: geny.base
I prefer this way for several reasons:
there is consistency between the code and the config
this avoids duplicating too much config for each service
you have a base class for each services, very helpful for common code

Related

Test Service with phpunit inside symfony


How can I test Services with PHPUnit using symfony? So far, I installed and included test-pack, DAMA Doctrine Bundle, and created Test Database.
Inside .env.test I added Database connection
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
# .env.test.local
DATABASE_URL="mysql://root:root#db:3306/testdb?serverVersion=mariadb-10.4.11&charset=utf8mb4"
I included inside phpunit.xml.dist the DAMA Doctrine bundle
<extensions>
<extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
</extensions>
Now, what I want to test is my Services (for instance CartService, ProductService etc.)
use App\Entity\Cart;
use App\Entity\CartItem;
use App\Entity\Product;
use App\Entity\User;
use App\Repository\CartItemRepository;
use App\Repository\CartRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Security;
class CartService
{
private CartRepository $cartRepository;
private ManagerRegistry $managerRegistry;
private CartItemRepository $cartItemRepository;
private Security $security;
public function __construct(Security $security, CartItemRepository $cartItemRepository, CartRepository $cartRepository, ManagerRegistry $managerRegistry)
{
$this->cartItemRepository = $cartItemRepository;
$this->cartRepository = $cartRepository;
$this->managerRegistry = $managerRegistry;
$this->security = $security;
}
/**
* Get Cart by ID
*
* #return Cart|null
*/
public function getCartByUserId(): ?Cart
{
$user = $this->security->getUser();
return $this->cartRepository->findOneBy(['customer' => $user]);
}
/**
* Show Cart and Total Price
*
* #return Cart|null
*/
public function showCart(): ?Cart
{
$cart = $this->getCartByUserId();
$this->calculateTotalPrice();
return $cart;
}
When I run phpunit test on CartServiceTest, I get this error:
1) App\Tests\CartServiceTest::testShowCart
Error: Typed property App\Tests\CartServiceTest::$cartService must not be accessed before initialization
/var/www/html/Tests/CartServiceTest.php:29
CartServiceTest look like this
<?php
namespace App\Tests;
use App\Entity\Product;
use App\Service\CartService;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CartServiceTest extends KernelTestCase
{
/**
* #var EntityManager
*/
private EntityManager $entityManager;
private CartService $cartService;
public function setUp(): void
{
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')
->getManager();
}
public function testShowCart()
{
$user = 11;
$cart = $this->cartService->getCartByUserId();
dump($cart);
}
protected function tearDown(): void
{
$this->entityManager->close();
}
}
Error: Typed property App\Tests\CartServiceTest::$cartService must not be accessed before initialization
Means that you have to already Use the cartService in your application. For exemple if you already inject this service has a dependency injection in one of your controller it's okay.
But you can do better. Just create a service config for your tests "services_test.yaml" and make your service public
Something like:
#servies_test.yaml
services:
App\Service\CartService:
public: true

Laravel service provider not binding to contract

i have below contract/interface which is binded by a service provider ,however the i get below error :
ReflectionException in RouteDependencyResolverTrait.php line 81:
Class App\Http\Controllers\RocketShipContract does not exist
What am i doing wrong ?
Contract
namespace App\Contracts\Helpers;
Interface RocketShipContract
{
public function blastOff();
}
The concrete class
namespace app\Contracts;
use App\Contracts\Helpers\RocketShipContract;
class RocketShip implements RocketShipContract
{
public function blastOff()
{
return 'Houston, we have ignition';
}
}
The service provider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Contracts\RocketShip;
class RocketShipServiceProvider extends ServiceProvider
{
protected $defer = true;
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('App\Contracts\Helpers\RocketShipContract', function($app){
return new App\Contracts\RocketShip($app['HttpClient']);
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return ['App\Contracts\Helpers\RocketShipContract'];
}
}
The controller
public function test(RocketShipContract $rocketship)
{
$boom = $rocketship->blastOff();
return view('test.index', compact('boom'));
}
The error you're getting hints at the problem: the class is being resolved in the App\Http\Controllers namespace. That's because you need to specify the full namespace of your interface in the controller.
So either include it with a use statement:
use App\Contracts\Helpers\RocketShipContract;
Or type hint the full namespace:
public function test(App\Contracts\Helpers\RocketShipContract $rocketship)
{
// ...
}

Create a doctrine repository with dependencies (dependency injection) in ZF2

I want to make a repository with hard dependencies. I found this blog post by Jurian Sluisman but he suggests getting the repository from the service manager and injecting it into the service where needed.
It would be much better if I would be able to get my custom repositories with injected dependencies like normally from my EntityManager or ObjectManager instance by using the getRepository method:
$objectManager->getRepository('My\Entity\Class');
How can I use constructor injection in my Repositories and still get them like normally from the ObjectManager directly with the getRepository method?
Doctrine uses a factory class Doctrine\ORM\EntityManagerInterface\DefaultRepositoryFactory for creating repository instances. If no custom factory is set this default factory is created here in the getRepositoryFactory method in the Doctrine\ORM\Configuration class.
By defining a custom repository_factory we can overwrite this default factory class and add custom logic to the factory that will inject the hard dependencies:
To illustrate how you can do this I will show an example where the repository factory class creates repositories that are dependent on a ServiceLocator instance through constructor injection.
1) make a custom factory class that implements the doctrine RepositoryFactory interface
This class looks very similar to the doctrine DefaultRepositoryFactory class.
<?php
namespace My\ORM\Repository;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\ORM\EntityManagerInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\ServiceManager\ServiceLocatorInterface;
class CustomRepositoryFactory implements RepositoryFactory, ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait;
/**
* #var ObjectRepository[]
*/
private $repositoryList = array();
/**
* #var ServiceLocator
*/
protected $serviceLocator;
/**
* #param ServiceLocatorInterface $serviceLocator
*/
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
/**
* {#inheritdoc}
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName)
{
$repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_hash($entityManager);
if (isset($this->repositoryList[$repositoryHash])) {
return $this->repositoryList[$repositoryHash];
}
return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName);
}
/**
* #param EntityManagerInterface $entityManager The EntityManager instance.
* #param string $entityName The name of the entity.
* #return ObjectRepository
*/
private function createRepository(EntityManagerInterface $entityManager, $entityName)
{
/* #var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$metadata = $entityManager->getClassMetadata($entityName);
$repositoryClassName = $metadata->customRepositoryClassName
?: $entityManager->getConfiguration()->getDefaultRepositoryClassName();
// Constructor injection, I check with subclass of but it is just an example
if(is_subclass_of($repositoryClassName, ServiceLocatorAwareInterface::class)){
$serviceLocator = $this->getServiceLocator()
$repository = new $repositoryClassName($entityManager, $metadata, $serviceLocator);
}else{
$repository = new $repositoryClassName($entityManager, $metadata);
}
return $repository;
}
}
2) Create a factory for the repository factory
<?php
namespace My\ORM\Repository\Factory;
use My\ORM\Repository\CustomRepositoryFactory;
use Zend\Cache\Storage\StorageInterface;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CustomRepositoryFactoryFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return StorageInterface
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new CustomRepositoryFactory($serviceLocator);
}
}
3) register the factory for the repository factory in the service_manager config
'service_manager' => array(
'factories' => array(
'My\ORM\Repository\CustomRepositoryFactory' => 'My\ORM\Repository\Factory\CustomRepositoryFactoryFactory'
)
)
4) register the repository factory in the doctrine config
'doctrine' => array(
'configuration' => array(
'orm_default' => array(
'repository_factory' => 'My\ORM\Repository\CustomRepositoryFactory'
)
)
)

How do I solve "Target [Interface] is not instantiable" in Laravel 4?

My error message:
Illuminate \ Container \ BindingResolutionException
Target [Project\Backend\Service\Validation\ValidableInterface] is not instantiable.
I understand that interfaces and abstract classes are not instantiable so I know that Laravel should not be trying to instantiate my interface. Yet somehow it's trying to and I suspect this may be a binding issue...even though I believe I have bound it correctly and have registered it as a service provider.
I should mention that I have taken this example out of Chris Fidao's "Implementing Laravel" and it's almost identical!
This is the first couple of lines of my form class:
namespace Project\Backend\Service\Form\Job;
use Project\Backend\Service\Validation\ValidableInterface;
use Project\Backend\Repo\Job\JobInterface;
class JobForm {
/**
* Form Data
*
* #var array
*/
protected $data;
/**
* Validator
*
* #var \Project\Backend\Form\Service\ValidableInterface
*/
protected $validator;
/**
* Job repository
*
* #var \Project\Backend\Repo\Job\JobInterface
*/
protected $job;
public function __construct(ValidableInterface $validator, JobInterface $job)
{
$this->validator = $validator;
$this->job = $job;
}
This is the first few lines of my validator class:
namespace Project\Backend\Service\Form\Job;
use Project\Backend\Service\Validation\AbstractLaravelValidator;
class JobFormValidator extends AbstractLaravelValidator {
// Includes some validation rules
This is the abstract validator:
namespace Project\Backend\Service\Validation;
use Illuminate\Validation\Factory;
abstract class AbstractLaravelValidator implements ValidableInterface {
/**
* Validator
*
* #var \Illuminate\Validation\Factory
*/
protected $validator;
/**
* Validation data key => value array
*
* #var Array
*/
protected $data = array();
/**
* Validation errors
*
* #var Array
*/
protected $errors = array();
/**
* Validation rules
*
* #var Array
*/
protected $rules = array();
/**
* Custom validation messages
*
* #var Array
*/
protected $messages = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
This is the code where I bind it all to the app:
namespace Project\Backend\Service\Validation;
use Illuminate\Support\ServiceProvider;
use Project\Backend\Service\Form\Job\JobFormValidator;
class ValidationServiceProvider extends ServiceProvider {
public function register()
{
$app = $this->app;
$app->bind('Project\Backend\Service\Form\Job\JobFormValidator', function($app)
{
return new JobFormValidator($app['validator']);
});
}
}
This is then registered in app/config/app.php:
.....
'Project\Backend\Service\Validation\ValidationServiceProvider',
....
Finally these are the first few lines of my controller:
use Project\Backend\Repo\Job\JobInterface;
use Project\Backend\Service\Form\Job\JobForm;
class JobController extends \BaseController {
protected $jobform;
function __construct(JobInterface $job, JobForm $jobform)
{
$this->job = $job;
$this->jobform = $jobform;
}
You need to tell Laravel which instance it should use for a certain interface when injecting it into the constructor via type hinting.
You do this using the bind() method (in your service provider for example)
$app->bind('JobInterface', 'Job'); // Job being the class you want to be used
I highly recommend you watch the video here where Taylor Otwell, the creator of Laravel, explains this and some other things.
First you need to bind using
/app/Providers/AppServiceProvider.php
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
$this->app->bind('JobInterface', 'Job');
}
}
Once you complete this change
Run composer update

Events FOSUserBundle Symfony2 on register and login

I'm trying to implement events on FOSUserBundle
<?php
namespace EasyApp\UserBundle\Service;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\Bundle\DoctrineBundle\Registry as Doctrine;
use EasyApp\UserBundle\Entity\UserLogin;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserBundle;
class LoginManager implements EventSubscriberInterface
{
/** #var \Symfony\Component\Security\Core\SecurityContext */
private $securityContext;
/** #var \Doctrine\ORM\EntityManager */
private $em;
/**
* Constructor
*
* #param SecurityContext $securityContext
* #param Doctrine $doctrine
*/
public function __construct(SecurityContext $securityContext, Doctrine $doctrine)
{
$this->securityContext = $securityContext;
$this->em = $doctrine->getEntityManager();
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::SECURITY_IMPLICIT_LOGIN => 'onSecurityImplicitLogin',
FOSUserEvents::REGISTRATION_COMPLETED=> 'onRegistrationCompleted'
);
}
public function onSecurityImplicitLogin(UserEvent $event)
{
die;
$user = $event->getAuthenticationToken()->getUser();
$request = $event->getRequest();
if ($this->securityContext->isGranted('IS_AUTHENTICATED_FULLY')) {
// user has just logged in
$this->saveLogin($request, $user);
}
}
public function onRegistrationCompleted(FilterUserResponseEvent $event){
$user = $event->getAuthenticationToken()->getUser();
$request = $event->getRequest();
saveLogin($request, $user);
}
public function saveLogin($request, $user){
$login = new UserLogin();
$login->setIp($request->getClientIp());
$login->setUser($user);
$this->em->persist($login);
$this->em->flush();
}
}
And my service
services:
user_login_manager:
class: 'EasyApp\UserBundle\Service\LoginManager'
arguments: ['#security.context', '#doctrine']
tags:
- { name: 'kernel.event_subscriber', event: 'fos_user.security.interactive_login'}
- { name: 'kernel.event_subscriber', event: 'fos_user.registration.completed'}
But I have problems. When I login nothing happen, the getSubscribedEvents() is called but not onSecurityImplicitLogin(UserEvent $event)
And the other problem is on register. Following error occurs on onSecurityImplicitLogin(UserEvent $event)
Catchable Fatal Error: Argument 1 passed to EasyApp\UserBundle\Service\LoginManager::onSecurityImplicitLogin() must be an instance of EasyApp\UserBundle\Service\UserEvent, instance of FOS\UserBundle\Event\UserEvent given in /Users/antoine/Documents/projects/easyApp/application/src/EasyApp/UserBundle/Service/LoginManager.php line 46
and if I comment this line I got the same error on onRegistrationCompleted(FilterUserResponseEvent $event)
Edit
services:
user_login_manager:
class: 'EasyApp\UserBundle\Service\LoginManager'
arguments: ['#security.context', '#doctrine']
tags:
- { name: 'kernel.event_subscriber'}
- { name: 'kernel.event_listener', event: 'security.interactive_login' }
Two points:
In the service definition - { name: 'kernel.event_subscriber'} is enough, no event entry. This the difference between subscriber and listener. A listener can only listen to one event, defined trough event and method params in the service definition. A subscriber can listen to multiple events, defined trough the method getSubscribedEvents(), so no further params in the service definition needed.
Second point, you are type hinting against the event classes (UserEvent andFilterUserResponseEvent), but you never imported them, so PHP assumes them in the same namespace (as said in the error message).
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\Event\FilterUserResponseEvent;

Categories