Symfony 4 access parameters inside of repository - php

I have a repository class called EmailRepository
class EmailRepository extends EntityRepository implements ContainerAwareInterface { ... }
I need to get a parameter injected into this repository class but I dont know how...
This is what I currently have inside of the repository, which is being called from my controller:
Controller:
$em->getRepository(Email::class)->getEmailApi();
Repository
class EmailRepository extends EntityRepository implements ContainerAwareInterface {
protected $container;
public function setContainer(ContainerInterface $container = null) {
$this->container = $container;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
echo $this->container->getParameter('email_api');
}
}
I always get this error:
Call to a member function getParameter() on null
The parameter is not null, it does have a value. I know it's telling me that $this->container is null. How do I fix this?
If I run this inside of my controller, it works fine and returns Google
echo $this->getParameter('email_api');

Inject container not a good idea. Try this
services.yaml
App\Repository\EmailRepository:
arguments:
$emailApi: '%env(EMAIL_API)%'
Repository
class EmailRepository
{
protected $emailApi;
public function __construct(string $emailApi)
{
$this->emailApi = $emailApi;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
return $this->emailApi;
}
}
Or via setter injection
services.yaml
App\Repository\EmailRepository:
calls:
- method: setEmailApi
arguments:
$emailApi: '%env(EMAIL_API)%'
Repository
class EmailRepository extends EntityRepository implements ContainerAwareInterface
{
protected $emailApi;
public function setEmailApi(string $emailApi)
{
$this->emailApi = $emailApi;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
return $this->emailApi;
}
}

Your original code is not going to work because there is nothing calling EmailRepository::setContainer. Furthermore, using ContainerAware and injecting the full container is discouraged.
Fortunately, the Doctrine bundle has a new base repository class that the entity manager can use to pull the repository from container and allow you to inject additional dependencies as needed. Something like:
namespace App\Repository;
use App\Entity\Email;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class EmailRepository extends ServiceEntityRepository // Different class to extend from
{
private $emailApi;
public function __construct(RegistryInterface $registry, ParameterBagInterface $parameterBag)
{
parent::__construct($registry, Email::class);
$this->emailApi = $parameterBag->get('email_api');
}
So in this case we inject all the parameters and then store the ones we need.
Even injecting the parameter bag is a bit frowned upon. Better to inject individual parameters though this takes just a bit more configuration as we need to use services.yaml to explicitly inject the needed parameters:
public function __construct(RegistryInterface $registry, string $emailApi)
{
parent::__construct($registry, Email::class);
$this->emailApi = $emailApi;
}
#services.yaml
App\Repository\EmailRepository:
$emailApi: 'email_api_value'

Related

Symfony 6 fetching objects without a controller

In previous versions of symfony, you could fetch objects like this
`
public function someMethod()
{
$method = $this->getDoctrine()->getRepository(Method::class)->findOneBy(array('id' => 1));
return $method;
}
`
This was easy because it meant that you could easily make global variables in the twig.yaml file and have dynamic content all around your page.
Now in symfony as far as i know, an argument of ManagerRegistry has to be passed as a argument all the time. Am I being too close minded or is there a work around for this problem?
I tried extending classes and have it pass down that way but it gave me the same workaround errors.
In a controller you can do either this :
class MyController extends AbstractController {
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
}
or
class MyController extends AbstractController {
{ ... }
public function someMethod(EntityManagerInterface $em) {
$em->getRepository(SomeClass::class)
}
}
I took a controller as an example but you can do this into your services. If you work with multiple entity manager, you can use the ManagerRegistry that extends AbstractManagerRegistry and use the method getManager($name)
There is no real workaround for this as you always need to inject it. Depending on what you want to achieve, may be there is solution that can help.
you can also directly inject the repository:
public function someMethod(EntityRepository $repository) {
$entities = $repository->findAll();
}
and of course you can inject it in the construct as well:
class MyController extends AbstractController {
private EntityRepository $repository;
public function __construct(EntityRepository $repository) {
$this->repository = $repository;
}
}
if you are on php 8 you can write:
class MyController extends AbstractController {
public function __construct(private EntityRepository $repository) {}
}

Injecting Services into a Service

I am trying to inject public services like entityManager in the constructor of a service I created but I keep having this error :
Too few arguments to function App\Services\BillingInterface::__construct(), 0 passed in /var/www/.../src/Controller/TestController.php on line 144 and exactly 1 expected.
In my controllers, services are correctly injected in different methods but in the service I created it's not injected in the constructor.
I didn't change anything in the services.yaml as the documentation says autowire is automatic in Symfony 4.2
PS : I recently updated from Symfony 4.1 to 4.2 and I'm not sure but I think it worked before.
Maybe a library didn't updated correctly but I don't find any errors.
Here are the informations for the service
Service code :
#/src/Services/BillingInterface
namespace App\Services;
use Doctrine\ORM\EntityManagerInterface;
class BillingInterface {
private $em;
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
}
Controller code :
namespace App\Controller;
use App\Services\BillingInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController {
public function teest(EntityManagerInterface $entityManager)
{
$billing = new BillingInterface();
}
}
And If I instantiate BillingInterface with $entityManager parameter of Controller, it works but I would like it injected directly in the BillingInterface class constructor.
And finally, here is what is written in Symfony's documentation :
// 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!');
// ...
}
}
Link : https://symfony.com/doc/current/service_container.html
Chapter : Injecting Services/Config into a Service
So, I don't know what's wrong with my Service.
Thank you for your answers.
Since your BillingInterface is a service - you need to use its instance that is provided by Symfony container instead of attempting to instantiate it by yourself. Your controller needs to inject this service in order to be able to use it:
namespace App\Controller;
use App\Services\BillingInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
/**
* #var BillingInterface
*/
private $billing;
/**
* #param BillingInterface $billing
*/
public function __construct(BillingInterface $billing)
{
$this->billing = $billing;
}
public function teest(EntityManagerInterface $entityManager)
{
// Use $this->billing ...
}
}

