Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • ArrayHash
  • ArrayList
  • Arrays
  • Callback
  • DateTime
  • FileSystem
  • Finder
  • Html
  • Image
  • Json
  • ObjectHelpers
  • ObjectMixin
  • Paginator
  • Random
  • Reflection
  • SafeStream
  • Strings
  • TokenIterator
  • Tokenizer
  • Validators

Interfaces

  • IHtmlString

Exceptions

  • AssertionException
  • ImageException
  • JsonException
  • RegexpException
  • TokenizerException
  • UnknownImageFileException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Utils;
  9: 
 10: use Nette;
 11: use RecursiveDirectoryIterator;
 12: use RecursiveIteratorIterator;
 13: 
 14: 
 15: /**
 16:  * Finder allows searching through directory trees using iterator.
 17:  *
 18:  * <code>
 19:  * Finder::findFiles('*.php')
 20:  *     ->size('> 10kB')
 21:  *     ->from('.')
 22:  *     ->exclude('temp');
 23:  * </code>
 24:  */
 25: class Finder implements \IteratorAggregate, \Countable
 26: {
 27:     use Nette\SmartObject;
 28: 
 29:     /** @var array */
 30:     private $paths = [];
 31: 
 32:     /** @var array of filters */
 33:     private $groups = [];
 34: 
 35:     /** @var array filter for recursive traversing */
 36:     private $exclude = [];
 37: 
 38:     /** @var int */
 39:     private $order = RecursiveIteratorIterator::SELF_FIRST;
 40: 
 41:     /** @var int */
 42:     private $maxDepth = -1;
 43: 
 44:     /** @var array */
 45:     private $cursor;
 46: 
 47: 
 48:     /**
 49:      * Begins search for files matching mask and all directories.
 50:      * @param  mixed
 51:      * @return static
 52:      */
 53:     public static function find(...$masks)
 54:     {
 55:         $masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
 56:         return (new static)->select($masks, 'isDir')->select($masks, 'isFile');
 57:     }
 58: 
 59: 
 60:     /**
 61:      * Begins search for files matching mask.
 62:      * @param  mixed
 63:      * @return static
 64:      */
 65:     public static function findFiles(...$masks)
 66:     {
 67:         $masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
 68:         return (new static)->select($masks, 'isFile');
 69:     }
 70: 
 71: 
 72:     /**
 73:      * Begins search for directories matching mask.
 74:      * @param  mixed
 75:      * @return static
 76:      */
 77:     public static function findDirectories(...$masks)
 78:     {
 79:         $masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
 80:         return (new static)->select($masks, 'isDir');
 81:     }
 82: 
 83: 
 84:     /**
 85:      * Creates filtering group by mask & type selector.
 86:      * @param  array
 87:      * @param  string
 88:      * @return static
 89:      */
 90:     private function select($masks, $type)
 91:     {
 92:         $this->cursor = &$this->groups[];
 93:         $pattern = self::buildPattern($masks);
 94:         if ($type || $pattern) {
 95:             $this->filter(function (RecursiveDirectoryIterator $file) use ($type, $pattern) {
 96:                 return !$file->isDot()
 97:                     && (!$type || $file->$type())
 98:                     && (!$pattern || preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/')));
 99:             });
100:         }
101:         return $this;
102:     }
103: 
104: 
105:     /**
106:      * Searchs in the given folder(s).
107:      * @param  string|array
108:      * @return static
109:      */
110:     public function in(...$paths)
111:     {
112:         $this->maxDepth = 0;
113:         return $this->from(...$paths);
114:     }
115: 
116: 
117:     /**
118:      * Searchs recursively from the given folder(s).
119:      * @param  string|array
120:      * @return static
121:      */
122:     public function from(...$paths)
123:     {
124:         if ($this->paths) {
125:             throw new Nette\InvalidStateException('Directory to search has already been specified.');
126:         }
127:         $this->paths = is_array($paths[0]) ? $paths[0] : $paths;
128:         $this->cursor = &$this->exclude;
129:         return $this;
130:     }
131: 
132: 
133:     /**
134:      * Shows folder content prior to the folder.
135:      * @return static
136:      */
137:     public function childFirst()
138:     {
139:         $this->order = RecursiveIteratorIterator::CHILD_FIRST;
140:         return $this;
141:     }
142: 
143: 
144:     /**
145:      * Converts Finder pattern to regular expression.
146:      * @param  array
147:      * @return string|null
148:      */
149:     private static function buildPattern($masks)
150:     {
151:         $pattern = [];
152:         foreach ($masks as $mask) {
153:             $mask = rtrim(strtr($mask, '\\', '/'), '/');
154:             $prefix = '';
155:             if ($mask === '') {
156:                 continue;
157: 
158:             } elseif ($mask === '*') {
159:                 return null;
160: 
161:             } elseif ($mask[0] === '/') { // absolute fixing
162:                 $mask = ltrim($mask, '/');
163:                 $prefix = '(?<=^/)';
164:             }
165:             $pattern[] = $prefix . strtr(preg_quote($mask, '#'),
166:                 ['\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-']);
167:         }
168:         return $pattern ? '#/(' . implode('|', $pattern) . ')\z#i' : null;
169:     }
170: 
171: 
172:     /********************* iterator generator ****************d*g**/
173: 
174: 
175:     /**
176:      * Get the number of found files and/or directories.
177:      * @return int
178:      */
179:     public function count()
180:     {
181:         return iterator_count($this->getIterator());
182:     }
183: 
184: 
185:     /**
186:      * Returns iterator.
187:      * @return \Iterator
188:      */
189:     public function getIterator()
190:     {
191:         if (!$this->paths) {
192:             throw new Nette\InvalidStateException('Call in() or from() to specify directory to search.');
193: 
194:         } elseif (count($this->paths) === 1) {
195:             return $this->buildIterator($this->paths[0]);
196: 
197:         } else {
198:             $iterator = new \AppendIterator();
199:             $iterator->append($workaround = new \ArrayIterator(['workaround PHP bugs #49104, #63077']));
200:             foreach ($this->paths as $path) {
201:                 $iterator->append($this->buildIterator($path));
202:             }
203:             unset($workaround[0]);
204:             return $iterator;
205:         }
206:     }
207: 
208: 
209:     /**
210:      * Returns per-path iterator.
211:      * @param  string
212:      * @return \Iterator
213:      */
214:     private function buildIterator($path)
215:     {
216:         $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
217: 
218:         if ($this->exclude) {
219:             $iterator = new \RecursiveCallbackFilterIterator($iterator, function ($foo, $bar, RecursiveDirectoryIterator $file) {
220:                 if (!$file->isDot() && !$file->isFile()) {
221:                     foreach ($this->exclude as $filter) {
222:                         if (!call_user_func($filter, $file)) {
223:                             return false;
224:                         }
225:                     }
226:                 }
227:                 return true;
228:             });
229:         }
230: 
231:         if ($this->maxDepth !== 0) {
232:             $iterator = new RecursiveIteratorIterator($iterator, $this->order);
233:             $iterator->setMaxDepth($this->maxDepth);
234:         }
235: 
236:         $iterator = new \CallbackFilterIterator($iterator, function ($foo, $bar, \Iterator $file) {
237:             while ($file instanceof \OuterIterator) {
238:                 $file = $file->getInnerIterator();
239:             }
240: 
241:             foreach ($this->groups as $filters) {
242:                 foreach ($filters as $filter) {
243:                     if (!call_user_func($filter, $file)) {
244:                         continue 2;
245:                     }
246:                 }
247:                 return true;
248:             }
249:             return false;
250:         });
251: 
252:         return $iterator;
253:     }
254: 
255: 
256:     /********************* filtering ****************d*g**/
257: 
258: 
259:     /**
260:      * Restricts the search using mask.
261:      * Excludes directories from recursive traversing.
262:      * @param  mixed
263:      * @return static
264:      */
265:     public function exclude(...$masks)
266:     {
267:         $masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
268:         $pattern = self::buildPattern($masks);
269:         if ($pattern) {
270:             $this->filter(function (RecursiveDirectoryIterator $file) use ($pattern) {
271:                 return !preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/'));
272:             });
273:         }
274:         return $this;
275:     }
276: 
277: 
278:     /**
279:      * Restricts the search using callback.
280:      * @param  callable  function (RecursiveDirectoryIterator $file)
281:      * @return static
282:      */
283:     public function filter($callback)
284:     {
285:         $this->cursor[] = $callback;
286:         return $this;
287:     }
288: 
289: 
290:     /**
291:      * Limits recursion level.
292:      * @param  int
293:      * @return static
294:      */
295:     public function limitDepth($depth)
296:     {
297:         $this->maxDepth = $depth;
298:         return $this;
299:     }
300: 
301: 
302:     /**
303:      * Restricts the search by size.
304:      * @param  string  "[operator] [size] [unit]" example: >=10kB
305:      * @param  int
306:      * @return static
307:      */
308:     public function size($operator, $size = null)
309:     {
310:         if (func_num_args() === 1) { // in $operator is predicate
311:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?\z#i', $operator, $matches)) {
312:                 throw new Nette\InvalidArgumentException('Invalid size predicate format.');
313:             }
314:             list(, $operator, $size, $unit) = $matches;
315:             static $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
316:             $size *= $units[strtolower($unit)];
317:             $operator = $operator ?: '=';
318:         }
319:         return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $size) {
320:             return self::compare($file->getSize(), $operator, $size);
321:         });
322:     }
323: 
324: 
325:     /**
326:      * Restricts the search by modified time.
327:      * @param  string  "[operator] [date]" example: >1978-01-23
328:      * @param  mixed
329:      * @return static
330:      */
331:     public function date($operator, $date = null)
332:     {
333:         if (func_num_args() === 1) { // in $operator is predicate
334:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)\z#i', $operator, $matches)) {
335:                 throw new Nette\InvalidArgumentException('Invalid date predicate format.');
336:             }
337:             list(, $operator, $date) = $matches;
338:             $operator = $operator ?: '=';
339:         }
340:         $date = DateTime::from($date)->format('U');
341:         return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $date) {
342:             return self::compare($file->getMTime(), $operator, $date);
343:         });
344:     }
345: 
346: 
347:     /**
348:      * Compares two values.
349:      * @param  mixed
350:      * @param  mixed
351:      * @return bool
352:      */
353:     public static function compare($l, $operator, $r)
354:     {
355:         switch ($operator) {
356:             case '>':
357:                 return $l > $r;
358:             case '>=':
359:                 return $l >= $r;
360:             case '<':
361:                 return $l < $r;
362:             case '<=':
363:                 return $l <= $r;
364:             case '=':
365:             case '==':
366:                 return $l == $r;
367:             case '!':
368:             case '!=':
369:             case '<>':
370:                 return $l != $r;
371:             default:
372:                 throw new Nette\InvalidArgumentException("Unknown operator $operator.");
373:         }
374:     }
375: 
376: 
377:     /********************* extension methods ****************d*g**/
378: 
379: 
380:     public function __call($name, $args)
381:     {
382:         if ($callback = Nette\Utils\ObjectMixin::getExtensionMethod(__CLASS__, $name)) {
383:             return $callback($this, ...$args);
384:         }
385:         Nette\Utils\ObjectMixin::strictCall(__CLASS__, $name);
386:     }
387: 
388: 
389:     public static function extensionMethod($name, $callback)
390:     {
391:         Nette\Utils\ObjectMixin::setExtensionMethod(__CLASS__, $name, $callback);
392:     }
393: }
394: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0