Symfony service attem - symfony

i'm trying to configure services in symfony2:
services.yml
parameters:
trendio.user_provider.class: Trendio\DelivBundle\TrendioUserProvider
trendio.rest.class: Trendio\DelivBundle\Services\TrendioRest
trendio.rest.backend_ip: 192.168.0.102
services:
trendio_rest:
class: %trendio.rest.class%
arguments: [%trendio.rest.backend_ip%,#serializer,#buzz]
I got a error :
ClassNotFoundException: Attempted to load class "TrendioRest" from namespace "Trendio\DelivBundle\Services" in /var/www/deliv/app/cache/dev/appDevDebugProjectContainer.php line 3176. Do you need to "use" it from another namespace?
TrendioRest.php
<?php
namespace Trendio\DelivBundle\Services;
class TrendioRest { ... }
But,if i move class to top-level bundle namespace (Trendio\DelivBundle), service worked correctly. Why class doesn't load load from sub-folders namespaces?

It's best practise to use Symfony class
Since new Symfony 3.3 DI features you can simplify it like this:
parameters:
trendio.rest.backend_ip: 192.168.0.102
services:
_defaults:
autowire: true
Trendio\DelivBundle\TrendioUserProvider: ~
Trendio\DelivBundle\Services\TrendioRest:
arguments: [%trendio.rest.backend_ip%]

Related

Symfony 3.4 NotFoundHttpException No route found for "GET /lucky/number"

I'v created a symfony 3.4 project,and I've create a controller following the doc (I had to change the controller namespace)
<?php
namespace App\Controller;
//namespace AppBundle\Controller; this is the default namespace in the doc
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
/**
* #Route("/lucky/number")
*/
public function numberAction()
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
But when I try to access to http://127.0.0.1:8000/lucky/number I get this error:
NotFoundHttpException No route found for "GET /lucky/number"
I tried to clean the cache but didn't work and think I dont have to download any anootations library, I don't know what is wrong
If you changed the namespace you also need to make some changes to your config.
You can tag the individual controller with controller.service_arguments:
# app/config/services.yml
services:
# ...
# explicitly configure the service
App\Controller\LuckyController:
tags: [controller.service_arguments]
Or if you have changed the entire AppBundle namespace to App and you are using the Symfony Standard Edition (version 3.4) services.yml configuration, just change all instances of AppBundle to App:
# app/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
public: false
# makes classes in src/App available to be used as services
App\:
resource: '../../src/App/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/App/{Entity,Repository}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
App\Controller\:
resource: '../../src/App/Controller'
public: true
tags: ['controller.service_arguments']
Of course always clear the cache after making any changes.

Symfony 4 services local binding in different environments

I have to bind parameters with different values in different environments, and having problems with this.
I was trying this:
# config/services.yaml
services:
_defaults:
bind:
$param: 'param for PROD'
# config/services_dev.yaml
services:
_defaults:
bind:
$param: 'param for DEV'
# src/Controller/SomeController.php
class MyController extends AbstractController
{
public function example($param)
{
echo $param;
}
}
But it forces me to have all the services defined in both of services.yaml and services_dev.yaml files, otherwise it does not work.
I would like to have a services.yaml shared for any environment, and only override the custom services/bindings etc, not have two identical files with all services listed in them for changing one binding value.
The real problem is that I have to create two http clients (real and a dummy) with same interface, in production load the real one, and in development load the dummy, Symfony 4-s autowiring allows me to inject the interface in a controller and choose which client to use in binding:
# config/services.yaml
services:
_defaults:
bind:
'ClientInterface': '#real_client'
# More services here...
# config/services_dev.yaml
services:
_defaults:
bind:
'ClientInterface': '#dummy_client'
# Here I don't want to have another copy of the services,
# but it does not work without them
# Controller
public function someMethod(ClientInterface $client)
{
// ...
}
In Symfony 2 I was able to extend services.yml and in services_dev.yml only define the specific values I wanted to override/add, but in Symfony 4 services_dev.yaml can not use services from services.yaml and I have to keep my services identical in two different files which is pain.
Anny suggestions?
Thank you.
I'm updating the post again with a real example:
services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
app.access_token: '%env(string:APP_ACCESS_TOKEN)%'
app.aws_version: '%env(string:AWS_VERSION)%'
app.aws_profile: '%env(string:AWS_PROFILE)%'
app.aws_region: '%env(string:AWS_REGION)%'
app.aws_queue_url_creation: '%env(string:AWS_QUEUE_URL_CAMPAIGN_CREATION)%'
app.aws_queue_url_edition: '%env(string:AWS_QUEUE_URL_CAMPAIGN_EDITION)%'
app.redis_host: '%env(string:REDIS_HOST)%'
app.redis_port: '%env(string:REDIS_PORT)%'
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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
App\Service\MessageSenderServiceInterface: '#App\Service\MessageSenderSqsService'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
# Authenticators
App\Security\ApiKeyAuthenticator:
arguments:
- "%app.access_token%"
# Clients
App\Client\AwsSqsClient:
arguments:
- "%app.aws_version%"
- "%app.aws_profile%"
- "%app.aws_region%"
App\Client\RedisClient:
arguments:
- "%app.redis_host%"
- "%app.redis_port%"
# Services
App\Service\MessageSenderSqsService:
arguments:
- '#App\Client\AwsSqsClient'
- '#App\Client\RedisClient'
- "%app.aws_queue_url_creation%"
- "%app.aws_queue_url_edition%"
App\Service\MessageSenderRedisService:
arguments:
- '#App\Client\RedisClient'
services_dev.yaml
imports:
- { resource: services.yaml }
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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
App\Service\MessageSenderServiceInterface: '#App\Service\MessageSenderRedisService'
Controller.php
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
/**
* #Route("/api/dummy")
*/
public function dummyEndpoint(MessageSenderServiceInterface $messageSender)
{
echo get_class($messageSender); exit;
}
}
And the echo from controller for both envs (prod and dev) is
App\Service\MessageSenderSqsService
But if I copy whole node "services" form services.yaml to services_dev.yaml and only change the binding config, it works fine and says that the injected class is:
App\Service\MessageSenderRedisService
I've just noticed that if I don't touch the "_defaults" node, it works as expected, the problems start only when I want to override the _defaults node of services...
You can define parameters in parameters section of config.yml and overwrite this parameters in config_dev.yml.
# config.yml
imports:
# ...
parameters:
parameter_1: value 1
parameter_2: value 2
# ...
framework:
# ...
# config_dev.yml
imports:
# ...
parameters:
parameter_1: dev value 1
# ...
framework:
# ...
This parameters can be used used in service.yml as:
# service.yml
services:
_defaults:
bind:
$param: '%parameter_1%'
Finally the problem was only in overriding the "_defaults" node (which I was touching in order to have different "bind" configs in the project).
Extending services.yaml without overriding _defaults, everything works as expected. And the solution is to have different configuration for services with their bindings by environment, and have "_defaults" only in services.yaml.
If we override the "_defaults" in other files, we'll have to redefine all the services too.
Thanks everyone for help.
You have some options:
1.Don't use bind and write different service configs for different environments
# services.yaml
App\Controller:
arguments:
- "#client"
# services_dev.yaml
App\Controller:
arguments:
- "#dummy_client"
2.Use bind and create service alias in each environment's services.yaml:
# services.yaml
services:
some.client:
alias: "#client"
# services_dev.yaml
services:
some.client:
alias: "#dummy_client"
3.Just configure only one ClientInterface service per environment:
# services.yaml
App\ClientInterface:
class: App\RealClient
# services_dev.yaml
App\ClientInterface:
class: App\DummyClient
4.Use factory which will create this client depends on environment (but this is not very good practice as for me)
# services.yaml
App\ClientInterface:
factory: ["#App\ClientFactory", create]
arguments:
- '%kernel.environment%'
class ClientFactory
{
public function create(string $env): ClientInterface
{
if ($env === 'dev') {
return new DummyClient();
} else {
return new Client();
}
}
}
5.In your case, when you have so much services and you want to inject same service in all of them, you can use option #3 or you can create one interface for all of them and use _instanceof:
# services.yaml
_instanceof:
App\SomeCommonInterface:
calls:
- method: setSomeService # interface's method
arguments:
- '#service'
# services_dev.yaml
_instanceof:
App\SomeCommonInterface:
calls:
- method: setSomeService
arguments:
- '#dummy_service'

