How to define services and access it in controller? - php

I have a service file containing following lines of code.
namespace App\Service;
/**
* Class MyService
* #package App\Service
*/
class MyService
{
/**
* #var string
*/
private $newTest;
/**
* MyService constructor.
* #param string $newTest
*/
public function __construct(string $newTest)
{
$this->newTest = $newTest;
}
}
I have defined it under service section in services.yaml file.
my_service:
class: App\Service\MyService
bind:
$pcValue: '%env(resolve:LATEST)%'
Now I am trying to retrieve this pcValue at the controller,
$this->get('my_service');
It is returning me following error:
`Service "my_service" not found: even though it exists in the app's container, the container inside "App\Controller\WheelController" is a smaller service locator that only knows about the "doctrine", "form.factory", "http_kernel", "parameter_bag", "request_stack", "router", "security.authorization_checker", "security.csrf.token_manager", "security.token_storage", "serializer", "session" and "twig" services. Try using dependency injection instead.`

The problem is that you are not using dependency injection correctly to take advantage of auto-wiring.
In order to have your service available on your controller you just need to add the type-hinted service in the desired action in your controller.
Check this

Related

TYPO3: Inject Service Classes into AuthServiceClass with Constructor

I'm using TYPO3 10.2 and trying to inject some Service Classes that I created into my authentication service.
class AuthService extends \TYPO3\CMS\Core\Authentication\AuthenticationService
The Constructor in AuthService:
/**
* Contains the configuration of the current extension
* #var ConfigurationService
*/
protected $configurationService;
/**
* #var RestClientService
*/
protected $restClientService;
/**
* #var ConnectionPool
*/
protected $connectionPool;
/**
*
* #param ConfigurationService $configurationService
* #param RestClientService $restClientService
* #param ConnectionPool $connectionPool
*/
public function __construct(ConfigurationService $configurationService, RestClientService $restClientService, ConnectionPool $connectionPool)
{
$this->configurationService = $configurationService;
$this->restClientService = $restClientService;
$this->connectionPool = $connectionPool;
}
I am getting the following error:
Too few arguments to function Vendor\MyExt\Service\AuthService::__construct(), 0 passed in C:\xampp\htdocs\myproject\typo3\sysext\core\Classes\Utility\GeneralUtility.php on line 3461 and exactly 3 expected
Any advice what's going on here?
I used the same Constructor in my ControllerClass and everything is working fine there.
Thanks so far!
Looks like your AuthenticationService is internally instantiated by GeneralUtility::makeInstance(). This is true for many classes that you register at some point and TYPO3 then takes care of the creation of the class (think of user functions, plugin controller, module controller, authentication services, hooks, etc).
GeneralUtility::makeInstance() needs to get the class out of the DI container for the DI to work but this is only possible for classes made public during container compilation.
For that reason the solution to your problem should be to declare the class AuthService as public in your Configuration/Services.yaml:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\MyExt\:
resource: '../Classes/*'
Vendor\MyExt\Service\AuthService:
public: true
You can find this explained in the official docs or in my blog post about that topic.

How to subscribe to the DataTableFactory service?

I am trying to use DataTablesBundle in Symfony 4.2.
There is very good manual and starter however it is used with extends Controller class. But I am trying to use it with AbstractController
class.
So I have such issue:
Service "Omines\DataTablesBundle\DataTableFactory" not found: even
though it exists in the app's container, the container inside
"App\Controller\StaffController" is a smaller service locator that
only knows about the "doctrine", "form.factory", "http_kernel",
"parameter_bag", "request_stack", "router",
"security.authorization_checker", "security.csrf.token_manager",
"security.token_storage", "serializer", "session" and "twig" services.
Try using dependency injection instead
I have found a solution however I have just started with Symfony so I haven't really understood what it is about:
https://github.com/omines/datatables-bundle/commit/cd9b93eac9ef4fd3c1459305c71ca6e2ac0c444e
If using AbstractController instead, which is currently
recommended practice, ensure you subscribe to the
DataTableFactory service yourself. Alternatively you can
bypass the convenience trait and inject the service via regular
constructor injection
How to subscribe to the DataTableFactory service?
And what does the alternative "bypass the convenience trait and inject the service" mean?
I don't know how to subscribe to a service, but here is how to inject it.
Add a constructor to your controller and inject the DataTableFactory:
//use Omines\DataTablesBundle\DataTableFactory;
private $factory;
public function __construct(
DataTableFactory $factory
) {
$this->factory = $factory;
}
/**
* Creates and returns a basic DataTable instance.
*
* #param array $options Options to be passed
* #return DataTable
*/
protected function createDataTable(array $options = [])
{
return $this->factory->create($options);
}
/**
* Creates and returns a DataTable based upon a registered DataTableType or an FQCN.
*
* #param string $type FQCN or service name
* #param array $typeOptions Type-specific options to be considered
* #param array $options Options to be passed
* #return DataTable
*/
protected function createDataTableFromType($type, array $typeOptions = [], array $options = [])
{
return $this->factory->createFromType($type, $typeOptions, $options);
}
Then you can use it like this:
$this->createDateTable();

