How should 3rd party bundles be wrapped in Symfony2? How should 3rd party bundles be wrapped in Symfony2? symfony symfony

How should 3rd party bundles be wrapped in Symfony2?


This is an answer for 3rd party bundles included via composer in Symfony2 in general, it does not refer to a special bundle.

First of all

As long as you are fixing the version of the requested bundle to a stable version (like 1.*) in your composer.json (and as long as the developer follows his own guide lines), you shouldn't have any problems with compatibility breaks of the interfaces, thus wrapping is not necessary.

But I'm assuming that you want to prevent any code breaks by throwing Exceptions in the wrapper code and/or implementing fallbacks, so that everything that uses the wrapper code could still work or at least display appropriate errors.

If you want to wrap

If you want to use the dev-master version of a given 3rd party bundle, major changes might occur. But there shouldn't be a case where you really want to include the dev-master when there are stable versions.

Anyway, there are two ways that I see, that could make sense in the case that you want to include the dev-master or want to wrap it to display errors, log them, catch exceptions etc.:

Build a single service class that uses all instances of the services of the 3rd party bundle

This service class could be in one of your bundles that uses the 3rd party bundle, there's no need for an extra bundle in this approach.

This way you have a single service like acme.thirdparty.client that wraps single method calls of other services. You would need to inject all 3rd party services that you need (or create instances of the desired sub classes) and wrap all desired method calls.

# src/Acme/MyBundle/Resources/config/services.ymlparameters:    acme.thirdparty.wrapper.class: Acme\MyBundle\Service\WrapperClassservices:    acme.thirdparty.wrapper:        class: %acme.thirdparty.wrapper.class%        arguments:            someService: @somevendor.somebundle.someservice            someOtherService: @somevendor.somebundle.someotherservice

And the service class:

<?phpnamespace Acme\MyBundle\Service;use SomeVendor\SomeBundle\SomeService\ConcreteService;use SomeVendor\SomeBundle\SomeService\OtherConcreteService;class WrapperClass{    private $someService;    private $someOtherService;    public function __construct(ConcreteService $someService, OtherConcreteService $someOtherService)    {        $this->someService = $someService;        $this->someOtherService = $someOtherService;    }    /**     * @see SomeVendor\SomeBundle\SomeService\ConcreteService::someMethod     */    public function someMethod($foo, $bar = null)    {        // Do stuff        return $this->someService->someMethod();    }    /**     * @see SomeVendor\SomeBundle\SomeService\ConcreteOtherService::someOtherMethod     */    public function someOtherMethod($baz)    {        // Do stuff        return $this->someOtherService->someOtherMethod();    }}

You could then add some error handling to those method calls (like catching all exceptions and log them etc.) and thus prevent any code outside of the service class to break. But needless to say, this does not prevent any unexpected behaviour of the 3rd party bundle.

or you could:

Create a bundle that has multiple services, each wrapping a single service of the 3rd party bundle

A whole bundle has the advantage of being more flexible on what you exactly want to wrap. You could wrap a whole service or just single repositories and replace the wrapped classes with your own ones. The DI container allows the overriding of injected classes, like the following:

# src/Acme/WrapperBundle/Resources/config/services.ymlparameters:    somevendor.somebundle.someservice.class: Acme\WrapperBundle\Service\WrapperClass

By overriding the class parameter somevendor.somebundle.someservice.class all services that use this class are now instances of Acme\WrapperBundle\Service\WrapperClass. This wrapper class could be either extending the base class:

<?phpnamespace Acme\WrapperBundle\Service;use SomeVendor\SomeBundle\SomeService\ConcreteService;class WrapperClass extends ConcreteService{     /**      * @see ConcreteService::someMethod      */     public function someMethod($foo, $bar = null)     {         // Do stuff here         parent::someMethod($foo, $bar);         // And some more stuff here     }}

... or could use an instance of the original class to wrap it:

<?phpnamespace Acme\WrapperBundle\Service;use SomeVendor\SomeBundle\SomeService\ConcreteServiceInterface;use SomeVendor\SomeBundle\SomeService\ConcreteService;class WrapperClass implements ConcreteServiceInterface{    private $someService;    /**     * Note that this class should have the same constructor as the service.      * This could be achieved by implementing an interface     */    public function __construct($foo, $bar)    {        $this->someService = new ConcreteService($foo, $bar);    }     /**      * @see ConcreteService::someMethod      */     public function someMethod($foo, $bar = null)     {         // Do stuff here         $this->someService->someMethod($foo, $bar);         // And some more stuff here     }}

Note that implementing an interface for a class that is overriding another one might be mandatory. Also the second one might not be the best idea, since then it's not very clear that you are actually wrapping the ConcreteService and not just replacing it. Also this is ignoring the whole idea of Dependency Injection.

This approach needs a lot more work and means a lot more testing, but if you want more flexibility, this is the way to go.

Perhaps there already are wrapper bundles for your desired 3rd party bundles around (like the SensioBuzzBundle for the Buzz Browser), in this case, you could porbably use those instead of writing everything yourself.

Conclusion

Trusting the developer and including a stable version (like 1.* for bugfixes and new features or 1.0.* for bugfixes only) is the way to go. If there are no stable versions or if you want to include the dev-master, wrapping is an option. If you want to wrap your code, building an extra bundle is the more flexible way, but a single service class could be enough if there's not much code to wrap.


The documentation for this bundle is available in the Resources/docdirectory of the bundle:

Read this it is easy to use.

add your bundle into /vendor/bundlesAdd the following namespace entries to the registerNamespaces call in your autoloader:// app/autoload.php

or Configure the bundle inapp/config/config.yml