Use parameters in Symfonybundle in service class - php

I have Symfony bundle called upload images:
I want to use parameters in my bundle in my class.
This is my parameter file:
upload-images:
image:
crop_size: 300
My files:
Configuration.php
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('upload-images');
$treeBuilder->getRootNode()
->children()
->arrayNode('image')
->children()
->integerNode('save_original')->end()
->scalarNode('crop_size')->end()
->end()
->end() // twitter
->end();
return $treeBuilder;
}
}
UploadImagesExtension.php
class UploadImagesExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources'));
$loader->load('services.yaml');
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
}
}
And final my service class:
Rotate.php
And in this class I want the parameter: crop_size
I tried the ParameterBagInterface:
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class Rotate
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function Rotate()
{
$cropSize = $params->get('crop_size');
}
}
UserController.php
use verzeilberg\UploadImagesBundle\Service\Rotate;
class UserController extends AbstractController
{
/** #var UserProfileService */
private $service;
private $userService;
public function __construct(
UserProfileService $service,
UserService $userService
) {
$this->service = $service;
$this->userService = $userService;
}
/**
* #param UserInterface $user
* #return Response
*/
public function profile(UserInterface $user)
{
$rotate = new Rotate();
$rotate->Rotate();
.....
...
}
Getting this error:
Too few arguments to function verzeilberg\UploadImagesBundle\Service\Rotate::__construct(), 0 passed in /home/vagrant/projects/diabetigraph-dev/src/Controller/User/UserController.php on line 62 and exactly 1 expected
I have search for a solution. But did not came accross the right one.

According to the latest edit, the error is pretty obvious: if you want to use dependency injection, you have to use it. Calling $rotate = new Rotate(); without any constructor parameters will fail, as Symfony cannot inject them for you.
Instead, inject it through the action:
public function profile(UserInterface $user, Rotate $rotate)
... this will use Symfony's container and inject the ParameterBagInterface, if you have enabled autowiring. If not, you have to write the proper service definitions to get this done

The error you are getting is not directly related to your question.
Either it is an autowiring issue, or maybe you are trying to instanciate the service manually? Please share what you are doing here: UserController.php on line 62
Anyway, to answer your question:
To access the parameters from the parameter bag you will have to set them in the extension.
$container->setParameter('my_bundle.config', $config);
Also, injecting the whole ParameterBag is fine for a project, but should be avoided for a bundle.
Use the DI config to inject just your parameter, OR make your extension implement CompilerPassInterface, and override the definition there. (It may be overkill for such a simple task)

Related

Abstract Class and Dependency Injection in Shopware 6, Symfony

Currently I am working on Shopware 6 extension, which is based on Symfony. What I don’t understand, is how to implement abstract classes and dependency injection.
So I want to be able to refactor the code, and to use those methods often, but in another context (with another repository)
<?php
declare(strict_types=1);
namespace WShop\Service;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Uuid\Uuid;
/**
* Service for writing Products
*/
class ProductService
{
private EntityRepository $productRepository;
private MediaImageService $mediaImageService;
private EntityRepository $productMediaRepository;
public function __construct(
EntityRepository $productRepository,
MediaImageService $mediaImageService,
EntityRepository $productMediaRepository
)
{
$this->productRepository = $productRepository;
$this->mediaImageService = $mediaImageService;
$this->productMediaRepository = $productMediaRepository;
}
private function createProduct(array $data, Context $context = null): void
{
$context = $context ?? Context::createDefaultContext();
$this->productRepository->create([
$data
], $context);
}
public function updateProduct(array $data): void
{
$this->productRepository->update([$data], Context::createDefaultContext());
}
public function getExistingProductId(string $productNumber): ?string
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productNumber', $productNumber));
return $this->productRepository->searchIds($criteria,
Context::createDefaultContext())->firstId();
}
}
As you can see, there are dependency injection inside construct (Product Repository). Now my question is, how am I able to create abstract class, that is storing those methods, but the child classes is going to kind of "rewrite" parent construct with repository that is needed? For example, I want to use getDataId (Now it is called getExistingProductId, but it is going to be refactored and renamed in abstract class) method on product repository, but for the next class I want to use the same method on categors repository?
Service.xml aka Dependency Injector
<service id="wshop_product_service" class="WShop\Service\ProductService">
<argument type="service" id="product.repository"/>
<argument id="wshop_media_image_service" type="service"/>
<argument type="service" id="product_media.repository"/>
</service>
I am kind of new into OOP. Please provide good example and code explanation. Thanks!
If I understood you correctly, you just want the first argument to be interchangeable and the 3 methods in your example should be implemented in the abstract. Here's one idea for that.
The abstract:
abstract class AbstractEntityService
{
protected EntityRepository $repository;
public function __construct(EntityRepository $repository)
{
$this->repository = $repository;
}
public function create(array $data, ?Context $context = null): void
{
$context = $context ?? Context::createDefaultContext();
$this->repository->create([
$data
], $context);
}
public function update(array $data): void
{
$this->repository->update([$data], Context::createDefaultContext());
}
abstract public function getDataId(array $params): ?string;
protected function searchId(Criteria $criteria): ?string
{
return $this->repository->searchIds(
$criteria,
Context::createDefaultContext()
)->firstId();
}
}
You take the repository in the constructor and implement all your general methods regarding the generic repositories in the abstract. The getDataId method you want to implement in the extending class, since you use a specific criteria for each one (presumably). So you just force the implementation in the extending class by defining an abstract signature.
Your service class:
class ProductService extends AbstractEntityService
{
private MediaImageService $mediaImageService;
private EntityRepository $productMediaRepository;
public function __construct(
EntityRepository $productRepository,
MediaImageService $mediaImageService,
EntityRepository $productMediaRepository
) {
parent::__construct($productRepository);
$this->mediaImageService = $mediaImageService;
$this->productMediaRepository = $productMediaRepository;
}
public function getDataId(array $params): ?string
{
if (!isset($params['productNumber'])) {
return null;
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productNumber', $params['productNumber']));
return $this->searchId($criteria);
}
// your other methods using the injected services
}
In the extending class you pass only the repository to the parent constructor since the other injected services are used only in this specific instance. You implement getDataId where you create your specific criteria and call the protected (since it should only be used by extensions) searchId method with the criteria.

How can I use doctrine method inside my service (Symfony 4)?

I created my first own service in Symfony :
// src/Service/PagesGenerator.php
namespace App\Service;
class PagesGenerator
{
public function getPages()
{
$page = $this->getDoctrine()->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
But I get the error message:
Attempted to call an undefined method named "getDoctrine" of class
"App\Service\PagesGenerator".
I tried then to add in my services.yaml:
PagesGenerator:
class: %PagesGenerator.class%
arguments:
- "#doctrine.orm.entity_manager"
But then I get the error message:
The file "/Users/work/project/config/services.yaml" does not contain
valid YAML in /Users/work/project/config/services.yaml (which is
loaded in resource "/Users/work/project/config/services.yaml").
So, in comments I was saying that is better to let Symfony doing his job and autowiring EntityManager. This is what you should do. Also, can you tell us what Symfony version are you using and if autowiring is enabled (check services.yaml for that)?
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class PagesGenerator
{
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function getPages()
{
$page = $this->em->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
With Symfony 4 and the new autowiring you can easily inject certain number of class
To find out, which classes/interface you can use for autowiring, use this command:
bin/console debug:autowiring
We are going to use this one :
Doctrine\ORM\EntityManagerInterface
(doctrine.orm.default_entity_manager)
So let's make it, add this just before getPages function
/**
* #var EntityManagerInterface
*/
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Then you can use it like this:
$page = $this->em->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
Hope it helps !
make sure you use proper indent using "spaces" for YAML.
A YAML file use spaces as indentation, you can use 2 or 4 spaces for
indentation, but no tab
read more about this
Before symfony 3.3
for example we have service sms_manager in AppBundle/FrontEndBundle/Services
services:
AppBundle.sms_manager:
class: AppBundle\FrontEndBundle\Services\SmsManager
arguments: [ '#service_container' ,'#doctrine.orm.entity_manager' ]
then your service can receive your arguments in constructor
<?php
namespace AppBundle\FrontEndBundle\Services;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class SmsManager {
private $container;
private $DM;
public function __construct( Container $container, \Doctrine\ORM\EntityManager $DM )
{
$this->container = $container;
$this->DM = $DM;
}
/**
* #return \Doctrine\ORM\EntityManager
*/
public function getDoctrine() {
return $this->DM;
}
}
With Symfony 3.3 or more,
Is there a way to inject EntityManager into a service
use Doctrine\ORM\EntityManagerInterface
class PagesGenerator
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
// ...
}

Refactoring a Controller in symfony to adapt to hexagonal architecture

I have create a controller that creates a Owner record into database. Everything was done on the CreateOwnerController like this and working properly:
class CreateOwnerController extends Controller
{
public function executeAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$owner = new Owner($request->request->get("name"));
$em->persist($owner);
$em->flush();
return new Response('Added',200);
}
}
Now,In order to refactor that I have created an interface that defines the OwnerRepository:
interface OwnerRepositoryInterface {
public function save(Owner $owner);
}
And a OwnerRepository that implements this interface:
class OwnerRepository extends EntityRepository implements OwnerRepositoryInterface {
public function save(Owner $owner) {
$this->_em->persist($owner);
$this->_em->flush();
}
}
Then I have Created for the application layer a CreateOwnerUseCase Class that receives a OwnerRepository and executes a method to save in into OwnerRepository:
class CreateOwnerUseCase {
private $ownerRepository;
public function __construct(OwnerRepositoryInterface $ownerRepository) {
$this->ownerRepository = $ownerRepository;
}
public function execute(string $ownerName) {
$owner = new Owner($ownerName);
$this->ownerRepository->save($owner);
}
}
Ok, i'm spliting the initial Controller intro layer Domain / Aplication / Framework layers.
On the CreateOwnerController now i have instantiated that Use Case and passed as parameter the OwnerRepository like this:
class CreateOwnerController extends Controller {
public function executeAction(Request $request) {
$createOwnerUseCase = new CreateOwnerUseCase(new OwnerRepository());
$createOwnerUseCase->execute($request->request->get("name"));
return new Response('Added',200);
}
}
But it fails when Make the request to create new Owner:
Warning: Missing argument 1 for Doctrine\ORM\EntityRepository::__construct(), called in /ansible/phpexercises/Frameworks/mpweb-frameworks-symfony/src/MyApp/Bundle/AppBundle/Controller/CreateOwnerController.php
It happens on OwnerRepository passed as parameter. It wants an $em and Mapped Class... What is the meaning of this mapped Class? How solve this error?
This answer is for Symfony 3.3+/4+.
You need to register your repository as a service. Instead of extending it 3rd party code, you should use composition over inheritance.
final class OwnerRepository implements OwnerRepositoryInterface
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function save(Owner $owner)
{
$this->entityManager->persist($owner);
$this->entityManager->flush();
}
}
And register it as a service:
# app/config/services.yml
services:
App\Repository\:
# for location app/Repository
resource: ../Repository
You might need to tune paths a bit, to make that work.
To get more extended answer, see How to use Repository with Doctrine as Service in Symfony

Zend Framework 2: Pass Variable to View Helper

I have created a View Helper to display latest Adverts from a Database Table. Since I have different Types of Adverts, I would like to be able to pass a variable from inside my View where I call the View Helper to show specific Adverts.
I am sorry that I can not explain it in a better way, but I am still a total beginner in ZF2. I will add the Sourcecode and hopefully this will make it more clear. Please note that I have the Sourcecode from a Book which displayed Pizza's randomly and changed it till it worked to show my adverts. I might still have Code in it which is not actually needed, so please do not wonder... Okay here the code:
1. the view: index.html
<?php foreach ($this->latestAdvert() as $value){ ?>
<li><?php echo $value->getAdvertTitle();?></li>
<?php }?>
2. the view Helper: Advert\View\Helper\LatestAdvert.php
namespace Advert\View\Helper;
use Zend\View\Helper\AbstractHelper;
class LatestAdvert extends AbstractHelper
{
protected $random = null;
public function __construct($random)
{
$this->setLatestAdvert($random);
}
public function setLatestAdvert($random)
{
$this->random = $random;
}
public function getLatestAdvert()
{
return $this->random;
}
public function __invoke()
{
$latestAdverts = $this->getLatestAdvert();
return $latestAdverts;
}
}
3. the Factory: Advert\View\Helper\LatestAdvertFactory.php
namespace Advert\View\Helper;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class LatestAdvertFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$locator = $serviceLocator->getServiceLocator();
$service = $locator->get('Advert\Service');
$random = $service->fetchSingleByRandom();
$helper = new LatestAdvert($random);
return $helper;
}
}
4. the Service: Advert\Service\LatestAdvertService .php
namespace Advert\Service;
use Advert\Entity\Advert as AdvertEntity;
use Doctrine\ORM\EntityManager;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\Debug\Debug;
class LatestAdvertService implements ServiceManagerAwareInterface
{
/**
* Service manager.
* #var Zend\ServiceManager\ServiceManager
*/
private $serviceManager = null;
/**
* Sets service manager.
* #param Zend\ServiceManager\ServiceManager $serviceManager Service manager.
*/
public function setServiceManager(ServiceManager $serviceManager)
{
$this->serviceManager = $serviceManager;
}
/**
* Returns service manager.
* #return type
*/
public function getServiceLocator()
{
return $this->serviceManager;
}
public function fetchSingleByRandom()
{
// Get Doctrine entity manager.
$entityManager = $this->getServiceLocator()
->get('doctrine.entitymanager.orm_default');
$advertType = 'wanted'; // This should be removed
$random = $entityManager->getRepository('Advert\Entity\Advert')
->findAdvertsByDate($advertType);
return $random;
}
}
5. Module: Advert\Module.php
public function getServiceConfig()
{
return array(
'invokables' => array(
'Advert\Service' => 'Advert\Service\LatestAdvertService',
),
);
}
public function getViewHelperConfig()
{
return array(
'factories' => array(
'latestAdvert' => 'Advert\View\Helper\LatestAdvertFactory',
),
);
}
As you can see in #4 I have a Variable called $advertType. I want to set the variable when I call the view Helper in my index.html, f.e. $this->latestAdvert('wanted'), but how can I pass this variable through all my files? I just can not find a solution for it. Does anyone got a tip for me how to achieve it? Thank you very much in advance.
!UPDATE!
As SenseException pointed out below, that injecting a service locator into a service is a bad practice and instead I should either inject repository or entity manager into the service, I have now worked out the first working solution for the entity manager.
For that I have updated 2 Files: module.php and LatestAdvertService.php
#5 module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'Advert\Service' => function ($sl) {
$entityManager = $sl->get('doctrine.entitymanager.orm_default');
$myService = new Service\LatestAdvertService();
$myService->setEntityManager($entityManager);
//or you can set repository
//$repository = $entityManager->getRepository('Advert\Entity\Advert');
//$myService->setRepository($repository);
return $myService;
},
4. the Service: Advert\Service\LatestAdvertService .php
namespace Advert\Service;
use Advert\Entity\Advert as AdvertEntity;
use Doctrine\ORM\EntityManager;
class LatestAdvertService
{
public function setEntityManager(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function setRepository(Repository $repository) {
$this->repository = $repository;
}
public function fetchSingleByAdvertType($advertType)
{
$random = $this->entityManager->getRepository('Advert\Entity\Advert')->findAdvertsByDate($advertType);
// $random = $this->repository->findAdvertsByDate($advertType);
return $random;
}
}
I have tried to inject the repository but get the following error message:
Argument 1 passed to Advert\Service\LatestAdvertService::setRepository() must be an instance of Advert\Service\AdvertRepository, instance of Advert\Repository\AdvertRepository given, called in
I will continue to find a solution for the repository injection and update when successful.
How about this solution:
namespace Advert\View\Helper;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class LatestAdvertFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$locator = $serviceLocator->getServiceLocator();
$service = $locator->get('Advert\Service');
$helper = new LatestAdvert($service);
return $helper;
}
}
And of course the helper class:
namespace Advert\View\Helper;
use Zend\View\Helper\AbstractHelper;
class LatestAdvert extends AbstractHelper
{
protected $service;
public function __construct($service)
{
$this->service = $service;
}
public function __invoke($advertType)
{
$latestAdverts = $this->service->fetchSingleByAdvertType($advertType);
return $latestAdverts;
}
}
And for the service:
public function fetchSingleByAdvertType($advertType)
{
$entityManager = $this->getServiceLocator()
->get('doctrine.entitymanager.orm_default');
$random = $entityManager->getRepository('Advert\Entity\Advert')
->findAdvertsByDate($advertType);
return $random;
}
I tried to keep your code as close to your original as possible but please hear some suggestions about the service locator. It is a bad practice to inject a service locator into a service like you did in LatestAdvertService. Since you only need a repository for your service, just inject that one into it. If you need the entity manager in your service, inject it instead. Your unittests will thank you.

