Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
51.85% covered (warning)
51.85%
28 / 54
CRAP
69.97% covered (warning)
69.97%
254 / 363
FileFinder
0.00% covered (danger)
0.00%
0 / 1
51.85% covered (warning)
51.85%
28 / 54
1111.62
69.97% covered (warning)
69.97%
254 / 363
 __construct
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 onlyFiles
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 onlyDirs
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 ignoreDotFiles
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
13 / 13
 followLinks
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 7
 ignoreUnreadableDirs
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 maxDepth
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 minDepth
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 nameFilter
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
8 / 8
 notNameFilter
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
8 / 8
 containsFilter
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
8 / 8
 notContainsFilter
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
8 / 8
 pathFilter
0.00% covered (danger)
0.00%
0 / 1
5.05
87.50% covered (warning)
87.50%
7 / 8
 notPathFilter
0.00% covered (danger)
0.00%
0 / 1
5.05
87.50% covered (warning)
87.50%
7 / 8
 excludeDirs
0.00% covered (danger)
0.00%
0 / 1
5.47
73.33% covered (warning)
73.33%
11 / 15
 orderByAccessedTime
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 orderByChangedTime
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 orderByModifiedTime
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 orderByName
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 orderBySize
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 orderByType
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 sizeFilter
0.00% covered (danger)
0.00%
0 / 1
87.98
40.00% covered (danger)
40.00%
22 / 55
 dateFilter
0.00% covered (danger)
0.00%
0 / 1
105.68
42.31% covered (danger)
42.31%
22 / 52
 in
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
11 / 11
 getIterator
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 where
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 limit
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 skip
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 take
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 count
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
 sum
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 any
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 all
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 average
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 min
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 max
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 reverse
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 asEnumerable
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 sort
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
2 / 2
 sortDesc
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 2
 sortKey
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 2
 sortKeyDesc
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 2
 orderBy
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
2 / 2
 orderByDesc
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 2
 firstOrNull
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 aggregate
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 lastOrNull
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 normalizeDir
0.00% covered (danger)
0.00%
0 / 1
3.33
66.67% covered (warning)
66.67%
4 / 6
 appendDirIterator
0.00% covered (danger)
0.00%
0 / 1
5.03
88.89% covered (warning)
88.89%
16 / 18
 appendFileNameFilter
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
13 / 13
 appendPathFilter
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
17 / 17
 appendFileContainsFilter
0.00% covered (danger)
0.00%
0 / 1
9.02
94.12% covered (success)
94.12%
16 / 17
 appendModeFilter
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
8 / 8
 create
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
<?php
namespace Gek\FileFinder;
use Gek\Collections\ArrayList;
use Gek\Collections\Enumerable;
use Gek\Collections\IEnumerable;
use Gek\FileFinder\Iterators\GekRecursiveDirectoryIterator;
use Gek\FileFinder\Iterators\GekRecursiveIteratorIterator;
use Traversable;
/**
 * Class FileFinder
 *
 * FileFinder, dosyaları ve dizinleri bulmak için kurallar oluşturmanıza izin verir.
 *
 * @package Gek\FileFinder
 */
