1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Finder\Iterator;
13
14/**
15 * SortableIterator applies a sort on a given Iterator.
16 *
17 * @author Fabien Potencier <fabien@symfony.com>
18 *
19 * @implements \IteratorAggregate<string, \SplFileInfo>
20 */
21class SortableIterator implements \IteratorAggregate
22{
23    public const SORT_BY_NONE = 0;
24    public const SORT_BY_NAME = 1;
25    public const SORT_BY_TYPE = 2;
26    public const SORT_BY_ACCESSED_TIME = 3;
27    public const SORT_BY_CHANGED_TIME = 4;
28    public const SORT_BY_MODIFIED_TIME = 5;
29    public const SORT_BY_NAME_NATURAL = 6;
30
31    private $iterator;
32    private $sort;
33
34    /**
35     * @param \Traversable<string, \SplFileInfo> $iterator
36     * @param int|callable                       $sort     The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
37     *
38     * @throws \InvalidArgumentException
39     */
40    public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
41    {
42        $this->iterator = $iterator;
43        $order = $reverseOrder ? -1 : 1;
44
45        if (self::SORT_BY_NAME === $sort) {
46            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
47                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
48            };
49        } elseif (self::SORT_BY_NAME_NATURAL === $sort) {
50            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
51                return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
52            };
53        } elseif (self::SORT_BY_TYPE === $sort) {
54            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
55                if ($a->isDir() && $b->isFile()) {
56                    return -$order;
57                } elseif ($a->isFile() && $b->isDir()) {
58                    return $order;
59                }
60
61                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
62            };
63        } elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
64            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
65                return $order * ($a->getATime() - $b->getATime());
66            };
67        } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
68            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
69                return $order * ($a->getCTime() - $b->getCTime());
70            };
71        } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
72            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
73                return $order * ($a->getMTime() - $b->getMTime());
74            };
75        } elseif (self::SORT_BY_NONE === $sort) {
76            $this->sort = $order;
77        } elseif (\is_callable($sort)) {
78            $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort;
79        } else {
80            throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
81        }
82    }
83
84    /**
85     * @return \Traversable<string, \SplFileInfo>
86     */
87    #[\ReturnTypeWillChange]
88    public function getIterator()
89    {
90        if (1 === $this->sort) {
91            return $this->iterator;
92        }
93
94        $array = iterator_to_array($this->iterator, true);
95
96        if (-1 === $this->sort) {
97            $array = array_reverse($array);
98        } else {
99            uasort($array, $this->sort);
100        }
101
102        return new \ArrayIterator($array);
103    }
104}
105