Using Doctrine's EntityManager in a repository design

I'm trying to implement a repository pattern in my zend framework 2 application. I have made a service
<?php
class UserService {
private $userRepository;
public function __construct(IUserRepository $repo) {
$this -> userRepository = $repo;
}
public function createUser($params) {
$this -> userRepository -> create($params);
}
public function findAllUsers() {
return $this -> userRepository -> getAllUsers();
}
}
which has a repository that implements an interface
class UserRepository implements IUserRepository {
public function getAllUsers() {
//return all users
}
public function getUserById($id) {
}
public function getOneUser($params){
}
public function getUsers($params){
}
public function create($params){
}
public function update($params){
}
public function delete($params){
}
}
<?php
interface IUserRepository {
public function getAllUsers();
public function getUserById($id);
public function getOneUser($params);
public function getUsers($params);
public function create($params);
public function update($params);
public function delete($params);
}
In my module.php I make use of dependency injection to determine which repository I inject into a controller
public function getControllerConfig() {
return array('factories' => array(
'My\Controller\Accounts' => function(){
return new AccountsController(new UserRepository());
},
),
);
}
In my controller I pass the repository to my service
class AccountsController extends AbstractActionController {
private $service;
public function __construct(IUserRepository $repo) {
$this->service = new UserService($repo);
}
public function indexAction() {
$all_users = $this->service->findAllUsers();
return new ViewModel(array('users' => $all_users));
}
}
My problem is that I'm using Doctrine as Orm and I want to use the entitymanager in my repositories but I don't know how to do that, any ideas and feedback are appreciated
There are several ways to do this, of course. The typical way you'd do this kind of thing in a ZF2/D2 project would be to start with DoctrineORMModule.
That module exposes Doctrine's EntityManager via the ZF2 Service Manager in a variety of handy ways (you can $sm->get('doctrine.entitymanager.orm_default') to explicitly get the EM instance).
Once you can get your entitymanager from the SM, you write a factory for your repository, and inject the EM.
That said, there's a cleaner way. Doctrine has built-in support for repositories, and you can extend the default implementation.
Your repository would then look like this:
<?php
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository implements IUserRepository {
public function getAllUsers() {
return $this->findAll();
}
// ...
}
Just remember to add the repository class to the User Entity's metadata. For example, with an annotation:
/**
* #ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
}

Symfony2 Extending Controller getParameter()

I Want to extend Symfony2 Controller to my project that is using API but I am having error of a non object use getParameter() function look at my code:
namespace Moda\CategoryBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ApiController extends Controller
{
/**
* #var String
*/
protected $_host;
/**
* #var String
*/
protected $_user;
/**
* #var String
*/
protected $_password;
public function __construct()
{
$this->_host = $this->container->getParameter('api_host');
$this->_user = $this->container->getParameter('api_user');
$this->_password = $this->container->getParameter('api_password');
}
}
And next Controller
namespace Moda\CategoryBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class CategoryController extends ApiController
{
/**
* #Route("/category", name="_category")
* #Template()
*/
public function indexAction()
{
return array('name' => 'test');
}
}
And the end, I got this Fatal Error:
FatalErrorException: Error: Call to a member function getParameter()
on a non-object in (..)
I try to use $this->setContainer() but it doesn't work. Do you have any idea how can I slove this problem?
If your controller is not defined as service, The constructor execution of the controller is not persisted.
You have two options to solve your situation:
Define the controller as a service and inject the parameters you need using dependency injection.
Add an init method in the controller, or on a parent abstract controller, and call the init method, before the action you need to have these parameters available;
You cant use container in Controller __construct at reason that when constructor called where is none container set yeat.
You can simply define some simple methods in controller like
class ApiController extends Controller
{
protected function getApiHost()
{
return $this->container->getParameter('api_host');
}
}
I wonder if something crazy like this would work? Instead of overriding the constructor, override the setContainer method? I haven't tried it...just thinking out loud.
namespace Moda\CategoryBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ApiController extends Controller
{
/**
* #var String
*/
protected $_host;
/**
* #var String
*/
protected $_user;
/**
* #var String
*/
protected $_password;
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->_host = $this->container->getParameter('api_host');
$this->_user = $this->container->getParameter('api_user');
$this->_password = $this->container->getParameter('api_password');
}
}

PHP/Laravel - Can't initiate extend of abstract class

I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/

Categories