5

目的是创建一个Reader类,它是 League Flysystem 文档之上的包装器

Reader应该提供方便的方式来读取目录中的所有文件,无论文件的物理形式是什么(本地文件或存档中的文件)

由于 DI 方法,包装器不应在其内部创建依赖项实例,而是将这些依赖项作为参数放入构造函数或其他 setter 方法。

这是一个如何单独使用League Flysystem(没有提到的包装器)从磁盘读取常规文件的示例:

<?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');

正如您所看到的,首先您创建了一个本地适配器,它在其构造函数中需要路径,然后您在其构造函数中创建需要适配器实例的文件系统。

两者的参数:FilesystemLocal不是可选的。从这些类创建对象时必须传递它们。这两个类也没有这些参数的任何公共设置器。

我的问题是如何使用依赖注入来编写包装 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);

但这不起作用,因为我需要将本地适配器传递给 Filesystem 构造函数,为了做到这一点,我需要首先传递给本地适配器路径,这完全违背了读者使用便利性的全部要点,即只是将路径传递给 dir where所有文件都是,Reader 只需要一个方法 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.
         */
    }
}

问题:

  1. 有没有办法编写一个使用 Filesystem 和 Local 作为依赖注入方式的依赖项的包装器?

  2. 除了包装器(适配器)之外,还有其他模式可以帮助构建 Reader 类而不与文件系统和本地紧密耦合吗?

  3. 暂时忘记 Reader 类:如果 Filesystem 在其构造函数中需要 Local 实例,而 Local 在其构造函数中需要字符串(目录路径),那么是否可以在 Dependency Injection Container(Symfony 或 Pimple)中合理地使用这些类方法?DIC 不知道将什么路径 arg 传递给本地适配器,因为该路径将在稍后的代码中进行评估。

4

3 回答 3

2

Filesystem每当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');

您将创建文件系统的工作委托给工厂,同时仍然通过依赖注入来保持组合的目标。

于 2019-03-22T00:02:57.490 回答
1

1.您可以在依赖注入方式中使用Filesystem和作为依赖项。Local您可以使用默认路径创建Adapter对象和对象并将它们传递给. 在方法中,您可以使用帮助方法修改路径。例如:FilesystemReaderreadContentssetPathPrefix()

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的任何接口。它是用于封装某些逻辑以使用文件系统的类。您可以在此处阅读有关适配器模式的更多信息。您应该使用接口并避免在类中直接创建对象,以减少 Reader 和 Filesystem 之间的耦合。

3.是的,您可以在DIC中设置适配器的默认路径...

于 2019-03-18T08:41:46.430 回答
1

我希望我能正确理解你的问题。实际上,我几周前才经历过这个。对我来说,这是一些有趣和有趣的东西。

阅读这个 laravel 片段帮助我理解了接口和依赖注入是如何工作的。这篇文章讨论了契约与外观,以及为什么你可能想要使用其中一种。

听起来您希望能够使用一个Filesystem可以读取远程文件(S3 等)或本地文件的实例。由于文件系统只能是远程的或本地的(不是组合),我认为正确的做法是使用接口以相同的方式与两者进行交互,然后允许用户/开发人员选择(通过依赖注入首选项)哪个文件当他们声明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通过编译di.xml文件并通过声明对另一个类的偏好来读取要替换的类来做到这一点。

Symfony以一种类似的方式做到这一点。他们的文档对我来说有点难以理解,但是经过几天的浏览(以及联盟),我终于对正在发生的事情有了很好的了解。

使用您的服务:

假设您的应用程序中有依赖注入工作,并且您想将您的阅读器类连接到您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);
    }
}
于 2019-03-21T23:40:11.007 回答