FOSUserBundle : get repository from inside FormHandler

I need to set a default value to a new user before saving it.
The problem is that I can't find a way to get an object through its repository from inside the FormHandler.
<?php
namespace Acme\UserBundle\Form\Handler;
use FOS\UserBundle\Form\Handler\RegistrationFormHandler as BaseHandler;
use FOS\UserBundle\Model\UserInterface;
class RegistrationFormHandler extends BaseHandler
{
protected function onSuccess(UserInterface $user, $confirmation)
{
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
if($user->isMale()){
$photo = $repository->getDefaultForMale();
$user->setPhoto($photo);
}
else {
$photo = $repository->getDefaultForFemale();
$user->setPhoto($photo);
}
parent::onSuccess($user, $confirmation);
}
}
The problem comes from the following line :
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
... and I can't find a way to get this repository, or the entity manager from this FormHandler.
Many thanks for your help !
A
You have to define a service that reference your extended handler class and point it in app/config.yml. e.g
The class,
//namespace definitions
class MyHandler extends RegistrationFormHandler{
private $container;
public function __construct(Form $form, Request $request, UserManagerInterface $userManager, MailerInterface $mailer, ContainerInterface $container)
{
parent::__construct($form, $request, $userManager, $mailer);
$this->container = $container;
}
protected function onSuccess(UserInterface $user, $confirmation)
{
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
// your code
}
The service,
my.registration.form.handler:
scope: request
class: FQCN\Of\MyHandler
arguments: [#fos_user.registration.form, #request, #fos_user.user_manager, #fos_user.mailer, #service_container]
Lastly in app/config.yml,
fos_user:
#....
registration:
#...
form:
handler: my.registration.form.handler
FOS got his own UserManager. Try to use this.

Categories