Symfony 3.3.6 change to service auto loading from 3.2

I updated to 3.3.6 yesterday and in setting up a new service I have created in AppBundle. Old style of service registration works - when I try the new approach it is not.
I have a service
AppBundle/Service/SomeService.php
namespace AppBundle\Service;
class SomeService {
}
services.yml
services:
# THIS WORKS
#app.some_service:
# class: AppBundle\Service\SomeService
# arguments: []
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
Inside my action I try and use the service:
$someService = $this->get(SomeService::class);
// THIS WORKS
//$someService = $this->get('app.some_service');
I get the following error:
You have requested a non-existent service
"AppBundle\Service\SomeService".
What am I missing or not understanding about the changes made to 3.3?
I feel it's likely something I missed due to upgrading from 3.2 to 3.3+
Ideas?

Injecting service with JMS\DiExtraBundle

I have service PgHistService in subdirectory Service in DbExtensionBundle:
namespace Iba\DbExtensionBundle\Service;
class PgHistService { ...}
This service is defined in bundles's services.yml and can be sucessfully included in a controller via $this->get('pghist.service'):
parameters:
pghist.service.class: Iba\DbExtensionBundle\Service\PgHistService
services:
pghist.service:
class: %pghist.service.class%
arguments:
entityManager: "#doctrine.orm.entity_manager"
Now I want to inject it with JMS\DIExtraBundle in doctrine entity listener:
namespace Iba\DbExtensionBundle\Entity;
use JMS\DiExtraBundle\Annotation as DI;
class BaseEntityListener {
/** #DI\Inject("pghist.service") */
public $pgHist;
}
Variable pgHist is always null. What am I doing wrong, please? I tried to set this in config.yml but it doesn't work either:
jms_di_extra:
locations:
all_bundles: false
bundles: [DbExtensionBundle]
directories: ["%kernel.root_dir%/../vendor/iba/db-extension-bundle/Iba/DbExtensionBundle/Service"]
Jason Roman is right, thank you.
If you want to use JMS\DiExtraBunde together with entity listener, you have to use DIExtraBundle own system of invoking listener via annotation #DoctrineListener in listener instead of Doctrine standard one #EntityListeners in the entity.

