I'm developping a symfony2 application with the DatatableBundle (https://github.com/AliHichem/AliDatatableBundle)
I need to override the DoctrineBuilder class in the Util Directory
I've created a DtatableBundle in my src directory with all the structure of the Alidatatable Bundle
I've write a getParentMethod in the newBundle, and create my new DoctrineBuilde class
Symfony always use the vendor class and not the new one
Here,s the Bundle Structure and then class i want to override :
DatatableBundle
Util
Factory
Query
DoctrineBuilder.php
and the bundle's service definition:
parameters:
datatable.class: Ali\DatatableBundle\Util\Datatable
services:
datatable:
class: "%datatable.class%"
arguments: [ #service_container ]
scope: prototype
datatable.twig.extension:
class: Ali\DatatableBundle\Twig\Extension\AliDatatableExtension
arguments: [ #service_container ]
tags:
- { name: twig.extension }
Any Ideas ?
Thansk a lot!
In normal (extendable) bundles you always have ability to override every part of it just by replacing one parameter with your value (default class name to your class name). But the bundle that you want to extend is not intended to be extended. So to extend it properly and override some parts you will need to do many manipulations.
In your particular case to use your own query builder you need to override __construct() of Datatable.php in your class and replace in it:
//from
$this->_queryBuilder = new DoctrineBuilder($container);
//to
$this->_queryBuilder = new YourDoctrineBuilder($container);
Also to make application to use your Datatable class you need to replace default class to yours in your parameters.yml:
datatable.class: Your\Path\To\Datatable
It is not necessary to implement the same directory structure in your bundle to override some parts of bundle. It has no effect! The only thing that you need is to define your own classes and set up bundle to use yours instead of original.
getParent() for bundle only works for cases when you need to override some Resources. They need to have the same structure as in original bundle. But your case is not about Resources.
For this you cannot directly override the builder class.
You could, however, override the Datatable class and call your own version of the builder in the __construct like so..
namespace Acme\DatatableBundle\Util;
use Ali\DatatableBundle\Util\Datatable as BaseDatatable;
use Acme\DatatableBundle\Util\Factory\Query\DoctrineBuilder;
class Datatable extends BaseDatatable
{
/**
* class constructor
*
* #param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
// This would change the default for your version of the builder
$this->_queryBuilder = new DoctrineBuilder($container);
}
}
And then just set your version of the datatable class as the %datatable.class% parameter like..
datatable.class: Acme\DatatableBundle\Util\Datatable
Related
I have a repository class for photos:
use Imagine\Image\ImageInterface;
use Imagine\Image\ImagineInterface;
use Imagine\Image\BoxInterface;
class PhotoRepository extends ServiceEntityRepository
{
protected $imagineInterface;
protected $mode;
protected $box;
public function __construct(ImagineInterface $imagineInterface,
BoxInterface $box,
$mode = ImageInterface::THUMBNAIL_OUTBOUND)
{
$this->imagineInterface = $imagineInterface;
$this->$box = $box;
$this->mode = $mode;
}
I am getting the typical Cannot autowire service "App\Repository\PhotoRepository": argument "$box" of method "__construct()" references interface "Imagine\Image\BoxInterface" but no such service exists. Did you create a class that implements this interface?
The Imagine\ImageBox class clearly exists in my vendor folder and implements the BoxInterface, it starts out as follows:
namespace Imagine\Image;
use Imagine\Exception\InvalidArgumentException;
/**
* A box implementation
*/
final class Box implements BoxInterface
{
/**
* #var integer
*/
private $width;
Here is a picture of my folder structure, you can see that this Box class is there and that it implements BoxInterface:
I'm stuck because it says that the service doesn't exist but you can see that it does.
Any help is greatly appreciated.
To reply your question regarding working with interfaces, check this section of the docs: https://symfony.com/doc/current/service_container/autowiring.html#working-with-interfaces
However you're misunderstanding the purpose of services. Imagine's BoxInterface is by no means a service and shouldn't be declared as one. A service is needed only when you need only one instance of it through your whole application.
BoxInterface just describes a coordinates of a picture, therefore there will be as many instances as you need pictures instances.
Just create for example $box = new Imagine\Image\Box(50, 50); when you need a box.
Hi,
I have an entity A :
id
att1
att2
Now, I'd like to add a new Bundle (which will add new functionality) and here especially I'd like to add an attribute to A. A must be like :
id
att1
att2
newAtt3
newAtt4
In order to do that, I was thinking to create a new entity which will extend A and add the new attribute.
But then, what I don't know is how can I prepare the first bundle to use the 2nd entity (and controller/view) if the 2nd bundle is installed?
I guess I need to add configuration in the first bundle, but I have no idea what to add...
Thanks !
Please read Best Practices for Reusable Bundles
and Doctrine Inheritance Mapping
You can also think about Traits but often the best way is to use Interfaces. With the help of interfaces you can make your bundle highly configurable on a clean way like this:
make a configuration variabele that tells the bundle what class should be used. This can be the default class A from the Bundle but may also be class A from your AppBundle.
$rootNode
->children()
->scalarNode('a_entity')
->defaultValue('AppBundle\\Entity\\A')
->end()
->end()
;
Then make a interface for class A with all the functions that are mandatory:
interface AInterface
{
public function setVariable($name, $var);
public function getHtml($template);
}
and implement the interface in the class:
class A implements AInterface
{
// ...
}
Every time if you pass the class as a paramter use AInterface instead of A:
class B
{
private $a;
public function __construct(AInterface $a)
{
$this->a = $a;
}
}
Right now you can change the configuration variabele a_entity to another class. This other class still needs to implement the interface AInterface.
I need to register a custom MimeTypeGuesser so I can add in some logic to handle .docx files, which my web server's installation of PHP is treating as application/zip (which is technically correct). I want it recognized as application/msword or application/vnd.openxmlformats-officedocument.wordprocessingml.document.
In the filedoc comment for the class Symfony\Component\HttpFoundation\File\MimeType, there's some info on how to register a custom guesser:
* You can register custom guessers by calling the register() method on the
* singleton instance. Custom guessers are always called before any default ones.
*
* $guesser = MimeTypeGuesser::getInstance();
* $guesser->register(new MyCustomMimeTypeGuesser());
Great, but where is an appropriate place for this registration code?
I'd want this to be an app-wide change, but I can't think of a good place to put it.
To be honest, I myself can't say for sure which place would be appropriate to register custom guesser, so I will simply suggest you one.
Since you're aiming for a app-wide solution, I believe overriding the build method of your Bundle class would do the trick.
Let's assume your bundle is named AppBundle, then your Bundle configuration file should be AppBundle.php located in src/AppBundle/AppBundle.php
Normally that file should be nothing, but an empty class that extends Symfony's core Bundle class - Symfony\Component\HttpKernel\Bundle\Bundle.
From there you can override the build method that we inherit from Bundle\Bundle and register your guesser. Normally build() is used to register custom extension, such as payment gateways, or compiler passes and stuff like that. You can see that left in comments actually:
/**
* Builds the bundle.
*
* It is only ever called once when the cache is empty.
*
* This method can be overridden to register compilation passes,
* other extensions, ...
*
* #param ContainerBuilder $container A ContainerBuilder instance
*/
public function build(ContainerBuilder $container)
{
}
So, add the following statements at the top of your class:
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use Symfony\Component\DependencyInjection\ContainerBuilder;
And then simply override the method:
public function build(ContainerBuilder $container) {
parent::build($container);
$guesser = MimeTypeGuesser::getInstance();
$guesser->register( new MyMimeTypeGuesser() );
}
This would load your custom guesser once the application loads all of its bundles. I would like to say that this might not be the perfect solution, but for the time being it can help you.
You can do it in Bundle::boot:
use AppBundle\HttpFoundation\File\MimeType\MyCustomMimeTypeGuesser;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
/**
* {#inheritdoc}
*/
public function boot()
{
parent::boot();
MimeTypeGuesser::getInstance()->register(new MyCustomMimeTypeGuesser());
}
...
}
This will be called every time the Kernel boots.
If you do it in Bundle::build it would not work, because the call needs to happen at runtime.
I'm trying to dynamically set a parameter in Symfony2 (that I cannot statically set in my parameters.yml file). My approach is to use an EventListener:
namespace Acme\AcmeBundle\EventListener;
use Symfony\Component\DependencyInjection\Container;
class AcmeListener
{
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function onKernelRequest()
{
// Dynamically fetch $bar
$bar = fetch('foobar');
// Set parameter
$this->container->setParameter('foo', $bar);
}
}
And my service definition in config.yml looks like this:
service:
kernel.listener.acme_listener:
class: Acme\AcmeBundle\EventListener\AcmeListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
arguments: [ '#service_container' ]
The problem is, I get an exception:
LogicException: Impossible to call set() on a frozen ParameterBag.
How can I work around this exception or do you see another way to dynamically set a parameter?
The container parameters rule is that:
You can only set a parameter before the container is compiled
How to remedy the problem depends on your needs with the premise that the container is not thought to have dynamic parameters.
create you custom dynamic "options" service and inject it in other services, in this way you can also manage your parameters in database (like wordpress wp_options), but i don't know a bundle that do this. For existing services (ex. mailer) you can use configurators.
invalidate the cache when parameters changes here an easy method so when you reload the page the container is rebuilt. If the parameters change frequently risks to reload the cache frequently and this becomes a problem if you have large loads.
if you choose the second option you need to set the parameters before it is filled in the container, so you can:
export in a custom yaml file loaded in app/config/config.yml when parameters changes before you clear the cache, in this way you can get the data from other services
load parameters in a bundle extension here the cookbook, in this way you can't access to other services to get the data, the extension can access only to the containerbuilder
I suggest, however, option 1 (options service and configurators) because (I repeat) the container is not thought to have dynamic parameters but it offers the ability to have custom dynamic service configurators that use data from any source.
I had the problem on a list of URLs starting by %base_url% when I wanted to do a failover system.
I finally replaced %base_url% by #base_url# and did a manual placeholders resolution in a service.
$tries = array (
$this->container->getParameter('base_url'),
$this->container->getParameter('base_url_failover'),
);
foreach ($tries as $try)
{
$url = str_replace('#base_url#', $try, $unresolvedUrl);
// whatever
}
I think we can add parameters as simple class functions to a service.
This is my controller function.
/**
* #Route("/products/checkService")
* #Template()
*/
public function checkServiceAction() {
$paypal = $this->get('Paypal');
return new Response($paypal->chkVal('dsdd'));
}
This is my service
<?php
namespace Eagle\ShopBundle\Services;
use Symfony\Component\HttpFoundation\Response;
class Paypal {
public function __construct($var) {
$this->paypalCongig = $var;
}
public function getVal() {
return $this->paypalCongig;
}
public function chkVal($var) {
return $var;
}
}
So, I'm using the bundle class to do most of my work as I dont need controllers (src\CSFs\QuicklinksBundle\CSFsQuicklinksBundle.php).
From the FrontController of another bundle, I get the quicklinks bundle, inject the container object into the bundle class (above) and then, within the bundle class, extract templating to return HTML, this works fine. However, I'm having trouble with repositories.
/**
* Get the container object, so we can use all the symfony2 fun stuffs
*/
public function injectContainer($cont)
{
// Template
$this->tpl = $cont->get('templating');
// EM
$this->em = $cont->get('doctrine')->getEntityManager();
}
/**
*
**/
public function doStuff()
{
$products = $this->em->getRepository('QuicklinksBundle:Quicklinks')
->getUsersWithQuicklinks();
}
The error I get is:
Unknown Entity namespace alias 'QuicklinksBundle'.
I have both the generated entity file and a repository class with the getUsersWithQuicklinks() method defined.
How do I get the entity manager to know about my repositories?
Thanks,
Mike
Change:
$this->em->getRepository('QuicklinksBundle:Quicklinks')
To:
$this->em->getRepository('CSFsQuicklinksBundle:Quicklinks')
And I'm assuming you have an Entity named 'Quicklinks'