基于 SPL 迭代器 过滤掉目录中不需要的文件实现

1、在一个模板目录中,由于存在一些需要忽略的文件,也被写入至表中,因此,需要过滤掉。参考:在 Laravle 6、TOGoS/PHPGitIgnore 中,用于解析和应用 .gitignore 类规则的实现

2、现阶段的代码实现

    private \RecursiveIteratorIterator $iterator;

    private $themeLocation;

    public function __construct($themeLocation)
    {
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($themeLocation, \FilesystemIterator::SKIP_DOTS)
        );
        $this->iterator = $iterator;
        $this->themeLocation = $themeLocation;
    }

3、导致的结果就是例如:node_modules 开头的文件被写入至数据库中。如图1

图1

4、期望通过 The RecursiveFilterIterator class 实现,避免将忽略文件写入至数据库中。这个抽象迭代器为 RecursiveIterator 过滤掉不需要的值。应扩展此类以实现自定义过滤器。 RecursiveFilterIterator::accept() 必须在子类中实现。参考:https://www.php.net/manual/zh/class.recursivefilteriterator.php 。Recursive directory/file listing, filteres “.svn”。

5、新建 ThemeViewIgnoreFinder.php,以获取忽略文件数组。includeDirectories 设置为 true,以包含目录。

<?php

namespace Modules\ThemeStoreDB\Installer;

use TOGoS_GitIgnore_FileFinder;
use TOGoS_GitIgnore_Ruleset;

class ThemeViewIgnoreFinder
{
    const THEMEIGNORE = '.themeignore'; // 主题忽略配置文件

    public $ignoreResults;

    private $themeLocation;

    public function __construct($themeLocation)
    {
        $this->themeLocation = $themeLocation;
        $this->ignoreResults = [];
    }

    /**
     * 添加忽略文件
     * @param $f
     * @param $result
     * @return void
     */    public function addIgnoreResult($f, $result) {
        $this->ignoreResults[$f] = $result;
    }

    /**
     * 查找忽略文件
     * @return array
     */    public function findIgnoreFiles() {
        $rulesContent = file_get_contents($this->themeLocation . DIRECTORY_SEPARATOR . self::THEMEIGNORE);
        $finder = new TOGoS_GitIgnore_FileFinder([
            'ruleset' => TOGoS_GitIgnore_Ruleset::loadFromString($rulesContent),
            'invertRulesetResult' => false,
            'defaultResult' => false,
            'includeDirectories' => true,
            'callback' => [$this, 'addIgnoreResult']
        ]);

        $finder->findFiles($this->themeLocation);

        return $this->ignoreResults;
    }

}

6、新建 ThemeViewRecursiveFilterIterator.php,以实现自定义过滤器

<?php

namespace Modules\ThemeStoreDB\Installer;

class ThemeViewRecursiveFilterIterator extends \RecursiveFilterIterator
{
    protected $filters;

    public function __construct(\RecursiveIterator $iterator, $ignoreResults) {

        $this->filters = $ignoreResults;
        parent::__construct($iterator);
    }

    public function accept() {

        $accept = true;

        if (isset($this->filters[$this->current()->getFilename()])) {
            $accept = !$this->filters[$this->current()->getFilename()];
        }

        return $accept;
    }


}

7、编辑 SPL 迭代器 代码实现

    private \RecursiveIteratorIterator $iterator;

    private $themeLocation;

    public function __construct($themeLocation)
    {
        $dirIterator = new \RecursiveDirectoryIterator($themeLocation, \FilesystemIterator::SKIP_DOTS);
        $themeViewIgnoreFinder = new ThemeViewIgnoreFinder($themeLocation);
        $filterIterator = new ThemeViewRecursiveFilterIterator($dirIterator, $themeViewIgnoreFinder->findIgnoreFiles());
        $iterator = new \RecursiveIteratorIterator($filterIterator);

        $this->iterator = $iterator;
        $this->themeLocation = $themeLocation;
    }

8、运行时报错。如图2

图2

PS E:\wwwroot\object> php artisan theme-store:theme:install E:\wwwroot\object\resources\views\theme 12345 blade
安装主题到数据仓库

   Symfony\Component\Debug\Exception\FatalThrowableError  : Too few arguments to function Modules\ThemeStoreDB\Installer\ThemeViewRecursiveFilterIterator::__construct(), 1 passed and exactly 2 expected

  at E:\wwwroot\object\Modules\ThemeStoreDB\Installer\ThemeViewRecursiveFilterIterator.php:9
     5| class ThemeViewRecursiveFilterIterator extends \RecursiveFilterIterator
     6| {
     7|     protected $filters;
     8|
  >  9|     public function __construct(\RecursiveIterator $rit, $ignoreResults) {
    10|
    11|         $this->filters = $ignoreResults;
    12|         parent::__construct($rit);
    13|     }

  Exception trace:

  1   Modules\ThemeStoreDB\Installer\ThemeViewRecursiveFilterIterator::__construct(Object(RecursiveDirectoryIterator))
      [internal]:0

  2   RecursiveFilterIterator::getChildren()
      [internal]:0

  Please use the argument -v to see more details.

9、编辑 ThemeViewRecursiveFilterIterator.php,添加方法 getChildren()

<?php

namespace Modules\ThemeStoreDB\Installer;

class ThemeViewRecursiveFilterIterator extends \RecursiveFilterIterator
{
    protected $filters;

    public function __construct(\RecursiveIterator $iterator, $ignoreResults) {

        $this->filters = $ignoreResults;
        parent::__construct($iterator);
    }

    public function accept() {

        $accept = true;

        if (isset($this->filters[$this->current()->getFilename()])) {
            $accept = !$this->filters[$this->current()->getFilename()];
        }

        return $accept;
    }

    public function getChildren() {
        return new self($this->getInnerIterator()->getChildren(), $this->filters);
    }

}

10、再次运行,运行成功,但 node_modules\ 开头的文件仍被写入至数据库中。不符合预期。打印 $this->filters

array(766) {
  [""]=>
  bool(false)
  [".browserslistrc"]=>
  bool(true)
  [".env"]=>
  bool(true)
  [".env.development"]=>
  bool(true)
  [".env.production"]=>
  bool(true)
  [".themeignore"]=>
  bool(true)
  ["assets"]=>
  bool(false)
  ["assets/css"]=>
  bool(false)
  ["assets/css/app.css"]=>
  bool(true)
  ["assets/images"]=>
  bool(false)
}

11、打印 $this->current()->getPath() 、$this->current()->getFilename()

E:\wwwroot\object\resources\views\theme
.browserslistrc
E:\wwwroot\object\resources\views\theme
.env
E:\wwwroot\object\resources\views\theme
.env.development
E:\wwwroot\object\resources\views\theme
.env.production
E:\wwwroot\object\resources\views\theme
.themeignore
E:\wwwroot\object\resources\views\theme
assets
E:\wwwroot\object\resources\views\theme\assets
css
E:\wwwroot\object\resources\views\theme\assets\css
app.css
E:\wwwroot\object\resources\views\theme\assets
images

12、原因在于判断文件路径是否应被过滤掉的规则不正确。重新实现如下

    /**
     * 添加忽略文件
     * @param $f
     * @param $result
     * @return void
     */    public function addIgnoreResult($f, $result) {
        $this->ignoreResults[$this->themeLocation . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $f)] = $result;
    }
    public function accept() {
        return !$this->filters[$this->current()->getPath() . DIRECTORY_SEPARATOR . $this->current()->getFilename()];
    }

13、再次运行,运行成功,且 node_modules 开头的文件已未被写入至数据库中。符合预期。如图3

图3

永夜