Symfony 2: Creating a service from a Repository

I'm learning Symfony and I've been trying to create a service, using a repository.
I've created my repositories and entities from generate:entity, so they should be fine.
So far what I got in my services.yml is:
parameters:
mytest.entity: TestTestBundle:Brand
mytest.class: Test\TestBundle\Entity\Brand
default_repository.class: Doctrine\ORM\EntityRepository
services:
myservice:
class: %default_repository.class%
factory-service: doctrine.orm.default_entity_manager
factory-method: getRepository
arguments:
- %mytest.entity%
But when I try to call the service, I get this error:
Catchable Fatal Error: Argument 2 passed to Doctrine\ORM\EntityRepository::__construct() must be an instance of Doctrine\ORM\Mapping\ClassMetadata, none given, called in
Then I tried to create the service just using an entity. My services.yml would look like:
services:
myservice:
class: %mytest.class%
factory-service: doctrine.orm.default_entity_manager
factory-method: getRepository
arguments:
- %mytest.entity%
But for this, I get:
Error: Call to undefined method
Test\TestBundle\Entity\Brand::findAll
Does anybody know what am I doing wrong?
Thanks
DEPRECATION WARNING: No more factory_service and factory_method. This is how you should do it since Symfony 2.6 (for Symfony 3.3+ check below):
parameters:
entity.my_entity: "AppBundle:MyEntity"
services:
my_entity_repository:
class: AppBundle\Repository\MyEntityRepository
factory: ["#doctrine", getRepository]
arguments:
- %entity.my_entity%
The new setFactory() method was introduced in Symfony 2.6. Refer to older versions for the syntax for factories prior to 2.6.
http://symfony.com/doc/2.7/service_container/factories.html
EDIT: Looks like they keep changing this, so since Symfony 3.3 there's a new syntax:
# app/config/services.yml
services:
# ...
AppBundle\Email\NewsletterManager:
# call the static method
factory: ['AppBundle\Email\NewsletterManagerStaticFactory', createNewsletterManager]
Check it out: http://symfony.com/doc/3.3/service_container/factories.html
Here is how we did it in KnpRadBundle: https://github.com/KnpLabs/KnpRadBundle/blob/develop/DependencyInjection/Definition/DoctrineRepositoryFactory.php#L9
Finally it should be:
my_service:
class: Doctrine\Common\Persistence\ObjectRepository
factory_service: doctrine # this is an instance of Registry
factory_method: getRepository
arguments: [ %mytest.entity% ]
UPDATE
Since 2.4, doctrine allows to override the default repositor factory.
Here is a possible way to implement it in symfony: https://gist.github.com/docteurklein/9778800
You may have used the wrong YAML-Keys. Your first configuration works fine for me using
factory_service instead of factory-service
factory_method instead of factory-method
Since 2017 and Symfony 3.3+ this is now much easier.
Note: Try to avoid generic commands like generate:entity. They are desined for begginers to make project work fast. They tend to bare bad practises and take very long time to change.
Check my post How to use Repository with Doctrine as Service in Symfony for more general description.
To your code:
1. Update your config registration to use PSR-4 based autoregistration
# app/config/services.yml
services:
_defaults:
autowire: true
Test\TestBundle\:
resource: ../../src/Test/TestBundle
2. Composition over Inheritance - Create own repository without direct dependency on Doctrine
<?php
namespace Test\TestBundle\Repository;
use Doctrine\ORM\EntityManagerInterface;
class BrandRepository
{
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Brand::class);
}
public function findAll()
{
return $this->repository->findAll();
}
}
3. Use in any Service or Controller via constructor injection
use Test\TestBundle\Repository\BrandRepository;
class MyController
{
/**
* #var BrandRepository
*/
private $brandRepository;
public function __construct(BrandRepository $brandRepository)
{
$this->brandRepository = $brandRepository;
}
public function someAction()
{
$allBrands = $this->brandRepository->findAll();
// ...
}
}
I convert service.yml to service.xml, and update DependencyInjection Extension, everything is working for me. I don't know why, but yml config will thrown Catchable Fatal Error. You can try using xml config for service config.
service.yml:
services:
acme.demo.apikey_userprovider:
class: Acme\DemoBundle\Entity\UserinfoRepository
factory-service: doctrine.orm.entity_manager
factory-method: getRepository
arguments: [ AcmeDemoBundle:Userinfo ]
acme.demo.apikey_authenticator:
class: Acme\DemoBundle\Security\ApiKeyAuthenticator
arguments: [ "#acme.demo.apikey_userprovider" ]
service.xml:
<services>
<service id="acme.demo.apikey_userprovider" class="Acme\DemoBundle\Entity\UserinfoRepository" factory-service="doctrine.orm.entity_manager" factory-method="getRepository">
<argument>AcmeDemoBundle:Userinfo</argument>
</service>
<service id="acme.demo.apikey_authenticator" class="Acme\DemoBundle\Security\ApiKeyAuthenticator">
<argument type="service" id="acme.demo.apikey_userprovider" />
</service>
</services>
Symfony 3.3 and doctrine-bundle 1.8 there is a Doctrine\Bundle\DoctrineBundle\Repository\ContainerRepositoryFactory
which helps to create repository as service.
Example
What we want
$rep = $kernel->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository(Brand::class);
ORM description
# Brand.orm.yaml
...
repositoryClass: App\Repository\BrandRepository
...
Service description
# service.yaml
App\Repository\BrandRepository:
arguments:
- '#doctrine.orm.entity_manager'
- '#=service("doctrine.orm.entity_manager").getClassMetadata("App\\Entity\\Brand")'
tags:
- { name: doctrine.repository_service }
calls:
- method: setDefaultLocale
arguments:
- '%kernel.default_locale%'
- method: setRequestStack
arguments:
- '#request_stack'
sf 2.6+
parameters:
mytest.entity: TestTestBundle:Brand
mytest.class: Test\TestBundle\Entity\Brand
default_repository.class: Doctrine\ORM\EntityRepository
services:
myservice:
class: %default_repository.class%
factory: ["#doctrine.orm.default_entity_manager", "getRepository"]
arguments:
- %mytest.entity%

Resources