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
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

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

Interfaces

  • IHtmlString

Exceptions

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