class FileFinder implements IEnumerable
{
    #region fields
    /**
     * İç kolleksion
     *
     * @var Enumerable|null
     */
    protected ?Enumerable $_enumerable = null;
    /**
     * Ana Iterator
     *
     * Reqursive Iteratorleri barındıran iteratör.
     *
     * @var \AppendIterator
     */
    protected \AppendIterator $_baseIterator;
    /**
     * Directort iteratorlerin instancelarını tutan array
     *
     * @var array|GekRecursiveDirectoryIterator[]
     */
    protected array $dirIterators = array();
    /**
     *  RecursiveIterator Iterator instancelarını tutan array
     *
     * Max Min depth ayarları için kullanılıyor.
     *
     * @var array|GekRecursiveIteratorIterator[]
     */
    protected array $iteratorIterators = array();
    /**
     * .(nokta) ile başlayan dosya ve klasörleri göz ardı et.
     *
     * @var bool
     */
    protected $ignoreDotFiles = false;
    /**
     * Dosya adı filtrelerini tutan array (eşleşen)
     *
     * Dosya adlarını filtrelemek için kullanılan regex stringleri
     *
     * @var array|string[]
     */
    protected $nameFilters = [];
    /**
     * Dosya adı filtrelerini tutan array (eşleşmeyen)
     *
     * Dosya adlarını filtrelemek için kullanılan regex stringleri
     *
     * @var array|string[]
     */
    protected $notNameFilters = [];
    /**
     * Dosya içeriği filtrelerini tutan array (eşleşen)
     *
     * Dosya içeriğini filtrelemek için kullanılan regex stringleri
     *
     * @var array|string[]
     */
    protected $containsFilters = [];
    /**
     * Dosya içeriği filtrelerini tutan array (eşleşmeyen)
     *
     * Dosya içeriğini filtrelemek için kullanılan regex stringleri
     *
     * @var array|string[]
     */
    protected $notContainsFilters = [];
    /**
     * Dosya yolu filtrelerini tutan array (eşleşen)
     *
     * Dosya yoluna göre filtrelemek için kullanılan regex stringleri
     *
     * @var array|string[]
     */
    private $pathFilters = [];
    /**
     * Dosya yolu filtrelerini tutan array (eşleşmeyen)
     *
     * Dosya yoluna göre filtrelemek için kullanılan regex stringleri
     *
     * @var array|string[]
     */
    private $notPathFilters = [];
    /**
     * Sembolik linklerin takip edilip edilmeyeceğini tutar.
     *
     * @var bool
     */
    protected bool $followLinks = false;
    /**
     * Okunamayan dosya / dizinlerin gözardı edilip edilmeyeceğini tutar.
     *
     * @var bool
     */
    protected bool $ignoreUnreadableDirs = false;
    /**
     * Max depth i tutar.
     *
     * @var int|null
     */
    protected ?int $maxDepth = null;
    /**
     * Min depth i tutar.
     *
     * @var int|null
     */
    protected ?int $minDepth = null;
    /**
     * Modu tutar.
     *
     * @see FindMode
     * @var int
     */
    protected $mode = 0;
    #endregion fields
    #region ctor
    /**
     * FileFinder constructor.
     * @param int|null $maxDepth Max depth
     * @throws \Exception
     */
    public function __construct(?int $maxDepth = null)
    {
        $this->_baseIterator = new \AppendIterator();
        $this->_enumerable = Enumerable::fromIterable($this->_baseIterator);
        $this->maxDepth = $maxDepth;
        $this->ignoreDotFiles(true);
    }
    #endregion ctor
    #region methods
    /**
     * Sadece dosyaları arar
     *
     * @return $this
     */
    public function onlyFiles()
    {
        $notSetFilter = $this->mode === 0;
        $this->mode = FindMode::ONLY_FILES;
        if ($notSetFilter) {
            $this->appendModeFilter();
        }
        return $this;
    }
    /**
     * Sadece dizinleri arar
     *
     * @return $this
     */
    public function onlyDirs()
    {
        $notSetFilter = $this->mode === 0;
        $this->mode = FindMode::ONLY_DIRECTORIES;
        if ($notSetFilter) {
            $this->appendModeFilter();
        }
        return $this;
    }
    /**
     * .(nokta) ile başlayan dosya/dizinleri gözardı et/etme
     *
     * @param bool $ignore Göz ardı et
     * @return $this
     */
    public function ignoreDotFiles(bool $ignore = true)
    {
        if ($this->ignoreDotFiles !== $ignore) {
            $this->ignoreDotFiles = $ignore;
            $dotRegex = '#(^|/)\..+(/|$)#';
            if ($this->ignoreDotFiles) {
                $notSetFilter = empty($this->pathFilters) && empty($this->notPathFilters);
                $this->notPathFilters[] = $dotRegex;
                if ($notSetFilter) {
                    $this->appendPathFilter();
                }
            } else {
                $tmp = new ArrayList($this->notPathFilters);
                if ($tmp->remove($dotRegex)) {
                    $this->notPathFilters = $tmp->toArray();
                }
                $tmp = null;
            }
        }
        return $this;
    }
    /**
     * Sembolik linkleri takip et
     *
     * @return $this
     */
    public function followLinks()
    {
        if (!$this->followLinks) {
            $this->followLinks = true;
            foreach ($this->dirIterators as $dirIterator) {
                $flags = $dirIterator->getFlags();
                $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
                $dirIterator->setFlags($flags);
            }
        }
        return $this;
    }
    /**
     * Okunamayan dosya/dizinleri gözardı et/etme
     *
     * Bu seçenek varsayılan olarak etkindir.
     *
     * @param bool $ignore gözardı et
     * @return $this
     */
    public function ignoreUnreadableDirs(bool $ignore = true)
    {
        if ($this->ignoreUnreadableDirs !== $ignore) {
            $this->ignoreUnreadableDirs = $ignore;
            foreach ($this->dirIterators as $dirIterator) {
                $dirIterator->setIgnoreUnreadableDirs($this->ignoreUnreadableDirs);
            }
        }
        return $this;
    }
    /**
     * Max derinliği ayarla (alt dizinler)
     *
     * @param int $maxDepth max depth
     * @return $this
     */
    public function maxDepth(int $maxDepth)
    {
        if ($this->maxDepth !== $maxDepth) {
            $this->maxDepth = $maxDepth;
            foreach ($this->iteratorIterators as $iterator) {
                $iterator->setMaxDepth($this->maxDepth);
            }
        }
        return $this;
    }
    /**
     * Min derinliği ayarla (alt dizinler)
     *
     * @param int $minDepth min depth
     * @return $this
     */
    public function minDepth(int $minDepth)
    {
        if ($this->minDepth !== $minDepth) {
            $this->minDepth = $minDepth;
        }
        return $this;
    }
    /**
     * Dosya adına göre eşleşenleri ara.
     *
     * @param string ...$names
     * @return $this
     */
    public function nameFilter(string ...$names)
    {
        $notSetFilter = empty($this->nameFilters) && empty($this->notNameFilters);
        foreach ($names as $name) {
            if (!RegexHelper::isRegex($name)) {
                $name = RegexHelper::globToRegex($name);
            }
            $this->nameFilters[] = $name;
        }
        if ($notSetFilter) {
            $this->appendFileNameFilter();
        }
        return $this;
    }
    /**
     * Dosya adına göre eşleşmeyenleri ara.
     *
     * @param string ...$names
     * @return $this
     */
    public function notNameFilter(string ...$names)
    {
        $notSetFilter = empty($this->nameFilters) && empty($this->notNameFilters);
        foreach ($names as $name) {
            if (!RegexHelper::isRegex($name)) {
                $name = RegexHelper::globToRegex($name);
            }
            $this->notNameFilters[] = $name;
        }
        if ($notSetFilter) {
            $this->appendFileNameFilter();
        }
        return $this;
    }
    /**
     * Dosya içeriğine göre eşleşenleri ara.
     *
     * @param string ...$contents
     * @return $this
     */
    public function containsFilter(string ...$contents)
    {
        $notSetFilter = empty($this->containsFilters) && empty($this->notContainsFilters);
        foreach ($contents as $content) {
            if (!RegexHelper::isRegex($content)) {
                $content = '/' . preg_quote($content, '/') . '/';
            }
            $this->containsFilters[] = $content;
        }
        if ($notSetFilter) {
            $this->appendFileContainsFilter();
        }
        return $this;
    }
    /**
     * Dosya içeriğine göre eşleşmeyenleri ara.
     *
     * @param string ...$contents
     * @return $this
     */
    public function notContainsFilter(string ...$contents)
    {
        $notSetFilter = empty($this->containsFilters) && empty($this->notContainsFilters);
        foreach ($contents as $content) {
            if (!RegexHelper::isRegex($content)) {
                $content = '/' . preg_quote($content, '/') . '/';
            }
            $this->notContainsFilters[] = $content;
        }
        if ($notSetFilter) {
            $this->appendFileContainsFilter();
        }
        return $this;
    }
    /**
     * Dosya yoluna göre eşleşenleri ara.
     *
     * @param string ...$paths
     * @return $this
     */
    public function pathFilter(string ...$paths)
    {
        $notSetFilter = count($this->pathFilters) == 0 && count($this->notPathFilters) == 0;
        foreach ($paths as $path) {
            if (!RegexHelper::isRegex($path)) {
                $path = '/' . preg_quote($path, '/') . '/';
            }
            $this->pathFilters[] = $path;
        }
        if ($notSetFilter) {
            $this->appendPathFilter();
        }
        return $this;
    }
    /**
     * Dosya yoluna göre eşleşmeyenleri ara.
     *
     * @param string ...$paths
     * @return $this
     */
    public function notPathFilter(string ...$paths)
    {
        $notSetFilter = count($this->pathFilters) == 0 && count($this->notPathFilters) == 0;
        foreach ($paths as $path) {
            if (!RegexHelper::isRegex($path)) {
                $path = '/' . preg_quote($path, '/') . '/';
            }
            $this->notPathFilters[] = $path;
        }
        if ($notSetFilter) {
            $this->appendPathFilter();
        }
        return $this;
    }
    /**
     * Verilen dizinleri hariç tut.
     *
     * Bağımsız değişken olarak iletilen dizinler, "in ()" yöntemiyle tanımlananlara göre olmalıdır. Örneğin:
     *      $fileFinder->in(__DIR__)->excludeDirs('ruby');
     *
     * @param string ...$dirs
     * @return $this
     */
    public function excludeDirs(string ...$dirs)
    {
        foreach ($dirs as $dir) {
            $this->where(function (FileInfo $item) use ($dir) {
                $path = $item->isDir() ? $item->getRelativePathname() : $item->getRelativePath();
                $path = str_replace('\\', '/', $path);
                if (RegexHelper::isRegex($dir)) {
                    return !preg_match($dir, $path);
                }
                $dir = str_replace('\\', '/', $dir);
                $dir = rtrim($dir, '/');
                if (false !== strpos($dir, '/')) {
                    $pattern = preg_quote($dir, '#');
                    $pattern = '#(?:^|/)(?:' . $pattern . ')(?:/|$)#';
                    return !preg_match($pattern, $path);
                } else {
                    $paths = explode("/", $path);
                    return !in_array($dir, $paths);
                }
            });
        }
        return $this;
    }
    /**
     * Dosyaları ve dizinleri son erişilen zamana göre sıralar.
     *
     * Bu, dosyaya en son erişildiği, okunduğu veya yazıldığı zamandır.
     * Eşleşen tüm dosyaların ve dizinlerin karşılaştırma için alınması gerektiğinden,
     * bu yavaş olabilir.
     *
     * @return $this
     */
    public function orderByAccessedTime()
    {
        $this->_enumerable = $this->_enumerable->orderBy(function (FileInfo $item) {
            return $item->getATime();
        });
        return $this;
    }
    /**
     * Dosyaları ve dizinleri son inode değiştirme zamanına göre sıralar.
     *
     * Bu, inode bilgilerinin en son değiştirildiği zamandır
     * (izinler, sahip, grup veya diğer meta veriler).
     * Windows'ta, inode mevcut olmadığından, değişen zaman aslında dosya oluşturma zamanıdır.
     * Eşleşen tüm dosyaların ve dizinlerin karşılaştırma için alınması gerektiğinden,
     * bu yavaş olabilir.
     *
     * @return $this
     */
    public function orderByChangedTime()
    {
        $this->_enumerable = $this->_enumerable->orderBy(function (FileInfo $item) {
            return $item->getCTime();
        });
        return $this;
    }
    /**
     * Dosyaları ve dizinleri son değiştirilme zamanına göre sıralar.
     *
     * Bu, dosyanın gerçek içeriğinin en son değiştirildiği zamandır.
     * Eşleşen tüm dosyaların ve dizinlerin karşılaştırma için alınması gerektiğinden, bu yavaş olabilir.
     *
     * @return $this
     */
    public function orderByModifiedTime()
    {
        $this->_enumerable = $this->_enumerable->orderBy(function (FileInfo $item) {
            return $item->getMTime();
        });
        return $this;
    }
    /**
     * Dosyaları ve dizinleri ada göre sıralar.
     *
     * Eşleşen tüm dosyaların ve dizinlerin karşılaştırma için alınması gerektiğinden, bu yavaş olabilir.
     *
     * @return $this
     */
    public function orderByName()
    {
        $this->_enumerable = $this->_enumerable->orderBy(function (FileInfo $item) {
            return $item->getRealPath() ?: $item->getPathname();
        });
        return $this;
    }
    /**
     * Dosyaları ve dizinleri boyuta göre sıralar.
     *
     * Eşleşen tüm dosyaların ve dizinlerin karşılaştırma için alınması gerektiğinden, bu yavaş olabilir.
     *
     * @return $this
     */
    public function orderBySize()
    {
        $this->_enumerable = $this->_enumerable->orderBy(function (FileInfo $item) {
            return $item->getSize();
        });
        return $this;
    }
    /**
     * Dosyaları ve dizinleri türe göre (dizinler, dosyalardan önce gelir ), sonra ada göre sıralar.
     *
     * Eşleşen tüm dosyaların ve dizinlerin karşılaştırma için alınması gerektiğinden, bu yavaş olabilir.
     *
     * @return $this
     */
    public function orderByType()
    {
        $this->_enumerable = $this->_enumerable->orderBy(function (FileInfo $item) {
            return $item->getType() . "-" . $item->getRealPath() ?: $item->getPathname();
        });
        return $this;
    }
    /**
     * Dosya boyutları için testler ekler.
     *
     *     $fileFinder->sizeFilter('> 10K');
     *     $fileFinder->sizeFilter('<= 1Ki');
     *     $fileFinder->sizeFilter(4);
     *     $fileFinder->sizeFilter('> 10K', '< 20K')
     *
     * @param string ...$sizes Bir boyut aralığı dizesi veya bir tam sayı veya boyut aralıkları dizisi
     * @return $this
     */
    public function sizeFilter(string ...$sizes)
    {
        foreach ($sizes as $size) {
            if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $size, $matches)) {
                throw new \InvalidArgumentException("size geçerli bir size girdisi değil. " . $size);
            }
            $target = $matches[2];
            if (!is_numeric($target)) {
                throw new \InvalidArgumentException('Geçersiz sayı :.' . $target);
            }
            if (isset($matches[3])) {
                // magnitude
                switch (strtolower($matches[3])) {
                    case 'k':
                        $target *= 1000;
                        break;
                    case 'ki':
                        $target *= 1024;
                        break;
                    case 'm':
                        $target *= 1000000;
                        break;
                    case 'mi':
                        $target *= 1024 * 1024;
                        break;
                    case 'g':
                        $target *= 1000000000;
                        break;
                    case 'gi':
                        $target *= 1024 * 1024 * 1024;
                        break;
                }
            }
            $operator = $matches[1] ?? '==';
            if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
                throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
            }
            switch ($operator) {
                case '>':
                    $this->where(function (FileInfo $item) use ($target) {
                        return $item->getSize() > $target;
                    });
                    break;
                case '>=':
                    $this->where(function (FileInfo $item) use ($target) {
                        return $item->getSize() >= $target;
                    });
                    break;
                case '<':
                    $this->where(function (FileInfo $item) use ($target) {
                        return $item->getSize() < $target;
                    });
                    break;
                case '<=':
                    $this->where(function (FileInfo $item) use ($target) {
                        return $item->getSize() <= $target;
                    });
                    break;
                case '!=':
                    $this->where(function (FileInfo $item) use ($target) {
                        return $item->getSize() != $target;
                    });
                    break;
                case '==':
                default:
                    $this->where(function (FileInfo $item) use ($target) {
                        return $item->getSize() == $target;
                    });
                    break;
            }
        }
        return $this;
    }
    /**
     * Dosya tarihleri ​​için testler ekler (son değiştirilme).
     *
     * Tarih, strtotime () tarafından ayrıştırılabilecek bir şey olmalıdır:
     *
     *     $fileFinder->dateFilter('since yesterday');
     *     $fileFinder->dateFilter('until 2 days ago');
     *     $fileFinder->dateFilter('> now - 2 hours');
     *     $fileFinder->dateFilter('>= 2020-10-15');
     *     $fileFinder->dateFilter('>= 2020-10-15', '<= 2020-05-27');
     *
     * @param string ...$dates Bir tarih aralığı dizesi veya bir tarih aralığı dizisi
     * @return $this
     */
    public function dateFilter(string ...$dates)
    {
        foreach ($dates as $date) {
            if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $date, $matches)) {
                throw new \InvalidArgumentException("date geçerli bir date girdisi değil. " . $date);
            }
            try {
                $dt = new \DateTime($matches[2]);
                $target = $dt->format('U');
            } catch (\Exception $e) {
                throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
            }
            $operator = $matches[1] ?? '==';
            if ('since' === $operator || 'after' === $operator) {
                $operator = '>';
            }
            if ('until' === $operator || 'before' === $operator) {
                $operator = '<';
            }
            if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
                throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
            }
            switch ($operator) {
                case '>':
                    $this->where(function (FileInfo $item) use ($target) {
                        if (!file_exists($item->getPathname())) {
                            return false;
                        }
                        return $item->getMTime() > $target;
                    });
                    break;
                case '>=':
                    $this->where(function (FileInfo $item) use ($target) {
                        if (!file_exists($item->getPathname())) {
                            return false;
                        }
                        return $item->getMTime() >= $target;
                    });
                    break;
                case '<':
                    $this->where(function (FileInfo $item) use ($target) {
                        if (!file_exists($item->getPathname())) {
                            return false;
                        }
                        return $item->getMTime() < $target;
                    });
                    break;
                case '<=':
                    $this->where(function (FileInfo $item) use ($target) {
                        if (!file_exists($item->getPathname())) {
                            return false;
                        }
                        return $item->getMTime() <= $target;
                    });
                    break;
                case '!=':
                    $this->where(function (FileInfo $item) use ($target) {
                        if (!file_exists($item->getPathname())) {
                            return false;
                        }
                        return $item->getMTime() != $target;
                    });
                    break;
                case '==':
                default:
                    $this->where(function (FileInfo $item) use ($target) {
                        if (!file_exists($item->getPathname())) {
                            return false;
                        }
                        return $item->getMTime() == $target;
                    });
                    break;
            }
        }
        return $this;
    }
    /**
     * Tanımlanmış kurallarla eşleşen dosyaları ve dizinleri arar.
     *
     * @param string ...$dirs Bir dizin yolu veya bir dizi dizin
     * @return $this
     */
    public function in(string ...$dirs)
    {
        foreach ($dirs as $dir) {
            if (is_dir($dir)) {
                $dir = $this->normalizeDir($dir);
                $this->appendDirIterator($dir);
            } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
                sort($glob);
                $glob = array_map([$this, 'normalizeDir'], $glob);
                foreach ($glob as $gDir) {
                    $this->appendDirIterator($gDir);
                }
            } else {
                throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir));
            }
        }
        return $this;
    }
    #endregion methods
    #region IteratorAggregate
    /**
     * Retrieve an external iterator
     * @link https://php.net/manual/en/iteratoraggregate.getiterator.php
     * @return Traversable An instance of an object implementing <b>Iterator</b> or
     * <b>Traversable</b>
     * @throws \Exception on failure.
     */
    public function getIterator()
    {
        return $this->_enumerable->getIterator();
    }
    #endregion IteratorAggregate
    #region linq
    /**
     * Yineleyiciyi anonim bir işlevle filtreler.
     *
     * Anonim işlev bir \ SplFileInfo alır ve dosyaları kaldırmak için false döndürmelidir.
     *
     * @param callable $fn  Anonim işlev
     * @return $this
     */
    public function where(callable $fn)
    {
        $this->_enumerable = $this->_enumerable->where($fn);
        return $this;
    }
    /**
     * Bir Yineleyicide sınırlı bir öğe alt kümesi üzerinde yinelemeye izin verir.
     * 
     * @param int $skip Atlanacak öğe sayısı
     * @param int $take Alınacak öğe sayısı
     * @return $this
     */
    public function limit(int $skip, int $take = -1)
    {
        $this->_enumerable = $this->_enumerable->limit($skip, $take);
        return $this;
    }
    /**
     * Verilen sayıda öğeyi atlar.
     * 
     * @param int $count Atlanacak öğe sayısı
     * @return $this
     */
    public function skip(int $count)
    {
        $this->_enumerable = $this->_enumerable->skip($count);
        return $this;
    }
    /**
     * Verilen sayıda öğeyi alır
     * 
     * @param int $count Alınacak öğe sayısı
     * @return $this
     */
    public function take(int $count)
    {
        $this->_enumerable = $this->_enumerable->take($count);
        return $this;
    }
    
    /**
     * Count elements of an object
     * @link https://php.net/manual/en/countable.count.php
     * @param callable|null $fn Filtreleme için anonim işlev
     * @return int The custom count as an integer.
     * </p>
     * <p>
     * The return value is cast to an integer.
     * @since 5.1.0
     */
    public function count(?callable $fn = null): int
    {
        return $fn == null ?
            iterator_count($this->_enumerable->getIterator()) :
            $this->_enumerable->count($fn);
    }
    /**
     * Verilen anaonim işlevin öğelerden döndürdüğü değerleri toplamını verir.
     *
     * @param callable|null $fn Öğeden değer alacak anonim işlev
     * @return int|float
     */
    public function sum(callable $fn)
    {
        return $this->_enumerable->sum($fn);
    }
    /**
     * Herhangi bir sonuç bulunup bulunmadığını kontrol eder.
     *
     * Eğer bir anonim işlev verilirse işlevin şartınna uyan herhangi bir sonuç bulunup bulunmadığını kontrol eder.
     *
     * @param callable|null $fn Anonim işlev
     * @return bool
     */
    public function any(?callable $fn = null): bool
    {
        return $this->_enumerable->any($fn);
    }
    /**
     * Bütün öğelerin, verilen anonim işlevin şartına uyup uymadığını kontrol eder.
     *
     * @param callable $fn Anonim işlev
     * @return bool
     */
    public function all(callable $fn): bool
    {
        return $this->_enumerable->all($fn);
    }
    /**
     * Verilen anaonim işlevin öğelerden döndürdüğü değerlerin ortalamasını verir.
     *
     * @param callable $fn
     * @return float
     */
    public function average(callable $fn): float
    {
        return $this->_enumerable->average($fn);
    }
    /**
     * Verilen anaonim işlevin öğelerden döndürdüğü değerlerden en küçüğünü verir.
     *
     * @param callable $fn
     * @return int|float|null
     */
    public function min(callable $fn)
    {
        return $this->_enumerable->min($fn);
    }
    /**
     * Verilen anaonim işlevin öğelerden döndürdüğü değerlerden en büyüğünü verir.
     *
     * @param callable $fn
     * @return int|float|null
     */
    public function max(callable $fn)
    {
        return $this->_enumerable->max($fn);
    }
    /**
     * Sıralamayı tersine çevirir.
     *
     * @return $this
     */
    public function reverse()
    {
        $this->_enumerable = $this->_enumerable->reverse();
        return $this;
    }
    /**
     * Enumerable nesnesi çevirir.
     *
     * @return Enumerable
     */
    public function asEnumerable(): Enumerable
    {
        return $this->_enumerable->asEnumerable();
    }
    /**
     * Öğeleri sıralar.
     *
     * @param callable|null $compareFn Karşılaştırma için Anonim Fonksion
     * @return $this
     */
    public function sort(?callable $compareFn = null)
    {
        $this->_enumerable = $this->_enumerable->sort($compareFn);
        return $this;
    }
    /**
     * Öğeleri tersine sıralar.
     *
     * @param callable|null $compareFn Karşılaştırma için Anonim Fonksion
     * @return $this
     */
    public function sortDesc(?callable $compareFn = null)
    {
        $this->_enumerable = $this->_enumerable->sortDesc($compareFn);
        return $this;
    }
    /**
     * öğeleri anahtara (key => ...) göre sıralar.
     *
     * @param callable|null $compareFn Karşılaştırma için Anonim Fonksion
     * @return $this
     */
    public function sortKey(?callable $compareFn = null)
    {
        $this->_enumerable = $this->_enumerable->sortKey($compareFn);
        return $this;
    }
    /**
     * öğeleri anahtara (key => ...) göre tersine sıralar.
     *
     * @param callable|null $compareFn Karşılaştırma için Anonim Fonksion
     * @return $this
     */
    public function sortKeyDesc(?callable $compareFn = null)
    {
        $this->_enumerable = $this->_enumerable->sortKeyDesc($compareFn);
        return $this;
    }
    /**
     * Öğeleri sıralar.
     *
     * @param callable|null $selectorFn Karşılaştırmaya girecek değeri veren anonim işlev.
     * @return $this
     */
    public function orderBy(?callable $selectorFn = null)
    {
        $this->_enumerable = $this->_enumerable->orderBy($selectorFn);
        return $this;
    }
    /**
     * Öğeleri tersine sıralar.
     *
     * @param callable|null $selectorFn Karşılaştırmaya girecek değeri veren anonim işlev.
     * @return $this
     */
    public function orderByDesc(?callable $selectorFn = null)
    {
        $this->_enumerable = $this->_enumerable->orderByDesc($selectorFn);
        return $this;
    }
    /**
     * İlk öğeyi verir.
     *
     * Eğer bir anonim işlev verilmişse işlevin şartına uyan ilk öğeyi verir.
     *
     * @param callable|null $fn Anonim işlev
     * @return FileInfo|null
     */
    public function firstOrNull(?callable $fn = null)
    {
        return $this->_enumerable->firstOrNull($fn);
    }
    /**
     * Bir dizi üzerine bir toplayıcı işlevi ($fn) uygular.
     *
     * Belirtilen çekirdek ($tart) değeri, ilk biriktirici değeri olarak kullanılır ve
     * belirtilen işlev($resultFn), sonuç değerini seçmek için kullanılır.
     *
     * @param callable $fn Toplayıcı işlev
     * @param mixed|null $start başlangıç değeri
     * @param callable|null $resultFn sonuç işlevi
     * @return mixed|null
     */
    public function aggregate(callable $fn, $start = null, ?callable $resultFn = null)
    {
        return $this->_enumerable->aggregate($fn, $start, $resultFn);
    }
    /**
     * Son öğeyi verir.
     *
     * Eğer bir anonim işlev verilmişse işlevin şartına uyan son öğeyi verir.
     *
     * @param callable|null $fn Anonim işlev
     * @return FileInfo|null
     */
    public function lastOrNull(?callable $fn = null)
    {
        return $this->reverse()->firstOrNull($fn);
    }
    #endregion linq
    #region utils
    /**
     * Sondaki eğik çizgileri kaldırarak verilen dizin adlarını normalleştirir.
     *
     */
    private function normalizeDir(string $dir): string
    {
        if ('/' === $dir) {
            return $dir;
        }
        $dir = rtrim($dir, '/' . \DIRECTORY_SEPARATOR);
        if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
            $dir .= '/';
        }
        return $dir;
    }
    /**
     *
     * Yeni bir dizin iteratorü ekler.
     *
     * @param $dir Dizin
     */
    private function appendDirIterator($dir)
    {
        $flags = \FilesystemIterator::SKIP_DOTS;
        if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
            $flags |= \FilesystemIterator::UNIX_PATHS;
        }
        if ($this->followLinks) {
            $flags |= \FilesystemIterator::FOLLOW_SYMLINKS;
        }
        $iterator = new GekRecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
        $this->dirIterators[] = $iterator;
        $iterator = new GekRecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
        if ($this->maxDepth !== null) {
            $iterator->setMaxDepth($this->maxDepth);
        }
        $that = $this;
        $iterator->setDephtFilterFn(function ($depht) use ($that) {
            if ($that->minDepth === null) {
                return true;
            }
            return $depht >= $that->minDepth;
        });
        $this->iteratorIterators[] = $iterator;
        $this->_baseIterator->append($iterator);
    }
    /**
     * Dosya adı filtre iteratorü ekler
     *
     */
    protected function appendFileNameFilter()
    {
        $that = $this;
        $this->where(function (FileInfo $item) use ($that) {
            $string = $item->getFilename();
            // should at least not match one rule to exclude
            foreach ($that->notNameFilters as $regex) {
                if (preg_match($regex, $string)) {
                    return false;
                }
            }
            // should at least match one rule
            if ($that->nameFilters) {
                foreach ($that->nameFilters as $regex) {
                    if (preg_match($regex, $string)) {
                        return true;
                    }
                }
                return false;
            }
            // If there is no match rules, the file is accepted
            return true;
        });
    }
    /**
     * Dosya yolu filtre iteratorü ekler
     *
     */
    protected function appendPathFilter()
    {
        $that = $this;
        $this->where(function (FileInfo $item) use ($that) {
            if (count($that->notPathFilters) == 0 && count($that->pathFilters) == 0) {
                return true;
            }
            $string = $item->getRelativePathname();
            if ('\\' === \DIRECTORY_SEPARATOR) {
                $string = str_replace('\\', '/', $string);
            }
            // should at least not match one rule to exclude
            foreach ($that->notPathFilters as $regex) {
                if (preg_match($regex, $string)) {
                    return false;
                }
            }
            // should at least match one rule
            if ($that->pathFilters) {
                foreach ($that->pathFilters as $regex) {
                    if (preg_match($regex, $string)) {
                        return true;
                    }
                }
                return false;
            }
            // If there is no match rules, the file is accepted
            return true;
        });
    }
    /**
     * Dosya içeriği filtre iteratorü ekler
     *
     */
    protected function appendFileContainsFilter()
    {
        $that = $this;
        $this->where(function (FileInfo $item) use ($that) {
            if ($item->isDir() || !$item->isReadable()) {
                return false;
            }
            $string = $item->getContents();
            if (!$string) {
                return false;
            }
            // should at least not match one rule to exclude
            foreach ($that->notContainsFilters as $regex) {
                if (preg_match($regex, $string)) {
                    return false;
                }
            }
            // should at least match one rule
            if ($that->containsFilters) {
                foreach ($that->containsFilters as $regex) {
                    if (preg_match($regex, $string)) {
                        return true;
                    }
                }
                return false;
            }
            // If there is no match rules, the file is accepted
            return true;
        });
    }
    /**
     * Mode filtre iteratorü ekler
     *
     */
    protected function appendModeFilter()
    {
        $that = $this;
        $this->where(function (FileInfo $item) use ($that) {
            if (FindMode::ONLY_DIRECTORIES === (FindMode::ONLY_DIRECTORIES & $that->mode) && $item->isFile()) {
                return false;
            } elseif (FindMode::ONLY_FILES === (FindMode::ONLY_FILES & $that->mode) && $item->isDir()) {
                return false;
            }
            return true;
        });
    }
    #endregion utils
    #region statics
    /**
     * Yeni bir FileFinder oluşturur.
     *
     * @param int|null $maxDepth max derinlik
     * @return FileFinder
     */
    public static function create(?int $maxDepth = null)
    {
        return new self($maxDepth);
    }
    #endregion statics
}