Symfony2 global functions - php

For example i have algorithmic function, which calculates specific hash-code. Function itself is 300+ lines of code. I need to use that functions many times in many different controllers in my bundle. Where can i store my calculate_hash() to use it in my bundle ? Can i access it from other bundles ?
Can i also write global calculate_hash() which have access to entity manager ?
Didn't find my answer here.

In the Symfony2 world, this is clearly belonging to a service. Services are in fact normal classes that are tied to the dependency injection container. You can inject them the dependencies you need. For example, say your class where the function calculate_hash is located is AlgorithmicHelper. The service holds "global" functions. You define your class something like this:
namespace Acme\AcmeBundle\Helper;
// Correct use statements here ...
class AlgorithmicHelper {
private $entityManager;
public function __construct(EntityManager $entityManager) {
$this->entityManager = $entityManager;
}
public function calculate_hash() {
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
This class then needs to be made aware to symfony dependecy container. For this, you define you service in the app/config/config.yml files by adding a service section like this:
services:
acme.helper.algorithmic:
class: Acme\AcmeBundle\Helper\AlgorithmicHelper
arguments:
entityManager: "#doctrine.orm.entity_manager"
Just below the service, is the service id. It is used to retrieve your service in the controllers for example. After, you specify the class of the service and then, the arguments to pass to the constructor of the class. The # notation means pass a reference to the service with id doctrine.orm.entity_manager.
Then, in your controller, you do something like this to retrieve the service and used it:
$helper = $this->get('acme.helper.algorithmic');
$helper-> calculate_hash();
Note that the result of the call to $this->get('acme.helper.algorithmic') will always return the same instance of the helper. This means that, by default, service are unique. It is like having a singleton class.
For further details, I invite you to read the Symfony2 book. Check those links also
The service container section from Symfony2 book.
An answer I gave on accesing service outside controllers, here.
Hope it helps.
Regards,
Matt

Braian in comment asked for Symfony 3 answer, so here is one Symfony 3.3 (released May 2017):
1. The original class remains the same
namespace Acme\AcmeBundle\Helper;
use Doctrine\ORM\EntityManager;
final class AlgorithmicHelper
{
/**
* #var EntityManager
*/
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function calculateHash()
{
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
2. Service registration is much simpler
# app/config/services.yml
services:
_defaults: autowire # this enabled constructor autowiring for all registered services
Acme\AcmeBundle\Helper\AlgorithmicHelper: ~
3. Use constructor injection to get the service
use Acme\AcmeBundle\Helper\AlgorithmicHelper;
class SomeController
{
/**
* #var AlgorithmicHelper
*/
private $algorithmicHelper;
public function __construct(AlgorithmicHelper $algorithmicHelper)
{
$this->algorithmicHelper = $algorithmicHelper;
}
public function someAction()
{
// some code
$hash = $this->algorithmicHelper->calculateHash();
// some code
}
}
You can read about Symfony 3.3 dependency injection (in this case registering services in config and using it in controller) news in these 2 posts:
https://www.tomasvotruba.cz/blog/2017/05/07/how-to-refactor-to-new-dependency-injection-features-in-symfony-3-3/
https://symfony.com/blog/the-new-symfony-3-3-service-configuration-changes-explained

Related

Symfony static/global Logger with doctrine access

maybe i try the impossible thing, but i try so archive a logging system where i can log anywhere with something like Logger::info('some info') or plain LogInfo(...).
So no pre-initialisation with new .. or as parameter/injection (have to us it in some functions with "dynamic" count of parameters). I need to use inside a controller/command and outside in smaller custom made classes and functions, called by some way by the controllers/commands but not extending any containers.
for example i use the MessageGenerator Example from the Symfony Docs
// srcMessageGenerator.php
namespace App\Library\Util;
use Doctrine\ORM\EntityManagerInterface;
class MessageGenerator
{
private $em;
public function setEM(EntityManagerInterface $em){
$this->em = $em;
var_dump('a');
}
public function getHappyMessage(){
var_dump($this->em);
$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];
}
}
I already tried to define it as service
# config/services.yaml
services:
App\Library\Util\MessageGenerator:
arguments: ['#doctrine.orm.entity_manager']
public: true
problem:
i have to inject it to every function i wanna use it - not possible
i tried use it as an static function - services can't be static
i tried to isolate it as static function without beeing a service, but then i don't know how to get the config/package/doctrine settings depending on my env.
i have looked into monolog too, but here is the same problem, i cant use it without declaration outside a controller/command.
anyone have a hint how i can access the default (env based) doctrine connection, or how i can use a service inside/outside a controller/command without declaring it (static like) and giving the entity manager to it.
(a solution to second way would be lovely, so i can "upgrade" to monolog some day using the same solution)
You can use Injecting Services/Config into a Service in Symfony by passing your service to the function in your controller.
Your service:
class MessageGeneratorService
{
private $em;
public function __construct(EntityManagerInterface $em){
$this->em = $em;
}
public function getHappyMessage(){
// the job
}
}
In an other service:
class MyService
{
private $messageGeneratorService;
public __construct(MessageGeneratorService $mGService) {
$this->messageGeneratorService = $mGService
}
// You can use this service like you use MessageGeneratorService
public makeMeHappy() {
$messageGeneratorService->getHappyMessage();
}
}
In a controller:
class MyController
{
/**
* #Route("/example", name="page_example")
*/
public index(MessageGeneratorService $mGService) {
$mGService->getHappyMessage(); // Now, you can use it
return $this->render('example/index.html.twig');
}
}
Now, you can use an instance of MessageGeneratorService in a controller or in a service:
The container will automatically know to pass the logger service when
instantiating the MessageGenerator

How to access not-injected services directly on Symfony 4+?

I'm trying to update Symfony 2.8 to Symfony 4 and I am having serious problems with the Services Injection.
I'm looking the new way to use Services inside Controllers, with auto-wiring:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
....
This way works fine, but I dislike the explicit way to refer MyService as a parameter of the function. This way I don't even need to register the Service in the services.yaml
Is there any way to use Services as in Symfony 2.8:
class DefaultController extends Controller
{
public function index()
{
$var = $this->get('AuxiliarService')->MyFunction(); /*Doesn't need to be explicit indicate before*/
....
With the services.yaml
services:
auxiliar_service:
class: AppBundle\Services\AuxiliarService
arguments:
entityManager: "#doctrine.orm.entity_manager"
container: "#service_container" #I need to call services inside the service
This way I don't need to indicate the Service as a parameter in the function of the Controller. In some cases, inside a Service, I need to call more than 10 services depends on the data, so indicate them as a parameter in the function is annoying.
Another doubt in Symfony 4, is how to call a Service inside another Service without pass it as an argument or parameter. It used to be possible by injecting the service container to be able to call a service inside a service:
$this->container->get('anotherService')
In Symfony 4, I think it is more expensive (in code) use Service because you have to explicitely indicate them when you are going to use them.
tldr; you can achieve that by using Service Subscribers & Locators.
In your controller:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// services you want to access through $this->get()
'auxiliar_service' => AuxiliarService:class,
]);
}
// rest of the implementation
}
If your service needs to implement a similar pattern, you'll need to implement ServiceSubscriberInterface (AbstractController, that you are extending for your controller, already does that for you).
class AuxiliaryService implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function has(string $id): bool
{
return $this->container->has($id);
}
protected function get(string $id)
{
return $this->container->get($id);
}
public static function getSubscribedServices()
{
return [
// array_merge is not necessary here, because we are not extending another class.
'logger' => LoggerInterface::class,
'service2' => AnotherService::class,
'service3' => AndMore::class
];
}
}
That being said, you are very probably not doing things right if you want to continue this way
Before Symfony 4+ you could do $this->get('service') because these controllers all had access to the container. Passing the dependency container around for this it is an anti-pattern, and shouldn't be done.
If you do not declare your dependencies, your dependencies are hidden. Users of the class do not know what it uses, and it's easier to break the system by changing the behaviour of one of the hidden dependencies.
Furthermore, with Symfony providing auto-wiring and a compiled container; dependency injection is both easier to implement and faster to execute.
That you are having trouble with implementing this probably reveals deeper issues with your code in general, and you should do some work on segregating the responsibilities of your classes. The fact that one service may depend on that many other services which you can't even know until runtime it's a very strong smell that the concerns are not well separated.
Try to adapt to the changes, it will do your application and yourself good in the long term (even if brings a small amount of pain right now).

