file_cache_준비
This commit is contained in:
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "nette/finder",
|
||||
"description": "🔍 Nette Finder: find files and directories with an intuitive API.",
|
||||
"keywords": ["nette", "filesystem", "iterator", "glob"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"nette/utils": "^2.4 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"tracy/tracy": "^2.3",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/nette": "<2.2"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse --level 5 src",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.5-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
How to contribute & use the issue tracker
|
||||
=========================================
|
||||
|
||||
Nette welcomes your contributions. There are several ways to help out:
|
||||
|
||||
* Create an issue on GitHub, if you have found a bug
|
||||
* Write test cases for open bug issues
|
||||
* Write fixes for open bug/feature issues, preferably with test cases included
|
||||
* Contribute to the [documentation](https://nette.org/en/writing)
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Please **do not use the issue tracker to ask questions**. We will be happy to help you
|
||||
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
**Feature requests** are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
|
||||
|
||||
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
|
||||
|
||||
Please do not fix whitespace, format code, or make a purely cosmetic patch.
|
||||
|
||||
Thanks! :heart:
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
Vendored
+181
@@ -0,0 +1,181 @@
|
||||
Nette Finder: Files Searching
|
||||
=============================
|
||||
|
||||
[](https://packagist.org/packages/nette/finder)
|
||||
[](https://travis-ci.org/nette/finder)
|
||||
[](https://coveralls.io/github/nette/finder?branch=master)
|
||||
[](https://github.com/nette/finder/releases)
|
||||
[](https://github.com/nette/finder/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Nette Finder makes browsing the directory structure really easy.
|
||||
|
||||
Documentation can be found on the [website](https://doc.nette.org/finder).
|
||||
|
||||
If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you!
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The recommended way to install is via Composer:
|
||||
|
||||
```
|
||||
composer require nette/finder
|
||||
```
|
||||
|
||||
It requires PHP version 7.1 and supports PHP up to 7.4.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
How to find all `*.txt` files in `$dir` directory without recursing subdirectories?
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*.txt')->in($dir) as $key => $file) {
|
||||
echo $key; // $key is a string containing absolute filename with path
|
||||
echo $file; // $file is an instance of SplFileInfo
|
||||
}
|
||||
```
|
||||
|
||||
As a result, the finder returns instances of `SplFileInfo`.
|
||||
|
||||
If the directory does not exist, an `UnexpectedValueException` is thrown.
|
||||
|
||||
And what about searching for `*.txt` files in `$dir` including subdirectories? Instead of `in()`, use `from()`:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*.txt')->from($dir) as $file) {
|
||||
echo $file;
|
||||
}
|
||||
```
|
||||
|
||||
Search by more masks, even inside more directories within one iteration:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*.txt', '*.php')
|
||||
->in($dir1, $dir2) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Parameters can also be arrays:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles($masks)->in($dirs) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Searching for `*.txt` files containing a number in the name:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*[0-9]*.txt')->from($dir) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Searching for `*.txt` files, except those containing '`X`' in the name:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*.txt')
|
||||
->exclude('*X*')->from($dir) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
`exclude()` is specified just after `findFiles()`, thus it applies to filename.
|
||||
|
||||
|
||||
Directories to omit can be specified using the `exclude` **after** `from` clause:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*.php')
|
||||
->from($dir)->exclude('temp', '.git') as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Here `exclude()` is after `from()`, thus it applies to the directory name.
|
||||
|
||||
|
||||
And now something a bit more complicated: searching for `*.txt` files located in subdirectories starting with '`te`', but not '`temp`':
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('te*/*.txt')
|
||||
->exclude('temp*/*')->from($dir) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Depth of search can be limited using the `limitDepth()` method.
|
||||
|
||||
|
||||
|
||||
Searching for directories
|
||||
-------------------------
|
||||
|
||||
In addition to files, it is possible to search for directories using `Finder::findDirectories('subdir*')`, or to search for files and directories: `Finder::find('file.txt')`.
|
||||
|
||||
|
||||
Filtering
|
||||
---------
|
||||
|
||||
You can also filter results. For example by size. This way we will traverse the files of size between 100B and 200B:
|
||||
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*.php')->size('>=', 100)->size('<=', 200)
|
||||
->from($dir) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Or files changed in the last two weeks:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*.php')->date('>', '- 2 weeks')
|
||||
->from($dir) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Here we traverse PHP files with number of lines greater than 1000. As a filter we use a custom callback:
|
||||
|
||||
```php
|
||||
$finder = Finder::findFiles('*.php')->filter(function($file) {
|
||||
return count(file($file->getPathname())) > 1000;
|
||||
})->from($dir);
|
||||
```
|
||||
|
||||
|
||||
Finder, find images larger than 50px × 50px:
|
||||
|
||||
```php
|
||||
foreach (Finder::findFiles('*')
|
||||
->dimensions('>50', '>50')->from($dir) as $file) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Connection to Amazon S3
|
||||
-----------------------
|
||||
|
||||
It's possible to use custom streams, for example Zend_Service_Amazon_S3:
|
||||
|
||||
```php
|
||||
$s3 = new Zend_Service_Amazon_S3($key, $secret);
|
||||
$s3->registerStreamWrapper('s3');
|
||||
|
||||
foreach (Finder::findFiles('photos*')
|
||||
->size('<=', 1e6)->in('s3://bucket-name') as $file) {
|
||||
echo $file;
|
||||
}
|
||||
```
|
||||
|
||||
Handy, right? You will certainly find a use for Finder in your applications.
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
|
||||
/**
|
||||
* Finder allows searching through directory trees using iterator.
|
||||
*
|
||||
* <code>
|
||||
* Finder::findFiles('*.php')
|
||||
* ->size('> 10kB')
|
||||
* ->from('.')
|
||||
* ->exclude('temp');
|
||||
* </code>
|
||||
*/
|
||||
class Finder implements \IteratorAggregate, \Countable
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var callable extension methods */
|
||||
private static $extMethods = [];
|
||||
|
||||
/** @var array */
|
||||
private $paths = [];
|
||||
|
||||
/** @var array of filters */
|
||||
private $groups = [];
|
||||
|
||||
/** @var array filter for recursive traversing */
|
||||
private $exclude = [];
|
||||
|
||||
/** @var int */
|
||||
private $order = RecursiveIteratorIterator::SELF_FIRST;
|
||||
|
||||
/** @var int */
|
||||
private $maxDepth = -1;
|
||||
|
||||
/** @var array */
|
||||
private $cursor;
|
||||
|
||||
|
||||
/**
|
||||
* Begins search for files matching mask and all directories.
|
||||
* @param string|string[] $masks
|
||||
* @return static
|
||||
*/
|
||||
public static function find(...$masks): self
|
||||
{
|
||||
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
|
||||
return (new static)->select($masks, 'isDir')->select($masks, 'isFile');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins search for files matching mask.
|
||||
* @param string|string[] $masks
|
||||
* @return static
|
||||
*/
|
||||
public static function findFiles(...$masks): self
|
||||
{
|
||||
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
|
||||
return (new static)->select($masks, 'isFile');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins search for directories matching mask.
|
||||
* @param string|string[] $masks
|
||||
* @return static
|
||||
*/
|
||||
public static function findDirectories(...$masks): self
|
||||
{
|
||||
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
|
||||
return (new static)->select($masks, 'isDir');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates filtering group by mask & type selector.
|
||||
* @return static
|
||||
*/
|
||||
private function select(array $masks, string $type): self
|
||||
{
|
||||
$this->cursor = &$this->groups[];
|
||||
$pattern = self::buildPattern($masks);
|
||||
$this->filter(function (RecursiveDirectoryIterator $file) use ($type, $pattern): bool {
|
||||
return !$file->isDot()
|
||||
&& $file->$type()
|
||||
&& (!$pattern || preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/')));
|
||||
});
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches in the given folder(s).
|
||||
* @param string|string[] $paths
|
||||
* @return static
|
||||
*/
|
||||
public function in(...$paths): self
|
||||
{
|
||||
$this->maxDepth = 0;
|
||||
return $this->from(...$paths);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches recursively from the given folder(s).
|
||||
* @param string|string[] $paths
|
||||
* @return static
|
||||
*/
|
||||
public function from(...$paths): self
|
||||
{
|
||||
if ($this->paths) {
|
||||
throw new Nette\InvalidStateException('Directory to search has already been specified.');
|
||||
}
|
||||
$this->paths = is_array($paths[0]) ? $paths[0] : $paths;
|
||||
$this->cursor = &$this->exclude;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows folder content prior to the folder.
|
||||
* @return static
|
||||
*/
|
||||
public function childFirst(): self
|
||||
{
|
||||
$this->order = RecursiveIteratorIterator::CHILD_FIRST;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts Finder pattern to regular expression.
|
||||
*/
|
||||
private static function buildPattern(array $masks): ?string
|
||||
{
|
||||
$pattern = [];
|
||||
foreach ($masks as $mask) {
|
||||
$mask = rtrim(strtr($mask, '\\', '/'), '/');
|
||||
$prefix = '';
|
||||
if ($mask === '') {
|
||||
continue;
|
||||
|
||||
} elseif ($mask === '*') {
|
||||
return null;
|
||||
|
||||
} elseif ($mask[0] === '/') { // absolute fixing
|
||||
$mask = ltrim($mask, '/');
|
||||
$prefix = '(?<=^/)';
|
||||
}
|
||||
$pattern[] = $prefix . strtr(preg_quote($mask, '#'),
|
||||
['\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-']);
|
||||
}
|
||||
return $pattern ? '#/(' . implode('|', $pattern) . ')$#Di' : null;
|
||||
}
|
||||
|
||||
|
||||
/********************* iterator generator ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Get the number of found files and/or directories.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return iterator_count($this->getIterator());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns iterator.
|
||||
*/
|
||||
public function getIterator(): \Iterator
|
||||
{
|
||||
if (!$this->paths) {
|
||||
throw new Nette\InvalidStateException('Call in() or from() to specify directory to search.');
|
||||
|
||||
} elseif (count($this->paths) === 1) {
|
||||
return $this->buildIterator((string) $this->paths[0]);
|
||||
|
||||
} else {
|
||||
$iterator = new \AppendIterator();
|
||||
foreach ($this->paths as $path) {
|
||||
$iterator->append($this->buildIterator((string) $path));
|
||||
}
|
||||
return $iterator;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns per-path iterator.
|
||||
*/
|
||||
private function buildIterator(string $path): \Iterator
|
||||
{
|
||||
$iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
|
||||
|
||||
if ($this->exclude) {
|
||||
$iterator = new \RecursiveCallbackFilterIterator($iterator, function ($foo, $bar, RecursiveDirectoryIterator $file): bool {
|
||||
if (!$file->isDot() && !$file->isFile()) {
|
||||
foreach ($this->exclude as $filter) {
|
||||
if (!$filter($file)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->maxDepth !== 0) {
|
||||
$iterator = new RecursiveIteratorIterator($iterator, $this->order);
|
||||
$iterator->setMaxDepth($this->maxDepth);
|
||||
}
|
||||
|
||||
$iterator = new \CallbackFilterIterator($iterator, function ($foo, $bar, \Iterator $file): bool {
|
||||
while ($file instanceof \OuterIterator) {
|
||||
$file = $file->getInnerIterator();
|
||||
}
|
||||
|
||||
foreach ($this->groups as $filters) {
|
||||
foreach ($filters as $filter) {
|
||||
if (!$filter($file)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return $iterator;
|
||||
}
|
||||
|
||||
|
||||
/********************* filtering ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Restricts the search using mask.
|
||||
* Excludes directories from recursive traversing.
|
||||
* @param string|string[] $masks
|
||||
* @return static
|
||||
*/
|
||||
public function exclude(...$masks): self
|
||||
{
|
||||
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
|
||||
$pattern = self::buildPattern($masks);
|
||||
if ($pattern) {
|
||||
$this->filter(function (RecursiveDirectoryIterator $file) use ($pattern): bool {
|
||||
return !preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/'));
|
||||
});
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restricts the search using callback.
|
||||
* @param callable $callback function (RecursiveDirectoryIterator $file): bool
|
||||
* @return static
|
||||
*/
|
||||
public function filter(callable $callback): self
|
||||
{
|
||||
$this->cursor[] = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Limits recursion level.
|
||||
* @return static
|
||||
*/
|
||||
public function limitDepth(int $depth): self
|
||||
{
|
||||
$this->maxDepth = $depth;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restricts the search by size.
|
||||
* @param string $operator "[operator] [size] [unit]" example: >=10kB
|
||||
* @return static
|
||||
*/
|
||||
public function size(string $operator, int $size = null): self
|
||||
{
|
||||
if (func_num_args() === 1) { // in $operator is predicate
|
||||
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) {
|
||||
throw new Nette\InvalidArgumentException('Invalid size predicate format.');
|
||||
}
|
||||
[, $operator, $size, $unit] = $matches;
|
||||
static $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
|
||||
$size *= $units[strtolower($unit)];
|
||||
$operator = $operator ?: '=';
|
||||
}
|
||||
return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $size): bool {
|
||||
return self::compare($file->getSize(), $operator, $size);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restricts the search by modified time.
|
||||
* @param string $operator "[operator] [date]" example: >1978-01-23
|
||||
* @param string|int|\DateTimeInterface $date
|
||||
* @return static
|
||||
*/
|
||||
public function date(string $operator, $date = null): self
|
||||
{
|
||||
if (func_num_args() === 1) { // in $operator is predicate
|
||||
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) {
|
||||
throw new Nette\InvalidArgumentException('Invalid date predicate format.');
|
||||
}
|
||||
[, $operator, $date] = $matches;
|
||||
$operator = $operator ?: '=';
|
||||
}
|
||||
$date = DateTime::from($date)->format('U');
|
||||
return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $date): bool {
|
||||
return self::compare($file->getMTime(), $operator, $date);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares two values.
|
||||
*/
|
||||
public static function compare($l, string $operator, $r): bool
|
||||
{
|
||||
switch ($operator) {
|
||||
case '>':
|
||||
return $l > $r;
|
||||
case '>=':
|
||||
return $l >= $r;
|
||||
case '<':
|
||||
return $l < $r;
|
||||
case '<=':
|
||||
return $l <= $r;
|
||||
case '=':
|
||||
case '==':
|
||||
return $l == $r;
|
||||
case '!':
|
||||
case '!=':
|
||||
case '<>':
|
||||
return $l != $r;
|
||||
default:
|
||||
throw new Nette\InvalidArgumentException("Unknown operator $operator.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************* extension methods ****************d*g**/
|
||||
|
||||
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
return isset(self::$extMethods[$name])
|
||||
? (self::$extMethods[$name])($this, ...$args)
|
||||
: Nette\Utils\ObjectHelpers::strictCall(get_class($this), $name, array_keys(self::$extMethods));
|
||||
}
|
||||
|
||||
|
||||
public static function extensionMethod(string $name, callable $callback): void
|
||||
{
|
||||
self::$extMethods[$name] = $callback;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user