Is there any way (other than tag parameters) to access the "providing" bundle (object or name) during a compiler pass that is collecting tagged services?
class MyContainerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('my.service')) {
return;
}
$definition = $container->getDefinition('my.service');
foreach ($container->findTaggedServiceIds('my.tag') as $id => $linkContainer) {
// here is where I would like the bundle name or object
$definition->addMethodCall('add', array(new Reference($id)));
}
}
}
Related
I'd like my bundle to inject a twig global from its config.
class MyBundle extends AbstractBundle
{
public function build(ContainerBuilder $container): void
{
parent::build($container);
$container->addCompilerPass(new TwigPass());
}
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
$theme = $config['theme']; // how to make this a twig global?
// $twig = $builder->getDefinition('twig'); // NOPE! Twig isn't loaded yet.
}
The compiler pass gets twig, which isn't available when my bundle extension is loading.
class TwigPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('twig')) {
return;
}
$theme = 'theme_from_config';
$def = $container->getDefinition('twig');
$def->addMethodCall('addGlobal', ['theme', $theme]);
I'm missing something, or something is out of order. How can the compiler pass get the config data from the extension? Or if that's not the right approach, how can I inject a twig global from my bundle config?
The way to make config data available to a pass is to add the data to the container as a parameter:
class TwigPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('twig')) {
return;
}
$theme = $container->getParameter('my.theme');
$def = $container->getDefinition('twig');
$def->addMethodCall('addGlobal', ['theme', $theme]);
}
}
class CeradMyBundle extends AbstractBundle
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
$container->parameters()->set('my.theme', $config['theme']);
}
To actually get your theme config value into the $config array requires making a Configure class. Fortunately AbstractBundle can simplify this with a configure method:
# config/packages/cerad_my.yaml
cerad_my:
theme: theme_from_config_file
class CeradMyBundle extends AbstractBundle
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
->scalarNode('theme')->defaultValue('theme_default')->end()
->end()
;
}
After getting this to work, it was a tiny bit annoying to have that one extra DI class i.e. TwigPass so I stole a trick from the Kernel and just had the AbstractBundle implement the pass interface:
class CeradMyBundle extends AbstractBundle implements CompilerPassInterface
{
public function build(ContainerBuilder $container): void
{
// Register as a pass
$container->addCompilerPass($this);
}
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
$container->parameters()
->set('my.theme', $config['theme']);
}
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
->scalarNode('theme')->defaultValue('theme_default')->end()
->end()
;
}
// The compiler pass
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('twig')) {
return;
}
$theme = $container->getParameter('my.theme');
$def = $container->getDefinition('twig');
$def->addMethodCall('addGlobal', ['theme', $theme]);
}
}
If you follow this approach then you can actually save your theme as a property in your bundle class and avoid the need to add a public parameter. Your choice.
I have this wrapper:
class APIWrapper {
protected static $clientClass = \Hardcoded_API; // from a library
protected $client;
public function __construct() {
$this->client = new self::$clientClass(array_merge(self::$config, $config));
}
/**
* Does something.
*
* #throws SomeException
*/
public function act() {
$this->client->doSomething();
}
public static function setClientClass($clientClass) {
self::$clientClass = $clientClass;
}
}
Now let's say I am testing functions that make use of this wrapper:
class UnrelatedFunction {
public function callAPI() {
$wrapper = new APIWrapper();
try {
$wrapper->act();
} catch (SomeException $e) {
// ... do other things
}
}
}
class UnrelatedFunctionsTest extends PHPUnit_Framework_TestCase {
public function testUnrelatedFunctionException() {
$unrelatedFunction = new UnrelatedFunction();
$this->setExpectedException('SomeException');
// this call triggers the APIWrapper
$unrelatedFunction->callAPI();
// Some assertions here...
}
}
I need to be able to verify that UnrelatedFunction::callAPI() did its job recovering from the exception. In this case, how can I mock a \Hardcoded_API class, make it throw a SomeException when act() is called, and pass it onto APIWrapper::setClientClass() so that I can test the behaviour?
I have this issue where I'm attempting to create a subrequest in Silex and basically forward my parameters to another controllers. Exhibit A is broken below (after attempts to refactor), and Exhibit B, the original version, works:
Exhibit A ($this->app is lost after creating the request):
class EntriesController {
private $app;
private $req;
public function __construct($app, $req) {
$this->app = $app;
$this->req = $req;
}
public function updateAction() {
//...
//$url defined here (eyesore-ingly long, so not shown)
$subRequest = Request::create($url, 'GET', $params, $this->req->cookies->all(), array(), $this->req->server->all());
//$this->app **no longer** exists here
return $this->app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
EntriesController instance is created below:
class AppControllerProvider implements ControllerProviderInterface {
public function connect(Application $app) {
$controllers = $app['controllers_factory'];
//...
$controllers->patch('/edit', function (Request $req) use ($app) {
$entriesCtrl = new \EntriesController($app, $req);
return $entriesCtrl->updateAction();
});
//...
}
Exhibit B (works just fine):
class AppControllerProvider implements ControllerProviderInterface {
public function connect(Application $app) {
$controllers = $app['controllers_factory'];
$controllers->patch('/edit', function (Request $req) use ($app) {
//...
//$url defined here
$subRequest = Request::create($url, 'GET', $params, $this->req->cookies->all(), array(), $this->req->server->all());
return $this->app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
//...
});
I basically just reorganized the logic from Exhibit B's 'PATCH' /edit method body into a controller class, and I passed the Silex Application instance $app to a new instance of the controller class.
The only difference between Exhibit A and Exhibit B as far as I can tell is that you instantiate a controller object in the path method callback. Maybe there is something wrong with how this controller is setup or a namespace issue? Shootin' in the dark here.
I can confirm that in my silex application the following code does not produce a null $app container:
GlobalControllerProvider.php
<?php
namespace Dev\Pub\Provider\Controller;
use Silex\Application;
use Silex\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\Request;
class GlobalControllerProvider implements ControllerProviderInterface
{
public function connect(Application $app)
{
$controllers = $app['controllers_factory'];
$controllers
->get('/', 'Dev\Pub\Controller\GlobalController::indexAction')
->bind('homepage')
;
$controllers
->patch('/edit', function (Request $req) use ($app) {
$entriesCtrl = new \Dev\Pub\Controller\GlobalController();
return $entriesCtrl->updateAction($app, $req);
});
return $controllers;
}
}
GlobalController.php
<?php
namespace Dev\Pub\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class GlobalController
{
public function indexAction(Application $app, Request $request)
{
return new Response($app['twig']->render('index.html.twig'));
}
public function updateAction(Application $app, Request $request)
{
$url = 'http://silex.local/index_dev.php/';
$params = array();
$subRequest = Request::create($url, 'GET', $params, $request->cookies->all(), array(), $request->server->all());
// outputs: 'Silex\Application'
error_log(print_r(get_class($app),1).' '.__FILE__.' '.__LINE__,0);
// outputs: 1
error_log(print_r(is_object($app),1).' '.__FILE__.' '.__LINE__,0);
return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
index.main.js
$(function(){
console.log('index.main.js');
$.ajax({
url: "http://silex.local/index_dev.php/edit",
method: "PATCH"
}).done(function( data ) {
console.log(data);
}).fail(function( data ) {
console.log(data);
});
});
Please i need some help:
I have the following services:
SERVICE CONFIGURATION IN services.yml
services:
xpad.producto_repository:
class: Xpad\ProductoBundle\Entity\ProductRepository
factory_service: doctrine.orm.clientes_entity_manager
factory_method: getRepository
arguments:
- Xpad\ProductoBundle\Entity\Product
backend_cliente.producto_filters:
class: Xpad\BackendClienteBundle\Filters\ProductFilters
calls:
- [setRepository, ["#xpad.producto_repository="]]
scope: container
AND THE CLASS FOR backend_cliente.producto_filters IS:
namespace Xpad\BackendClienteBundle\Filters;
use Xpad\ProductoBundle\Entity\ProductRepository;
class ProductFilters
{
private $_queryBuilder;
public function getQueryBuilder()
{
return $this->_queryBuilder;
}
public function setQueryBuilder($queryBuilder)
{
$this->_queryBuilder = $queryBuilder;
}
public function setRepository(ProductRepository $productRepository = null)
{
if($this->_queryBuilder == null)
{
$this->_queryBuilder = $productRepository->createQueryBuilder('p');
}
}
}
AND I HAVE THE FOLLOWING ACTION IN ONE OF MY CONTROLLERS:
class ProductController extends Controller
{
............
public function indexAction(Request $request)
{
//SOME CODE
$service_filter = $this->container->get('backend_cliente.producto_filters');
$queryBuilder = $service_filter->getQueryBuilder();
//SOME OTHER CODE
}
MY PROBLEM IS: ANYTIME THAT THE indexAction is execute I GOT A NEW INSTANCE OF backend_cliente.producto_filters SERVICE AND I DON'T KNOW WHY. I NEED AND UNIQUE INSTANCE AS A SINGLETON BECAUSE A HAVE THE $_queryBuilder ATRIBUTTE AND I NEED TO GET THE VALUE OF IT JUST MODIFY ITS VALUE WHEN IS NEEDED;
PLEASE HELP I DON KNOW WHAT I'M DOING WRONG.
Do you have to use the class as a service?
Why don't you use a Singleton, without using a service? Like this:
namespace Xpad\BackendClienteBundle\Filters;
use Xpad\ProductoBundle\Entity\ProductRepository;
class ProductFilters {
private $_queryBuilder;
private static $reference = null;
public function getInstance(){
if (self::$reference === null)
self::$reference = new ProductFilters();
return self::$reference;
}
private function __construct(){}
public function getQueryBuilder()
{
return $this->_queryBuilder;
}
public function setQueryBuilder($queryBuilder)
{
$this->_queryBuilder = $queryBuilder;
}
public function setRepository(ProductRepository $productRepository = null)
{
if($this->_queryBuilder == null)
{
$this->_queryBuilder = $productRepository->createQueryBuilder('p');
}
}
}
And call it with:
...
$filter = ProductFilters::getInstance();
...
I have implemented a new twig extension and I have some text which had to be translated.
Unfortunately when I use a code label it appears as a sample text.
I mean when twig render this following extension, it displays: 5 entity.years instead of 5 years for example:
class MyExtension extends \Twig_Extension {
public function getFilters()
{
return array(
'myextension' => new \Twig_Filter_Method($this, 'myextension'),
);
}
public function myextension ($myId)
{
// ....
// Some operations concerning $myId...
// ....
if($myId!=0) {
$res = $myId. ' '.'entity.year';
} else {
$res = ($months == 0 ? $days.'entity.days' : $months.'entity.months');
}
return $res;
}
}
Where entity.years, entity.months, entity.days is defined into my translations folder.
Inject the translator service into your extension and use it. For example:
class MyExtension extends \Twig_Extension
{
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
// ...
public function myMethod()
{
return $this->translator->trans('my_string');
}
}