Symfony 4 Dependency Injection

I've got class like this,
use Doctrine\ORM\EntityManagerInterface;
class LoginTools {
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
and in Controller
$logTool = new LoginTools();
Question
Does autowire should pass EntityManagerInterface to LoginTools automatically?
Because when I call LoginTools class without passing the argument I get error
Too few arguments to function App\Utils\LoginTools::__construct(), 0 passed exactly 1 expected
With Regards,
Wiktor.
As you said, LoginTools is a service. That means, that you mustn't create it in code, like you do, Symfony creates it for you and you have just to inject this service in controller instead of EntityManagerInterface.
The thing is that Symfony has DI container, it's purpose is creating services based on your configuration from config/services.yaml and then injecting them into other services' constructors/functions/properties. There's no magic, all this code, which creates your services and inject them into other services, is generated automatically and is saved into var/cache dir by Symfony, you can check it by yourself.

Symfony 3.3 DI. Access a service name (class name) inside service factory

I want to register all Doctrine repositories as services
I found this approach: How to configure dependency injection for repository class in symfony 3
But this way I have to register every single one repository manually.
I'd like to create some Service Factory that would instantiate repositories for me given just a repository class name.
Something like this:
namespace AppBundle\Service;
use Doctrine\ORM\EntityManagerInterface;
class RepositoryFactory
{
/**
* #var EntityManagerInterface
*/
private $manager;
public function __construct(EntityManagerInterface $manager)
{
$this->manager = $manager;
}
public function getRepository(string $repositoryClass)
{
// do some magic that somehow obtaining actual repo
return $repo;
}
}
service.yml
AppBundle\Service\RepositoryFactory:
autowire: true
but Symfony's docs say nothing about how to pass service name that you want to instantiate (if it's even possible) in this factory

