如果我添加一个第三方捆绑包,例如从 Knp 捆绑包中,我应该先包装它还是应该直接在我的代码中使用它?
如果我决定包装它,我应该把包装代码放在哪里?在单独的新捆绑包中?在我的应用程序捆绑包中?
澄清一下:
我不是在问如何将第三方捆绑包添加到我的项目中。我不是在问捆绑是什么。
此问题旨在将第三方代码封装在包装类后面。由于该捆绑包是由第三方开发人员开发的,因此可能会发生意外更改,这些更改可能会破坏我的代码。
将第三方捆绑包添加到您的项目后,如何包装它?
这是通过 Symfony2 中composer
包含的第三方捆绑包的答案,通常,它不是指特殊的捆绑包。
首先
只要您将所请求的捆绑包的版本修复为composer.json
中的稳定版本(如 1.*
),并且只要开发人员遵循他自己的指南),您应该不会有接口兼容性中断的任何问题,因此不需要包装。
但我假设您希望通过在包装器代码中抛出异常和/或实现回退来防止任何代码中断,以便使用包装器代码的所有内容仍然可以工作或至少显示适当的错误。
如果要包装
如果要使用给定第三方捆绑包的dev-master
版本,则可能会发生重大更改。但是,当有稳定版本时,不应该有您真的想要包含dev-master
的情况。
无论如何,我看到有两种方法,如果您想包含dev-master
或想要包装它以显示错误、记录错误、捕获异常等,这可能是有意义的:
构建使用第三方捆绑包的所有服务实例的单个服务类
此服务类可能位于使用第三方捆绑包的捆绑包之一中,此方法中不需要额外的捆绑包。
这样,您就有了一个像 acme.thirdparty.client
这样的服务,它包装了其他服务的单个方法调用。您需要注入所需的所有第三方服务(或创建所需子类的实例)并包装所有所需的方法调用。
# src/Acme/MyBundle/Resources/config/services.yml
parameters:
acme.thirdparty.wrapper.class: AcmeMyBundleServiceWrapperClass
services:
acme.thirdparty.wrapper:
class: %acme.thirdparty.wrapper.class%
arguments:
someService: @somevendor.somebundle.someservice
someOtherService: @somevendor.somebundle.someotherservice
和服务类:
<?php
namespace AcmeMyBundleService;
use SomeVendorSomeBundleSomeServiceConcreteService;
use SomeVendorSomeBundleSomeServiceOtherConcreteService;
class WrapperClass
{
private $someService;
private $someOtherService;
public function __construct(ConcreteService $someService, OtherConcreteService $someOtherService)
{
$this->someService = $someService;
$this->someOtherService = $someOtherService;
}
/**
* @see SomeVendorSomeBundleSomeServiceConcreteService::someMethod
*/
public function someMethod($foo, $bar = null)
{
// Do stuff
return $this->someService->someMethod();
}
/**
* @see SomeVendorSomeBundleSomeServiceConcreteOtherService::someOtherMethod
*/
public function someOtherMethod($baz)
{
// Do stuff
return $this->someOtherService->someOtherMethod();
}
}
然后,您可以向这些方法调用添加一些错误处理(例如捕获所有异常并记录它们等),从而防止服务类之外的任何代码中断。但不用说,这并不能阻止第三方捆绑包的任何意外行为。
或者您可以:
创建具有多个服务的捆绑包,每个服务包装第三方捆绑包的单个服务
整个捆绑包的优点是可以更灵活地包装您想要包装的内容。您可以包装整个服务或仅包装单个存储库,并将包装的类替换为您自己的类。DI 容器允许重写注入的类,如下所示:
# src/Acme/WrapperBundle/Resources/config/services.yml
parameters:
somevendor.somebundle.someservice.class: AcmeWrapperBundleServiceWrapperClass
通过覆盖类参数somevendor.somebundle.someservice.class
使用此类的所有服务现在都是 AcmeWrapperBundleServiceWrapperClass
的实例。此包装类可以扩展基类:
<?php
namespace AcmeWrapperBundleService;
use SomeVendorSomeBundleSomeServiceConcreteService;
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
}
}
。或者可以使用原始类的实例来包装它:
<?php
namespace AcmeWrapperBundleService;
use SomeVendorSomeBundleSomeServiceConcreteServiceInterface;
use SomeVendorSomeBundleSomeServiceConcreteService;
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
}
}
请注意,为重写另一个类的类实现接口可能是必需的。第二个可能不是最好的主意,因为那时还不清楚您实际上是在包装ConcreteService
而不仅仅是更换它。这也忽略了依赖注入的整个思想。
这种方法需要更多的工作,意味着更多的测试,但如果你想要更大的灵活性,这就是你要走的路。
也许已经有您想要的第三方捆绑包的包装器捆绑包(例如Buzz Browser
的包装SensioBuzzBundle
),在这种情况下,您可以方便地使用这些包装包,而不是自己编写所有内容。
结论
信任开发人员并包括一个稳定版本(例如1.*
用于错误修复和新功能或仅针对错误修复的1.0.*
)是要走的路。如果没有稳定版本,或者如果您想包含 dev-master
,包装是一种选择。如果要包装代码,构建额外的捆绑包是更灵活的方法,但是如果没有太多代码要包装,则单个服务类就足够了。
此捆绑包的文档可在 参考资料/文档中找到捆绑包的目录:
阅读此内容,它易于使用。
将您的捆绑包添加到/vendor/bundles将以下命名空间条目添加到自动加载器中的 registerNamespaces 调用中:应用程序/自动加载.php
或配置捆绑包app/config/config.yml