Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • Arrays
  • Finder
  • Html
  • Json
  • LimitedScope
  • MimeTypeDetector
  • Neon
  • NeonEntity
  • Paginator
  • Strings
  • Tokenizer
  • Validators

Exceptions

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