Как обернуть систему Flysystem с помощью инъекции зависимостей - PullRequest
4 голосов
/ 14 марта 2019

Цель состоит в том, чтобы создать класс Reader , который является оберткой поверх Лиги Flysystem документация

Reader должен обеспечивать удобный способ чтения всех файлов в каталоге независимо от физической формы файла (локальный файл или файл в архиве)

Из-за метода DI оболочка не должна создавать экземпляры зависимостей внутри него, а должна принимать эти зависимости в качестве аргументов в конструкторе или другом методе установки.

Вот пример , как использовать 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');

Как видите, вы сначала создаете адаптер Local , для которого требуется путь в его конструкторе. затем вы создаете файловую систему , которая требует экземпляра адаптера в своем конструкторе.

аргументы для обоих: Файловая система и Локальные не являются обязательными. Они должны быть переданы при создании объектов из этих классов. оба класса также не имеют открытых сеттеров для этих аргументов.

Мой вопрос заключается в том, как написать класс Reader, который оборачивает Filesytem и Local, используя затем Dependency Injection?

Обычно я делал бы что-то похожее на это:

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

Но это не будет работать, потому что мне нужно передать локальный адаптер в Конструктор файловой системы и для этого мне нужно перейти к Путь к локальному адаптеру, во-первых, полностью против целой точки удобство чтения читателя, который просто проходит путь к каталогу где все файлы и 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. Есть ли способ написать оболочку, которая использует файловую систему и локальные в качестве зависимостей в режиме внедрения зависимостей?

  2. Существует ли какой-либо другой шаблон, кроме оболочки (адаптера), который помог бы создать класс Reader без тесной связи с файловой системой и локальной системой?

  3. Забывает на некоторое время о классе Reader вообще: если Файловая система требует экземпляра Local в своем конструкторе, а Local требует строку (путь к dir) в своем конструкторе, то возможно ли использовать эти классы внутри контейнера внедрения зависимости ( Symfony или Pimple) в разумных пределах? DIC не знает, какой путь arg проходит к локальному адаптеру, поскольку путь будет определен где-то позже в коде.

Ответы [ 3 ]

1 голос
/ 22 марта 2019

Надеюсь, я правильно понял ваш вопрос.Я только что прошел через это несколько недель назад.Для меня это забавный и интересный материал.

Чтение этого фрагмента кода 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);
    }
}
1 голос
/ 18 марта 2019

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.Это класс для инкапсуляции некоторой логики для работы с файловой системой.Вы можете прочитать больше о шаблоне адаптера здесь .Вы должны работать с интерфейсами и избегать прямого создания объектов в вашем классе, чтобы уменьшить связь между Reader и файловой системой.

3.Да, вы можете установить путь по умолчанию для адаптера в DIC ...

0 голосов
/ 22 марта 2019

Вы можете использовать Factory Pattern для генерации 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');

Вы делегируете работу по созданию файловой системы на фабрику, сохраняя при этомцель композиции через внедрение зависимостей.

...