Binding to Laravel IoC instead of instantiating again and again

In my app I have a service called "LogService" to log events and other items. I basically need to use this on every controller to log events by users. Instead of having to instantiate this service in each controller, I had two thoughts for accomplishing this.
Option 1: Bind the service into the IoC and then resolve it that way
Option 2: Make a master class with the service in it and then extend it for other classes so they come with the service already bound
I have questions for each of these methods:
Option 1: Is this even possible? If so, would it just be with "App::make()" that it would be called? That way doesn't seem to play too well with IDE's
Option 2: I have done this kind of thing in the past but PHPStorm does not seem to recognize the service from the parent object because it is instantiated by "App::make()" and not through the regular dependency injection.
What would be the best course of action?
Thanks!
You can have it both ways, I think the neatest way would be:
1) Have an interface that describes your class, let's call it LogServiceInterface
2) Create a Service Provider that instantiates your class, like so:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class LoggerServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind(LogServiceInterface::class, function($app)
{
return new LogService();
});
}
}
3) Register this service provider in config/app.ph file:
'providers' => [
// Other Service Providers
App\Providers\LoggerServiceProvider::class,
],
4) Now, in controller you can request the instance of something that implements LoggerServiceInterface straight in the constructor:
(Some controller):
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Repositories\OrderRepository;
class OrdersController extends Controller {
/**
* The logger service.
* #var LoggerServiceInterface $loggerService
*/
protected $loggerService;
/**
* Create a controller instance.
*
* #param OrderRepository $orders
* #return void
*/
public function __construct(LoggerServiceInterface $loggerService)
{
$this->loggerService = $loggerService;
}
/**
* Show all of the orders.
*
* #return Response
*/
public function index()
{
// $this->loggerService will be an instance of your LoggerService class that
// is instantiated in your service provider
}
}
This way, you have got an easy way to quickly change the implementation of your service, moreover, Phpstorm can handle this very easily.
You will still be able to use app()->make() to obtain an instance of your service.
This, however, will not be automatically picked up by Phpstorm. But you can help it to understand that, all you need to do is to use #var annotation, see:
/**
* #var LoggerServiceInterface $logger
*/
$logger = app()->make(LoggerServiceInterface::class);
That way, Phpstorm will know what to expect from that $logger object.

Symfony - inject doctrine repository in service

