我正在尝试创建一个可以从代码中的任何位置调用的类。
它接受可以从构造函数(或设置器)配置的不同参数。
该类将在多个项目之间共享,因此我需要能够轻松配置一次并多次使用相同的配置(或不同/特定的配置)。
这是我的课程:
namespace Allsoftware\SymfonyBundle\Utils;
class GdImageConverter
{
public function __construct(
?int $width = null,
?int $height = null,
int|array|null $dpi = null,
int $quality = 100,
string $resizeMode = 'contain',
) {
$this->width = $width ? \max(1, $width) : null;
$this->height = $height ? \max(1, $height) : null;
$this->dpi = $dpi ? \is_int($dpi) ? [\max(1, $dpi), \max(1, $dpi)] : $dpi : null;
$this->quality = \max(-1, \min(100, $quality));
$this->resizeMode = $resizeMode;
}
}
大多数时候,一个应用程序的构造函数参数是相同的。
所以我想到使用一个与其自身相对应但已经配置好的
private static
变量。
所以我添加了
$default
变量:
namespace Allsoftware\SymfonyBundle\Utils;
class GdImageConverter
{
private static GdImageConverter $default;
public function __construct(
?int $width = null,
?int $height = null,
int|array|null $dpi = null,
int $quality = 100,
string $resizeMode = 'contain',
) {
// ...
}
public static function setDefault(self $default): void
{
self::$default = $default;
}
public static function getDefault(): self
{
return self::$default ?? self::$default = new self();
}
}
看起来像单例,但实际上并非如此。
要设置一次并使用
GdImageConverter::getDefault()
来获取它,我在 service.yaml
文件中写入了这些行:
services:
default.gd_image_converter:
class: Allsoftware\SymfonyBundle\Utils\GdImageConverter
arguments:
$width: 2000
$height: 2000
$dpi: 72
$quality: 80
$resizeMode: contain
Allsoftware\SymfonyBundle\Utils\GdImageConverter:
calls:
- setDefault: [ '@default.gd_image_converter' ]
拨打
GdImageConverter::getDefault()
时ATE,不对应default.gd_image_converter
服务。
$default = GdImageConverter::getDefault();
$imageConverter = new GdImageConverter(2000, 2000, 72, 80);
dump($default);
dump($imageConverter);
die();
并且在调试
self::$default
内部getDefault()
时,它是空的。
我做错了什么?
注意:当我将
calls
方法 setDefault
更改为不存在的方法 setDefaults
时,symfony 告诉我该方法未定义。
无效服务“Allsoftware\SymfonyBundle\Utils\GdImageConverter”:方法“setDefaults()”不存在。
谢谢!
决定发布一个新的且希望更加连贯的答案。
基本问题是
GdImageConverter::getDefault();
返回一个所有参数都为空的实例。 这是因为 Symfony 容器仅在请求(也称为注入)时创建服务。 setDefault 从未被调用,因此使用 new self() 。
有一个名为 MimeTypes 的 Symfony 类,它采用类似的模式,但它不会尝试自定义服务,所以这并不重要。
第二个问题是GdImageConverter服务的配置方式。 即使它确实正确设置了默认时刻,它基本上也会注入一个“空”版本。
要解决第二个问题,您需要使用当前服务调用 setDefault 并删除 default.gd_image_converter ,除非您需要它用于其他用途:
services:
App\Service\GdImageConverter:
class: App\Service\GdImageConverter
public: true
arguments:
$width: 2000
$height: 2000
$dpi: 72
$quality: 80
$resizeMode: contain
calls:
- setDefault: [ '@App\Service\GdImageConverter' ]
顺便说明一下,静态方法 setDefault 将被动态调用。 这有点不寻常,但它在 PHP 中是合法的,并且 Symfony 对其他类也这样做。
接下来我们需要确保服务始终被实例化。 这是一个罕见的要求,我认为没有默认的方法可以做到这一点。 但使用 Kernel::boot 可以工作:
# src/Kernel.php
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function boot()
{
parent::boot();
$this->container->get(GdImageConverter::class);
}
}
这可确保为命令和 Web 应用程序设置默认服务。
GdImageConverter::getDefault();
现在可以随时调用,并将返回初始化的服务。 请注意,必须将服务声明为公共才能让 Container::get 正常工作。
您可以停在这里,但总是创建一个服务,即使您通常可能不需要它,这有点烦人。 通过将容器本身注入到您的类中可以避免这样做。
这绝对违反了 Symfony 推荐的做法,如果读者觉得他们需要否决答案,甚至建议它,那么就做你需要做的事情。 然而,Laravel 框架在常规基础上使用这种方法(称为外观),并且这些应用程序以某种方式设法工作。
use Psr\Container\ContainerInterface;
class GdImageConverter
{
private static GdImageConverter $default;
private static ContainerInterface $container; // Add this
public static function setContainer(ContainerInterface $container)
{
self::$container = $container;
}
public static function getDefault(): self
{
//return self::$default ?? self::$default = new self();
return self::$default ?? self::$default = self::$container->get(GdImageConverter::class);
}
}
# Kernel.php
public function boot()
{
parent::boot();
GdImageConverter::setContainer($this->container);
}
现在我们又回到了惰性实例化。
虽然我不会提供详细信息,但您可以消除注入容器以及通过注入 GdImageConverterServiceLocater 公开服务的需要。