代码之家  ›  专栏  ›  技术社区  ›  Jimmix

如何用依赖注入封装系统

  •  -1
  • Jimmix  · 技术社区  · 6 年前

    其目的是创建一个 读者 这是一个在联盟中排名靠前的职业 飞行系统 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');
    

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

    两者的论据都是: 文件系统 地方的 不是可选的。从这些类创建对象时必须传递它们。 这两个类也没有任何用于这些参数的公共设置器。

    我的问题是,如何使用依赖注入编写封装文件系统和本地的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);
    

    但这不起作用,因为我需要将本地适配器传递给 文件系统构造函数,为了做到这一点,我需要传递给 首先是本地适配器路径,它完全反对整点 读者使用的便利性只是传递给dir的途径 所有的文件都在那里,读者做了所有需要做的事情 只使用一个方法readContents()提供这些文件的内容。

    所以我被卡住了。 是否可以将该读取器作为文件项及其本地适配器上的包装器?

    我想避免紧耦合,我使用关键字new,并以这种方式获取dependecies的对象 :

    <?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. 除了包装器(适配器)之外,还有其他模式可以帮助构建读卡器类,而不必与文件系统和本地系统紧密耦合吗?

    3. 暂时忘记了Reader类:如果文件系统在其构造函数中需要本地实例,在其构造函数中需要本地字符串(指向dir的路径),那么是否可以以合理的方式在依赖注入容器(Symfony或Pimple)中使用这些类? DIC不知道arg传递给本地适配器的路径是什么,因为该路径将在代码后面的某个地方进行计算。

    0 回复  |  直到 6 年前
        1
  •  2
  •   samrap    6 年前

    可以使用Factory模式生成 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');
    

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

        2
  •  1
  •   Maksym Fedorov    6 年前

    1.你可以使用 Filesystem Local 作为依赖项注入方式中的依赖项。你可以创造 Adapter 对象和 文件系统 对象,并将其传入 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. 读者 不是适配器模式,因为它没有实现任何来自系统的接口。它是用于封装某些逻辑以使用文件系统的类。您可以阅读有关适配器模式的更多信息 here 。您应该使用接口,避免在类中直接创建对象,以减少读取器和文件系统之间的耦合。

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

        3
  •  1
  •   domdambrogia    6 年前

    我希望我正确地理解了你的问题。事实上,我几周前刚刚经历过这件事。对我来说,这是一些有趣的事情。

    Reading through this laravel snippet 帮助我理解了接口和依赖注入是如何工作的。这篇文章讨论了合同与外观,以及为什么你可能希望使用其中一种而不是另一种。

    听起来你想用一个 Filesystem 实例,可以读取远程文件(S3等)或本地文件。由于文件系统只能是远程的或本地的(不是组合的),我认为正确的做法是使用一个接口以相同的方式与两者交互,然后允许用户/开发人员(通过依赖项注入首选项)在声明一个 文件系统 .

    // 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 在一个 有点 类似的时尚。对我来说,他们是医生有点粗糙,但在经过几天(以及联赛)的筛选之后,我终于站到了另一边,非常了解正在发生的事情。

    使用您的服务:

    假设应用程序中有依赖注入,并且希望连接到 文件系统 在你的阅读课上,你会包括 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);
        }
    }
    
    推荐文章