according to How to inject a repository into a service in Symfony2?
it's like
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments:
- 'Acme\FileBundle\Model\File'
but I get an Exception
Invalid service "acme.custom_repository": class
"EntityManager5aa02de170f88_546a8d27f194334ee012bfe64f629947b07e4919__CG__\Doctrine\ORM\EntityManager"
does not exist.
How can I do this in Symfony 3.4?
update:
EntityClass is actually a valid class FQCN (also used copy reference on phpstorm to be sure) , just renamed it because a companies name is in it :). updated it anyway.
solution
BlueM's solution works perfectly.
In case you are not using autowiring here's the service defintion:
Acme\AcmeBundle\Respository\MyEntityRepository:
arguments:
- '#Doctrine\Common\Persistence\ManagerRegistry'
- Acme\AcmeBundle\Model\MyEntity # '%my_entity_class_parameter%'
As you are using Symfony 3.4, you can use a much simpler approach, using ServiceEntityRepository. Simply implement your repository, let it extend class ServiceEntityRepository and you can simply inject it. (At least when using autowiring – I haven’t used this with classic DI configuration, but would assume it should also work.)
In other words:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class ExampleRepository extends ServiceEntityRepository
{
/**
* #param ManagerRegistry $managerRegistry
*/
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry, YourEntity::class);
}
}
Now, without any DI configuration, you can inject the repository wherever you want, including controller methods.
One caveat (which equally applies to the way you try to inject the repository): if the Doctrine connection is reset, you will have a reference to a stale repository. But IMHO, this is a risk I accept, as otherwise I won’t be able to inject the repository directly..
Create the custom repository properly
First, you need to create the repository custom class that extends the default repository from doctrine:
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
// your own methods
}
Then you need this annotation in the entity class:
/**
* #ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
Then you define the repository in the .yml file:
custom_repository:
class: MyDomain\Model\UserRepository
factory: ["#doctrine", getRepository]
arguments:
- Acme\FileBundle\Model\File
Make sure that in the definition of your repository class points to your custom repository class and not to Doctrine\ORM\EntityRepository.
Inject custom services into your custom repository:
On your custom repository create custom setters for your services
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
protected $paginator;
public function setPaginator(PaginatorInterface $paginator)
{
$this->paginator = $paginator;
}
}
Then inject them like this:
custom_repository:
class: MyDomain\Model\UserRepository
factory: ["#doctrine", getRepository]
arguments:
- Acme\FileBundle\Model\File
calls:
- [setPaginator, ['#knp_paginator']]
Inject your repository into a service:
my_custom_service:
class: Acme\FileBundle\Services\CustomService
arguments:
- "#custom_repository"
Check the arguments is a valid class (with FQCN or with a bundle simplification) as example:
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory:
- '#doctrine.orm.entity_manager'
- getRepository
arguments:
- Acme\MainBundle\Entity\MyEntity
or
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory:
- '#doctrine.orm.entity_manager'
- getRepository
arguments:
- AcmeMainBundle:MyEntity
Hope this help
Solutions I could see here so far are not bad. I looked at it from a different angle. So my solution allows you to keep clean repositories, sorta enforces consistent project structure and you get to keep autowiring!
This is how I would solve it in Symfony 5.
GOAL
We want to have autowired Repositories and we want to keep them as clean as possible. We also want them to be super easy to use.
PROBLEM
We need to figure out a way to tell Repository about the entity it should use.
SOLUTION
The solution is simple and consists of a few things:
We have custom Repository class which extends Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository class.
Our custom class has public string $entity property on it.
When we create our new repository and extend our custom repository class we have two choices: on our new repository we can just point to the class like this
namespace App\Database\Repository\Post;
use App\Database\Repository\Repository;
use App\Entity\Blog\Post;
/**
* Class PostRepository
* #package App\Database\Repository
*/
class PostRepository extends Repository
{
public string $entity = Post::class;
public function test()
{
dd(99999, $this->getEntityName());
}
}
or we could omit that property and let our new base Repository class find it automatically! (More about that later.)
CODE
So let's start with the code and then I will explain it:
<?php
namespace App\Database\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Laminas\Code\Reflection\ClassReflection;
use Symfony\Component\Finder\Finder;
/**
* Class Repository
* #package App\Database\Repository
*/
abstract class Repository extends ServiceEntityRepository
{
/** #var string */
private const REPOSITORY_FILE = 'repository';
/** #var string */
public string $entity = '';
/** #var string */
public string $defaultEntitiesLocation;
/** #var string */
public string $defaultEntitiesNamespace;
/**
* Repository constructor.
*
* #param ManagerRegistry $registry
* #param $defaultEntitiesLocation
* #param $defaultEntitiesNamespace
* #throws \Exception
*/
public function __construct(
ManagerRegistry $registry,
$defaultEntitiesLocation,
$defaultEntitiesNamespace
) {
$this->defaultEntitiesLocation = $defaultEntitiesLocation;
$this->defaultEntitiesNamespace = $defaultEntitiesNamespace;
$this->findEntities();
parent::__construct($registry, $this->entity);
}
/**
* Find entities.
*
* #return bool
* #throws \ReflectionException
*/
public function findEntities()
{
if (class_exists($this->entity)) {
return true;
}
$repositoryReflection = (new ClassReflection($this));
$repositoryName = strtolower(preg_replace('/Repository/', '', $repositoryReflection->getShortName()));
$finder = new Finder();
if ($finder->files()->in($this->defaultEntitiesLocation)->hasResults()) {
foreach ($finder as $file) {
if (strtolower($file->getFilenameWithoutExtension()) === $repositoryName) {
if (!empty($this->entity)) {
throw new \Exception('Entity can\'t be matched automatically. It looks like there is' .
' more than one ' . $file->getFilenameWithoutExtension() . ' entity. Please use $entity
property on your repository to provide entity you want to use.');
}
$namespacePart = preg_replace(
'#' . $this->defaultEntitiesLocation . '#',
'',
$file->getPath() . '/' . $file->getFilenameWithoutExtension()
);
$this->entity = $this->defaultEntitiesNamespace . preg_replace('#/#', '\\', $namespacePart);
}
}
}
}
}
Ok, so what is happening here? I have bound some values to the container in services.yml:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
$defaultEntitiesLocation: '%kernel.project_dir%/src/Entity'
$defaultEntitiesNamespace: 'App\Entity'
Then in our new extension class, I know where by default to look for my Entities (this enforces some consistency).
VERY IMPORTANT BIT - I assume that we will name Repositories and Entities with exactly the same so for example: Post will be our Entity and PostRepository is our repository. Just note that the word Repository is not obligatory. If it is there it will be removed.
Some clever logic will create namespaces for you - I assume that you will follow some good practices and that it will all be consistent.
It's done! To have your repository autowired all you need to do is extend your new base repository class and name Entity the same as the repository. so End result looks like this:
<?php
namespace App\Database\Repository\Post;
use App\Database\Repository\Repository;
use App\Entity\Blog\Post;
/**
* Class PostRepository
* #package App\Database\Repository
*/
class PostRepository extends Repository
{
public function test()
{
dd(99999, $this->getEntityName());
}
}
It is CLEAN, AUTOWIRED, SUPER EASY AND QUICK TO CREATE!
What about the drawbacks about the ServiceEntityRepository?
https://symfony.com/doc/current/doctrine/multiple_entity_managers.html
One entity can be managed by more than one entity manager. This
however results in unexpected behavior when extending from
ServiceEntityRepository in your custom repository. The
ServiceEntityRepository always uses the configured entity manager for
that entity.
In order to fix this situation, extend EntityRepository instead and no
longer rely on autowiring:
In an own project I've seen that using:
$repository = $entityManager->getRepository(MyEntity:class)
The $repository->_em is not equals to $entityManager (with both using the same connection), causing problems like:
$entity = $entityManager->getRepository(MyEntity:class)->find($id);
$entityManager->refresh($entity); // throws 'entity is not managed'
That's why the entity is fetched with $repository->_em and the refresh (or persist, flush, etc.) is using $entityManager.
This problem is described here:
https://github.com/symfony/symfony-docs/issues/9878
So... You can't rely in ServiceEntityRepository using multiple entity managers, but the EntityRepository doesn't allow autowire, so, what?
My two cents (I believe this should be works in every scenario):
Manually set the class metadata (something like you need to do in the constructor of the ServiceEntityManager), so I can:
Remove the autowire of repositories in services.yaml:
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Repository,Tests,Kernel.php,Client}'
(you also add the repositories below in services.yaml)
And create another /config/packages/repositories.yaml and add:
my.entity.metadata:
class: App\Entity\Metadata
arguments:
$entityName: App\Entity\MyEntity
App\Repository\MyEntityRepository:
arguments:
[$class: my.entity.metadata]
Now you have a EntityRepository that is capable of being autowireable. You can make a repositories.yaml file in the config and keep updated when you create/edit/delete your repositories. Is not cleanest but it should be works.

Dependency injection of FilesystemCache (SimpleCache) in Symfony 4.0

I am trying to implement a SimpleCache concrete instance in one of my service classes to also allow caching, however I am having some issues at wiring the dependencies.
config/services.yml
services:
Psr\SimpleCache\CacheInterface: '#Symfony\Component\Cache\Simple\FilesystemCache'
src/Services/Shouter/Sources/Twitter.php
<?php
namespace App\Services\Shouter\Sources;
use Psr\SimpleCache\CacheInterface;
class Twitter
{
/**
* Cache instance
* #var Psr\SimpleCache\CacheInterface
*/
protected $cache;
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
}
This is the error that I get:
You have requested a non-existent service "Symfony\Component\Cache\Simple\FilesystemCache".
Fixed by adding Symfony\Component\Cache\Simple\FilesystemCache: in the services.yaml.

Categories