目标是创建一个阅读器类,该类是联赛 Flysystem documentation
的包装阅读器应提供便捷的方式来读取目录中的所有文件,无论文件是物理形式(本地文件还是存档中的文件)
由于使用DI方法,包装器不应在其内部创建依赖项实例,而应将这些依赖项作为构造函数或其他setter方法的参数。
以下是如何独立使用联赛飞行系统的示例(没有提到的包装器) 从磁盘读取常规文件:
<?php
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
$adapter = new Local(__DIR__.'/path/to/root');
$filesystem = new Filesystem($adapter);
$content = $filesystem->read('path-to-file.txt');
如您所见,首先创建一个 adapter Local ,该适配器的构造函数中需要路径 然后创建文件系统,该文件系统在其构造函数中需要适配器实例。
两者的自变量不是可选的:文件系统和本地。从这些类创建对象时必须传递它们。 这两个类都没有针对这些参数的公共设置方法。
我的问题是如何通过使用依赖注入来编写包装Filesytem和Local的Reader类?
我通常会做类似的事情:
<?php
use League\Flysystem\FilesystemInterface;
use League\Flysystem\AdapterInterface;
class Reader
{
private $filesystem;
private $adapter
public function __construct(FilesystemInterface $filesystem,
AdapterInterface $adapter)
{
$this->filesystem = $filesystem;
$this->adapter = $adapter;
}
public function readContents(string $pathToDirWithFiles)
{
/**
* uses $this->filesystem and $this->adapter
*
* finds all files in the dir tree
* reads all files
* and returns their content combined
*/
}
}
// and class Reader usage
$reader = new Reader(new Filesytem, new Local);
$pathToDir = 'someDir/';
$contentsOfAllFiles = $reader->readContents($pathToDir);
//somwhere later in the code using the same reader object
$contentsOfAllFiles = $reader->readContents($differentPathToDir);
但是这不起作用,因为我需要将本地适配器传递给 文件系统构造函数,为了做到这一点,我需要传递给 首先是本地适配器路径,它完全针对整个点 读者的使用便利性的传递,它只是将路径传递到目录 所有文件都在哪里,阅读器完成所有需要做的事情 只需一种方法readContents()即可提供这些文件的内容。
所以我被困住了。 是否可以使Reader作为Filestem及其本地适配器上的包装器?
我想避免在使用关键字new并以此方式获取依赖对象的地方紧密耦合:
<?php
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
class Reader
{
public function __construct()
{
}
public function readContents(string $pathToDirWithFiles)
{
$adapter = new Local($pathToDirWithFiles);
$filesystem = new Filesystem($adapter);
/**
* do all dir listing..., content reading
* and returning results.
*/
}
}
问题:
有什么方法可以以依赖注入的方式编写将Filesystem和Local用作依赖项的包装器?
除了包装器(适配器)以外,还有没有其他模式可以帮助构建Reader类而不紧密耦合到Filesystem和Local?
暂时忘了一会儿Reader类:如果Filesystem在其构造函数中需要Local实例,而Local在其构造函数中需要String(指向dir的路径),那么可以在依赖注入容器中使用这些类( Symfony或Pimple)以合理的方式? DIC不知道将什么路径arg传递给本地适配器,因为该路径将在代码的稍后部分进行评估。
答案 0 :(得分:1)
1。您可以将Filesystem
和Local
用作依赖关系注入方式的依赖关系。您可以使用默认路径创建Adapter
对象和Filesystem
对象,并将它们传递到Reader
中。在readContents
方法中,可以使用帮助setPathPrefix()
方法修改路径。例如:
class Reader
{
private $filesystem;
private $adapter;
public function __construct(FilesystemInterface $filesystem,
AdapterInterface $adapter)
{
$this->filesystem = $filesystem;
$this->adapter = $adapter;
}
public function readContents(string $pathToDirWithFiles)
{
$this->adapter->setPathPrefix($pathToDirWithFiles);
// some code
}
}
// usage
$adapter = new Local(__DIR__.'/path/to/root');
$filesystem = new Filesystem($adapter);
$reader = new Reader($filesystem, $adapter);
2。Reader
不是适配器模式,因为它没有实现League Flysystem的任何接口。这是用于封装某些逻辑以与文件系统一起使用的类。您可以阅读有关适配器模式here的更多信息。您应该使用接口,并避免在类中直接创建对象,以减少Reader和Filesystem之间的耦合。
3。是的,您可以在DIC中设置适配器的默认路径...
答案 1 :(得分:1)
希望我能正确理解您的问题。实际上,我几周前刚刚经历了这一过程。对我来说,这是一些有趣的东西。
Reading through this laravel snippet帮助我理解了接口和依赖注入的工作原理。本文讨论了合同与外观,以及为什么要在另一个之上使用。
听起来您希望能够使用一个可以读取远程文件(S3等)或本地文件的Filesystem
实例。由于文件系统只能是远程或本地(而不是组合),所以我认为正确的方法是使用接口以相同的方式进行交互,然后允许用户/开发人员(通过依赖项注入首选项)选择哪个文件声明Filesystem
的实例时,应使用系统(本地或远程)。
// Classes used
use League\Container\Container;
use League\Container\ReflectionContainer;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemInterface;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
// Create your container
$container = new Container;
/**
* Use a reflection container so devs don't have to add in every
* dependency and can autoload them. (Kinda out of scope of the question,
* but still helpful IMO)
*/
$container->delegate((new ReflectionContainer)->cacheResolutions());
/**
* Create available filesystems and adapters
*/
// Local
$localAdapter = new Local($cacheDir);
$localFilesystem = new Filesystem($localAdapter);
// Remote
$client = new S3Client($args);
$s3Adapter = new AwsS3Adapter($client, 'bucket-name');
$remoteFilesystem = new Filesystem($s3Adapter);
/**
* This next part is up to you, and many frameworks do this
* in many different ways, but it almost always comes down
* to declaring a preference for a certain class, or better
* yet, an interface. This example is overly simple.
*
* Set the class in the container to have an instance of either
* the remote or local filesystem.
*/
$container->add(
FileSystemInterface::class,
$userWantsRemoteFilesystem ? $remoteFilesystem : $localFilesystem
);
Magento 2 does this,方法是编译di.xml
文件,并通过声明优先选择其他类来读取要替换的类。
Symfony does this以类似的方式 。对我来说,他们的文档有些难懂,但是经过几天的考察(以及与联盟一起),我终于对另一边有了一个很好的了解。
假设您的应用程序中有依赖项注入,并且您想将Filesystem
与您的读者类挂钩,则将FilesystemInterface
作为构造函数依赖项包括在内,并在注入它时将使用您通过$container->add($class, $service)
use League\Flysystem\FilesystemInterface;
class Reader
{
protected $filesystem;
public function __construct(FilesystemInterface $filesystem)
{
$this->filesystem = $filesystem;
}
public function getFromLocation($location)
{
/**
* We know this will work, because any instance that implements the
* FilesystemInterface will have this read method.
* @see https://github.com/thephpleague/flysystem/blob/dab4e7624efa543a943be978008f439c333f2249/src/FilesystemInterface.php#L27
*
* So it doesn't matter if it is \League\Flysystem\Filesystem or
* a custom one someone else made, this will always work and
* will be served from whatever was declared in your container.
*/
return $this->filesystem->read($location);
}
}
答案 2 :(得分:0)
无论何时调用Filesystem
方法,您都可以使用Factory Pattern动态生成readContents
:
<?php
use League\Flysystem\FilesystemInterface;
use League\Flysystem\AdapterInterface;
class Reader
{
private $factory;
public function __construct(LocalFilesystemFactory $factory)
{
$this->filesystem = $factory;
}
public function readContents(string $pathToDirWithFiles)
{
$filesystem = $this->factory->createWithPath($pathToDirWithFiles);
/**
* uses local $filesystem
*
* finds all files in the dir tree
* reads all files
* and returns their content combined
*/
}
}
然后,您的工厂负责创建正确配置的文件系统对象:
<?php
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local as LocalAdapter;
class LocalFilesystemFactory {
public function createWithPath(string $path) : Filesystem
{
return new Filesystem(new LocalAdapter($path));
}
}
最后,当您构建Reader
时,它看起来像这样:
<?php
$reader = new Reader(new LocalFilesystemFactory);
$fooContents = $reader->readContents('/foo');
$barContents = $reader->readContents('/bar');
您将创建文件系统的工作委托给工厂,同时仍通过依赖项注入来维持编写目标。