Syfmony - load service on boot

I posted another question trying to find a way to statically access a repository class outside of a controller in a custom "helper" class.
So far the only way I have figured out how to achieve this is using the code below. If anyone wants to chime into the other question about "best practice" or "design patterns" please do.
I opened this question to seek the best method on having a singleton service (?) loaded when symfony boots so other classes can access it statically without any dependency injection. I haven't had much luck on finding any official docs or common practices. I know singleton is anti practice, but is the method below the best way, or is there a more ideal solution?
services.yml
parameters:
entity.device: Asterisk\DbBundle\Entity\Device
services:
asterisk.repository.device:
class: Asterisk\DbBundle\Entity\Repositories\DeviceRepository
factory: ["#doctrine.orm.asterisk_entity_manager", getRepository]
arguments:
- %entity.device%
tags:
- {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
DeviceRepository
class DeviceRepository extends \Doctrine\ORM\EntityRepository
{
/** #var ExtendedEntityRepository */
protected static $instance;
public function __construct(EntityManager $entityManager, ClassMetadata $class)
{
parent::__construct($entityManager, $class);
if(static::$instance instanceof static == false)
static::$instance = $this;
}
public static function getInstance()
{
return static::$instance;
}
public function onKernelRequest($event)
{
return;
}
}
Glad to see you are not running around anymore.
Your approach is not going to work unless someone grabs the repository out of the container first so self::$instance is initialized. But you really don't want to do this anyways. Super hacky.
You want to inject the repository service into your kernel listener. Trying to make the repository act as a kernel listener is just not a good design. So just make a service for your repository and then a second one for the listener. It may seem a bit strange at first but it really does work well in practice and it's the way S2 is designed.
If for some reason you are stuck with the notion that you have to be able to access the container globally then be aware that your kernel is defined globally(take a look at app.php) and it has a getContainer method in it.
$repo = $_GLOBAL['kernel']->getContainer()->get('asterisk.repository.device');
But again, there should be no need to do this.
==============================
Update - It looks like you are trying to use the listener functionality just to setup singletons. You should try to avoid singletons but if you really think you need them then the global access to the kernel can be used:
class DeviceRepository extends \Doctrine\ORM\EntityRepository
{
/** #var ExtendedEntityRepository */
protected static $instance;
public static function getInstance()
{
if (!static::$instance) {
static::$instance = $_GLOBAL['kernel']->getContainer()->get('asterisk.repository.device');
}
return static::$instance;
}
Poor design but at least it get's rid of the listener hack and it avoids creating the repository until it's actually needed. It aslo means you can access the repository from commands (listeners are not setup when commands are called).
I do not understand what the profit will be about this method. The idea of the servicecontainer is to make just one instance of each class and give a reference (or pointer if you like) to any method who asks to use this same instance. Let me proof it:
Service definition:
// app/config.yml
services:
app.test:
class: Vendor\AppBundle\Service\Test
and a custom class:
// src/AppBundle/Service/Test.php
namespace AppBundle/Service;
class Test {
public $test = 0;
}
and a controller:
// src/AppBundle/Controller/DefaultController
namespace AppBundle/Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction()
{
$instance1 = $this->get('app.test');
$instance2 = $this->get('app.test');
$instance1->test = 1;
echo $instance2->test; // RETURNS 1 !!!
exit;
}

Categories