Dep: update
주로 PHAN
This commit is contained in:
Vendored
+10
-5
@@ -3,7 +3,7 @@
|
||||
"description": "⏱ Nette Caching: library with easy-to-use API and many cache backends.",
|
||||
"keywords": ["nette", "cache", "journal", "sqlite", "memcached"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
@@ -15,15 +15,16 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"php": ">=7.2 <8.1",
|
||||
"nette/finder": "^2.4 || ^3.0",
|
||||
"nette/utils": "^2.4 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"nette/di": "^v3.0",
|
||||
"latte/latte": "^2.4",
|
||||
"tracy/tracy": "^2.4"
|
||||
"latte/latte": "^2.10",
|
||||
"tracy/tracy": "^2.4",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pdo_sqlite": "to use SQLiteStorage or SQLiteJournal"
|
||||
@@ -32,9 +33,13 @@
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
"dev-master": "3.1-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+318
-188
@@ -2,259 +2,389 @@ Nette Caching
|
||||
=============
|
||||
|
||||
[](https://packagist.org/packages/nette/caching)
|
||||
[](https://travis-ci.org/nette/caching)
|
||||
[](https://github.com/nette/caching/actions)
|
||||
[](https://coveralls.io/github/nette/caching?branch=master)
|
||||
[](https://github.com/nette/caching/releases)
|
||||
[](https://github.com/nette/caching/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
============
|
||||
|
||||
Cache accelerates your application by storing data - once hardly retrieved - for future use.
|
||||
|
||||
Documentation can be found on the [website](https://doc.nette.org/caching).
|
||||
If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you!
|
||||
|
||||
|
||||
[Support Me](https://github.com/sponsors/dg)
|
||||
--------------------------------------------
|
||||
|
||||
Do you like Nette Caching? Are you looking forward to the new features?
|
||||
|
||||
[](https://github.com/sponsors/dg)
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The recommended way to install Nette Caching is via Composer:
|
||||
|
||||
```
|
||||
composer require nette/caching
|
||||
```
|
||||
|
||||
It requires PHP version 7.1 and supports PHP up to 7.4.
|
||||
It requires PHP version 7.2 and supports PHP up to 8.0.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
Basic Usage
|
||||
===========
|
||||
|
||||
Nette Caching offers a very intuitive API for cache manipulation. Before we show you the first example, we need to think about place where
|
||||
to store data physically. We can use a database, Memcached server, or the most available storage - hard drive:
|
||||
The center of work with the cache is the object [Nette\Caching\Cache](https://api.nette.org/3.0/Nette/Caching/Cache.html). We create its instance and pass the so-called storage to the constructor as a parameter. Which is an object representing the place where the data will be physically stored (database, Memcached, files on disk, ...). You will find out all the essentials in [section Storages](#Storages).
|
||||
|
||||
```php
|
||||
// the `temp` directory will be the storage
|
||||
$storage = new Nette\Caching\Storages\FileStorage('temp');
|
||||
```
|
||||
|
||||
The `Nette\Caching\Storages\FileStorage` storage is very well optimized for performance and in the first place,
|
||||
it provides full atomicity of operations. What does that mean? When we use cache we can be sure we are not reading a file that is not fully
|
||||
written yet (by another thread) or that the file gets deleted "under our hands". Using the cache is therefore completely safe.
|
||||
|
||||
For manipulation with cache, we use the `Nette\Caching\Cache`:
|
||||
For the following examples, suppose we have an alias `Cache` and a storage in the variable `$storage`.
|
||||
|
||||
```php
|
||||
use Nette\Caching\Cache;
|
||||
|
||||
$cache = new Cache($storage); // $storage from the previous example
|
||||
$storage // instance of Nette\Caching\IStorage
|
||||
```
|
||||
|
||||
Let's save the contents of the '`$data`' variable under the '`$key`' key:
|
||||
The cache is actually a *key–value store*, so we read and write data under keys just like associative arrays. Applications consist of a number of independent parts, and if they all used one storage (for idea: one directory on a disk), sooner or later there would be a key collision. The Nette Framework solves the problem by dividing the entire space into namespaces (subdirectories). Each part of the program then uses its own space with a unique name and no collisions can occur.
|
||||
|
||||
The name of the space is specified as the second parameter of the constructor of the Cache class:
|
||||
|
||||
```php
|
||||
$cache->save($key, $data);
|
||||
$cache = new Cache($storage, 'Full Html Pages');
|
||||
```
|
||||
|
||||
This way, we can read from the cache: (if there is no such item in the cache, the `null` value is returned)
|
||||
We can now use object `$cache` to read and write from the cache. The method `load()` is used for both. The first argument is the key and the second is the PHP callback, which is called when the key is not found in the cache. The callback generates a value, returns it and caches it:
|
||||
|
||||
```php
|
||||
$value = $cache->load($key);
|
||||
if ($value === null) ...
|
||||
```
|
||||
|
||||
Method `load()` has second parameter `callable` `$fallback`, which is called when there is no such item in the cache. This callback receives the array *$dependencies* by reference, which you can use for setting expiration rules.
|
||||
|
||||
```php
|
||||
$value = $cache->load($key, function(& $dependencies) {
|
||||
// some calculation
|
||||
return 15;
|
||||
$value = $cache->load($key, function () use ($key) {
|
||||
$computedValue = ...; // heavy computations
|
||||
return $computedValue;
|
||||
});
|
||||
```
|
||||
|
||||
We could delete the item from the cache either by saving null or by calling `remove()` method:
|
||||
If the second parameter is not specified `$value = $cache->load($key)`, the `null` is returned if the item is not in the cache.
|
||||
|
||||
The great thing is that any serializable structures can be cached, not only strings. And the same applies for keys.
|
||||
|
||||
The item is cleared from the cache using method `remove()`:
|
||||
|
||||
```php
|
||||
$cache->save($key, null);
|
||||
// or
|
||||
$cache->remove($key);
|
||||
```
|
||||
|
||||
It's possible to save any structure to the cache, not only strings. The same applies for keys.
|
||||
You can also cache an item using method `$cache->save($key, $value, array $dependencies = [])`. However, the above method using `load()` is preferred.
|
||||
|
||||
|
||||
Web applications typically consist of a number of independent parts, and if they all cache data in the same storage (for example the same directory),
|
||||
sooner or later there would be collisions in names. Nette Framework solves this by splitting the whole storage to sections
|
||||
(in the `FileStorage` case using subdirectories). Each part of the application uses it's own section with unique name, therefore no collision can occur.
|
||||
The name of the section can be passed as the second parameter to the `Cache` class constructor. (These sections are often refered to as //cache namespaces//.)
|
||||
|
||||
|
||||
Memoization
|
||||
===========
|
||||
|
||||
Memoization means caching the result of a function or method so you can use it next time instead of calculating the same thing again and again.
|
||||
|
||||
Methods and functions can be called memoized using `call(callable $callback, ...$args)`:
|
||||
|
||||
```php
|
||||
$cache = new Cache($storage, 'htmlOutput');
|
||||
$result = $cache->call('gethostbyaddr', $ip);
|
||||
```
|
||||
|
||||
The function `gethostbyaddr()` is called only once for each parameter `$ip` and the next time the value from the cache will be returned.
|
||||
|
||||
It is also possible to create a memoized wrapper for a method or function that can be called later:
|
||||
|
||||
```php
|
||||
function factorial($num)
|
||||
{
|
||||
return ...;
|
||||
}
|
||||
|
||||
$memoizedFactorial = $cache->wrap('factorial');
|
||||
|
||||
$result = $memoizedFactorial(5); // counts it
|
||||
$result = $memoizedFactorial(5); // returns it from cache
|
||||
```
|
||||
|
||||
|
||||
Caching Function Results
|
||||
Expiration & Invalidation
|
||||
=========================
|
||||
|
||||
With caching, it is necessary to address the question that some of the previously saved data will become invalid over time. Nette Framework provides a mechanism, how to limit the validity of data and how to delete them in a controlled way ("to invalidate them", using the framework's terminology).
|
||||
|
||||
The validity of the data is set at the time of saving using the third parameter of the method `save()`, eg:
|
||||
|
||||
```php
|
||||
$cache->save($key, $value, [
|
||||
Cache::EXPIRE => '20 minutes',
|
||||
]);
|
||||
```
|
||||
|
||||
Or using the `$dependencies` parameter passed by reference to the callback in the `load()` method, eg:
|
||||
|
||||
```php
|
||||
$value = $cache->load($key, function (&$dependencies) {
|
||||
$dependencies[Cache::EXPIRE] = '20 minutes';
|
||||
return ...;
|
||||
]);
|
||||
```
|
||||
|
||||
In the following examples, we will assume the second variant and thus the existence of a variable `$dependencies`.
|
||||
|
||||
|
||||
Expiration
|
||||
----------
|
||||
|
||||
The simplest exiration is the time limit. Here's how to cache data valid for 20 minutes:
|
||||
|
||||
```php
|
||||
// it also accepts the number of seconds or the UNIX timestamp
|
||||
$dependencies[Cache::EXPIRE] = '20 minutes';
|
||||
```
|
||||
|
||||
If we want to extend the validity period with each reading, it can be achieved this way, but beware, this will increase the cache overhead:
|
||||
|
||||
```php
|
||||
$dependencies[Cache::SLIDING] = true;
|
||||
```
|
||||
|
||||
The handy option is the ability to let the data expire when a particular file is changed or one of several files. This can be used, for example, for caching data resulting from procession these files. Use absolute paths.
|
||||
|
||||
```php
|
||||
$dependencies[Cache::FILES] = '/path/to/data.yaml';
|
||||
// nebo
|
||||
$dependencies[Cache::FILES] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];
|
||||
```
|
||||
|
||||
We can let an item in the cache expired when another item (or one of several others) expires. This can be used when we cache the entire HTML page and fragments of it under other keys. Once the snippet changes, the entire page becomes invalid. If we have fragments stored under keys such as `frag1` and `frag2`, we will use:
|
||||
|
||||
```php
|
||||
$dependencies[Cache::ITEMS] = ['frag1', 'frag2'];
|
||||
```
|
||||
|
||||
Expiration can also be controlled using custom functions or static methods, which always decide when reading whether the item is still valid. For example, we can let the item expire whenever the PHP version changes. We will create a function that compares the current version with the parameter, and when saving we will add an array in the form `[function name, ...arguments]` to the dependencies:
|
||||
|
||||
```php
|
||||
function checkPhpVersion($ver): bool
|
||||
{
|
||||
return $ver === PHP_VERSION_ID;
|
||||
}
|
||||
|
||||
$dependencies[Cache::CALLBACKS] = [
|
||||
['checkPhpVersion', PHP_VERSION_ID] // expire when checkPhpVersion(...) === false
|
||||
];
|
||||
```
|
||||
|
||||
Of course, all criteria can be combined. The cache then expires when at least one criterion is not met.
|
||||
|
||||
```php
|
||||
$dependencies[Cache::EXPIRE] = '20 minutes';
|
||||
$dependencies[Cache::FILES] = '/path/to/data.yaml';
|
||||
```
|
||||
|
||||
|
||||
|
||||
Invalidation using Tags
|
||||
-----------------------
|
||||
|
||||
Tags are a very useful invalidation tool. We can assign a list of tags, which are arbitrary strings, to each item stored in the cache. For example, suppose we have an HTML page with an article and comments, which we want to cache. So we specify tags when saving to cache:
|
||||
|
||||
```php
|
||||
$dependencies[Cache::TAGS] = ["article/$articleId", "comments/$articleId"];
|
||||
```
|
||||
|
||||
Now, let's move to the administration. Here we have a form for article editing. Together with saving the article to a database, we call the `clean()` command, which will delete cached items by tag:
|
||||
|
||||
```php
|
||||
$cache->clean([
|
||||
Cache::TAGS => ["article/$articleId"],
|
||||
]);
|
||||
```
|
||||
|
||||
Likewise, in the place of adding a new comment (or editing a comment), we will not forget to invalidate the relevant tag:
|
||||
|
||||
```php
|
||||
$cache->clean([
|
||||
Cache::TAGS => ["comments/$articleId"],
|
||||
]);
|
||||
```
|
||||
|
||||
What have we achieved? That our HTML cache will be invalidated (deleted) whenever the article or comments change. When editing an article with ID = 10, the tag `article/10` is forced to be invalidated and the HTML page carrying the tag is deleted from the cache. The same happens when you insert a new comment under the relevant article.
|
||||
|
||||
Tags require [Journal](#Journal).
|
||||
|
||||
|
||||
Invalidation by Priority
|
||||
------------------------
|
||||
|
||||
Caching the result of a function or method call can be achieved using the `call()` method:
|
||||
We can set the priority for individual items in the cache, and it will be possible to delete them in a controlled way when, for example, the cache exceeds a certain size:
|
||||
|
||||
```php
|
||||
$name = $cache->call('gethostbyaddr', $ip);
|
||||
$dependencies[Cache::PRIORITY] = 50;
|
||||
```
|
||||
|
||||
The `gethostbyaddr($ip)` will therefore be called only once and next time, only the value from cache will be returned. Of course, for different `$ip`,
|
||||
different results are cached.
|
||||
|
||||
Output Caching
|
||||
--------------
|
||||
|
||||
The output can be cached not only in templates:
|
||||
Delete all items with a priority equal to or less than 100:
|
||||
|
||||
```php
|
||||
if ($block = $cache->start($key)) {
|
||||
|
||||
... printing some data ...
|
||||
|
||||
$block->end(); // save the output to the cache
|
||||
}
|
||||
```
|
||||
|
||||
In case that the output is already present in the cache, the `start()` method prints it and return `null`. Otherwise, it starts to buffer the output and
|
||||
returns the `$block` object using which we finally save the data to the cache.
|
||||
|
||||
|
||||
Expiration and Invalidation
|
||||
---------------------------
|
||||
Here come two problems of storing data in the cache. First, there is a possibility that the storage is completely filled and you cannot save more data inside.
|
||||
And it may happen that some od the previously saved data will become invalid over time. Therefore, Nette Framework provides a mechanism,
|
||||
how to limit the validity of data and how to delete them in a controlled way ("to invalidate them", using the framework's terminology).
|
||||
|
||||
Data validity is set when saving the data using the third parameter of the `save()` method:
|
||||
|
||||
```php
|
||||
$cache->save($key, $data, array(
|
||||
Cache::EXPIRE => '20 minutes', // accepts also seconds or a timestamp.
|
||||
));
|
||||
```
|
||||
|
||||
It's obvious from the code itself, that we saved the data for next 20 minutes. After this period, the cache will report that there is no record
|
||||
under the '`$key`' key (ie, will return `null`). In fact, you can use any time value that is a valid value for PHP function strToTime().
|
||||
If we want to extend the validity period with each reading, it can be achieved this way:
|
||||
|
||||
```php
|
||||
$cache->save($key, $data, array(
|
||||
Cache::EXPIRE => '20 minutes',
|
||||
Cache::SLIDING => true,
|
||||
));
|
||||
```
|
||||
|
||||
Very handy is also the ability to let the data expire when a particular file is changed or one of several files.
|
||||
That can be used for stroring data resulting from parsing these files to the cache. For trouble-free functionality, it's recommended to use absolute paths.
|
||||
|
||||
```php
|
||||
$cache->save($key, $data, array(
|
||||
Cache::FILES => 'data.yaml', // an array of files can also be specified
|
||||
));
|
||||
```
|
||||
|
||||
The `Cache::FILES` criteria, of course, can be combined with the time expiration using `Cache::EXPIRE` etc.
|
||||
|
||||
The cache can also depend on other cached items. That can be used when we save the whole HTML page in the cache and under different keys, some of its fragments.
|
||||
As soon as a part changes, the whole page is invalidated.
|
||||
|
||||
```php
|
||||
$cache->save('page', $html, array(
|
||||
// will expire if frag1 or frag2 expires
|
||||
Cache::ITEMS => array('frag1', 'frag2'),
|
||||
));
|
||||
```
|
||||
|
||||
Expiration can be controlled even by your own callbacks:
|
||||
|
||||
```php
|
||||
function controlExpiration($val)
|
||||
{
|
||||
return $val;
|
||||
}
|
||||
|
||||
$cache->save($key, $value, array(
|
||||
Cache::CALLBACKS => array(array('controlExpiration', 1)),
|
||||
));
|
||||
```
|
||||
|
||||
|
||||
Expiration Using Tags and Priority
|
||||
----------------------------------
|
||||
|
||||
Very useful invalidation tool are so called //tags//. We can assign a list of tags to each item. For example, suppose we have an HTML page with an article and
|
||||
comments, which we want to cache. So we specify tags when saving to cache:
|
||||
|
||||
```php
|
||||
$cache->save($articleId, $html, array(
|
||||
Cache::TAGS => array("article/$articleId", "comments/$articleId"),
|
||||
));
|
||||
```
|
||||
|
||||
Now, let's move to the administration. Here we have a form for article editing. Together with saving the article to a database, we call the `clean()` command,
|
||||
which will delete cached items by tag:
|
||||
|
||||
```php
|
||||
$cache->clean(array(
|
||||
Cache::TAGS => array("article/$articleId"),
|
||||
));
|
||||
```
|
||||
|
||||
And in the place for adding new comments (or editing them), don't forget to invalidate appropriate tag:
|
||||
|
||||
```php
|
||||
$cache->clean(array(
|
||||
Cache::TAGS => array("comments/$articleId"),
|
||||
));
|
||||
```
|
||||
|
||||
What we have achieved? That the HTML cache will invalidate automatically. Whenever someone changes the article with ID of 10, it will force the `article/10`
|
||||
tag to invalidate and the tagged HTML page in cache is cleared. The same will happen when someone inserts a new comment below the article.
|
||||
|
||||
Similar to tags, you can control expiration by priority:
|
||||
|
||||
```php
|
||||
$cache->save($key, $value, array(
|
||||
Cache::PRIORITY => 50,
|
||||
));
|
||||
|
||||
// all cached items with priority less than or equal to 100 will be removed.
|
||||
$cache->clean(array(
|
||||
$cache->clean([
|
||||
Cache::PRIORITY => 100,
|
||||
));
|
||||
]);
|
||||
```
|
||||
|
||||
Priorities require so-called [Journal](#Journal).
|
||||
|
||||
|
||||
Clear Cache
|
||||
-----------
|
||||
|
||||
The `Cache::ALL` parameter clears everything:
|
||||
|
||||
```php
|
||||
$cache->clean([
|
||||
Cache::ALL => true,
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
Cache Storage
|
||||
-------------
|
||||
In addition to already mentioned `FileStorage`, Nette Framework also provides `MemcachedStorage` which stores
|
||||
data to the `Memcached` server, and also `MemoryStorage` for storing data in memory for duration of the request.
|
||||
The special `DevNullStorage`, which does precisely nothing, can be used for testing, when we want to eliminate the influence of caching.
|
||||
Bulk Reading
|
||||
============
|
||||
|
||||
Of course, it's possible to create your own storage. The only requirement is to implement the `IStorage` interface.
|
||||
|
||||
|
||||
Concurrent Caching
|
||||
------------------
|
||||
|
||||
Deleting the cache is a common operation when uploading a new application version to the server. At that moment, however, the server gets pretty hard time,
|
||||
because it has to build a complete new cache. Retrieving some data can be quite difficult, for example the RobotLoader cache building.
|
||||
And moreover, if, say, 30 requests come in a short period, the resource consumption is even higher.
|
||||
|
||||
The solution is to modify application behaviour so that data are created only by one thread and others are waiting. To do this, specify the value as a callback
|
||||
or use an anonymous function:
|
||||
For bulk reading and writing to cache, the `bulkLoad()` method is used, where we pass an array of keys and obtain an array of values:
|
||||
|
||||
```php
|
||||
$result = $cache->save($key, function() {
|
||||
return buildData(); // difficult operation
|
||||
$values = $cache->bulkLoad($keys);
|
||||
```
|
||||
|
||||
Method `bulkLoad()` works similarly to `load()` with the second callback parameter, to which the key of the generated item is passed:
|
||||
|
||||
```php
|
||||
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
|
||||
$computedValue = ...; // heavy computations
|
||||
return $computedValue;
|
||||
});
|
||||
```
|
||||
|
||||
Framework will ensure that the body of the function will be called only by one thread at once, and other threads will be waiting.
|
||||
If the thread fails for some reason, another gets chance.
|
||||
|
||||
Output Caching
|
||||
==============
|
||||
|
||||
The output can be captured and cached very elegantly:
|
||||
|
||||
```php
|
||||
if ($capture = $cache->start($key)) {
|
||||
|
||||
echo ... // printing some data
|
||||
|
||||
$capture->end(); // save the output to the cache
|
||||
}
|
||||
```
|
||||
|
||||
In case that the output is already present in the cache, the `start()` method prints it and returns `null`, so the condition will not be executed. Otherwise, it starts to buffer the output and returns the `$capture` object using which we finally save the data to the cache.
|
||||
|
||||
|
||||
Caching in Latte
|
||||
================
|
||||
|
||||
Caching in templates [Latte](https://latte.nette.org) is very easy, just wrap part of the template with tags `{cache}...{/cache}`. The cache is automatically invalidated when the source template changes (including any included templates within the `{cache}` tags). Tags `{cache}` can be nested, and when a nested block is invalidated (for example, by a tag), the parent block is also invalidated.
|
||||
|
||||
In the tag it is possible to specify the keys to which the cache will be bound (here the variable `$id`) and set the expiration and [invalidation tags](#invalidation-using-tags).
|
||||
|
||||
```html
|
||||
{cache $id, expire => '20 minutes', tags => [tag1, tag2]}
|
||||
...
|
||||
{/cache}
|
||||
```
|
||||
|
||||
All parameters are optional, so you don't have to specify expiration, tags, or keys.
|
||||
|
||||
The use of the cache can also be conditioned by `if` - the content will then be cached only if the condition is met:
|
||||
|
||||
```html
|
||||
{cache $id, if => !$form->isSubmitted()}
|
||||
{$form}
|
||||
{/cache}
|
||||
```
|
||||
|
||||
|
||||
Storages
|
||||
========
|
||||
|
||||
A storage is an object that represents where data is physically stored. We can use a database, a Memcached server, or the most available storage, which are files on disk.
|
||||
|
||||
Storage | Description
|
||||
--------|----------------------
|
||||
FileStorage | default storage with saving to files on disk
|
||||
MemcachedStorage | uses the `Memcached` server
|
||||
MemoryStorage | data are temporarily in memory
|
||||
SQLiteStorage | data is stored in SQLite database
|
||||
DevNullStorage | data aren't stored - for testing purposes
|
||||
|
||||
|
||||
FileStorage
|
||||
-----------
|
||||
|
||||
Writes the cache to files on disk. The storage `Nette\Caching\Storages\FileStorage` is very well optimized for performance and above all ensures full atomicity of operations. What does it mean? That when using the cache, it cannot happen that we read a file that has not yet been completely written by another thread, or that someone would delete it "under your hands". The use of the cache is therefore completely safe.
|
||||
|
||||
This storage also has an important built-in feature that prevents an extreme increase in CPU usage when the cache is cleared or cold (ie not created). This is [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede) prevention.
|
||||
It happens that at one moment there are several concurrent requests that want the same thing from the cache (eg the result of an expensive SQL query) and because it is not cached, all processes start executing the same SQL query.
|
||||
The processor load is multiplied and it can even happen that no thread can respond within the time limit, the cache is not created and the application crashes.
|
||||
Fortunately, the cache in Nette works in such a way that when there are multiple concurrent requests for one item, it is generated only by the first thread, the others wait and then use the generated result.
|
||||
|
||||
Example of creating a FileStorage:
|
||||
|
||||
```php
|
||||
// the storage will be the directory '/path/to/temp' on the disk
|
||||
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');
|
||||
```
|
||||
|
||||
MemcachedStorage
|
||||
----------------
|
||||
|
||||
The server [Memcached](https://memcached.org) is a high-performance distributed storage system whose adapter is `Nette\Caching\Storages\MemcachedStorage`.
|
||||
|
||||
Requires PHP extension `memcached`.
|
||||
|
||||
```php
|
||||
$storage = new Nette\Caching\Storages\MemcachedStorage('10.0.0.158');
|
||||
```
|
||||
|
||||
MemoryStorage
|
||||
-------------
|
||||
|
||||
`Nette\Caching\Storages\MemoryStorage` is a storage that stores data in a PHP array and is thus lost when the request is terminated.
|
||||
|
||||
```php
|
||||
$storage = new Nette\Caching\Storages\MemoryStorage;
|
||||
```
|
||||
|
||||
|
||||
SQLiteStorage
|
||||
-------------
|
||||
|
||||
The SQLite database and adapter `Nette\Caching\Storages\SQLiteStorage` offer a way to cache in a single file on disk. The configuration will specify the path to this file.
|
||||
|
||||
Requires PHP extensions `pdo` and `pdo_sqlite`.
|
||||
|
||||
```php
|
||||
$storage = new Nette\Caching\Storages\SQLiteStorage('/path/to/cache.sdb');
|
||||
```
|
||||
|
||||
|
||||
DevNullStorage
|
||||
--------------
|
||||
|
||||
A special implementation of storage is `Nette\Caching\Storages\DevNullStorage`, which does not actually store data at all. It is therefore suitable for testing if we want to eliminate the effect of the cache.
|
||||
|
||||
```php
|
||||
$storage = new Nette\Caching\Storages\DevNullStorage;
|
||||
```
|
||||
|
||||
|
||||
Journal
|
||||
=======
|
||||
|
||||
Nette stores tags and priorities in a so-called journal. By default, SQLite and file `journal.s3db` are used for this, and **PHP extensions `pdo` and `pdo_sqlite` are required.**
|
||||
|
||||
If you like Nette, **[please make a donation now](https://github.com/sponsors/dg)**. Thank you!
|
||||
|
||||
@@ -39,12 +39,12 @@ final class CacheExtension extends Nette\DI\CompilerExtension
|
||||
|
||||
if (extension_loaded('pdo_sqlite')) {
|
||||
$builder->addDefinition($this->prefix('journal'))
|
||||
->setType(Nette\Caching\Storages\IJournal::class)
|
||||
->setType(Nette\Caching\Storages\Journal::class)
|
||||
->setFactory(Nette\Caching\Storages\SQLiteJournal::class, [$dir . '/journal.s3db']);
|
||||
}
|
||||
|
||||
$builder->addDefinition($this->prefix('storage'))
|
||||
->setType(Nette\Caching\IStorage::class)
|
||||
->setType(Nette\Caching\Storage::class)
|
||||
->setFactory(Nette\Caching\Storages\FileStorage::class, [$dir]);
|
||||
|
||||
if ($this->name === 'cache') {
|
||||
|
||||
+41
-14
@@ -59,8 +59,10 @@ final class CacheMacro implements Latte\IMacro
|
||||
$this->used = true;
|
||||
$node->empty = false;
|
||||
$node->openingCode = Latte\PhpWriter::using($node)
|
||||
->write('<?php if (Nette\Bridges\CacheLatte\CacheMacro::createCache($this->global->cacheStorage, %var, $this->global->cacheStack, %node.array?)) { ?>',
|
||||
Nette\Utils\Random::generate()
|
||||
->write(
|
||||
'<?php if (Nette\Bridges\CacheLatte\CacheMacro::createCache($this->global->cacheStorage, %var, $this->global->cacheStack, %node.array?)) /* line %var */ try { ?>',
|
||||
Nette\Utils\Random::generate(),
|
||||
$node->startLine
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,7 +74,14 @@ final class CacheMacro implements Latte\IMacro
|
||||
public function nodeClosed(Latte\MacroNode $node)
|
||||
{
|
||||
$node->closingCode = Latte\PhpWriter::using($node)
|
||||
->write('<?php Nette\Bridges\CacheLatte\CacheMacro::endCache($this->global->cacheStack, %node.array?); } ?>');
|
||||
->write(
|
||||
'<?php
|
||||
Nette\Bridges\CacheLatte\CacheMacro::endCache($this->global->cacheStack, %node.array?) /* line %var */;
|
||||
} catch (\Throwable $ʟ_e) {
|
||||
Nette\Bridges\CacheLatte\CacheMacro::rollback($this->global->cacheStack); throw $ʟ_e;
|
||||
} ?>',
|
||||
$node->startLine
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -94,8 +103,12 @@ final class CacheMacro implements Latte\IMacro
|
||||
* Starts the output cache. Returns Nette\Caching\OutputHelper object if buffering was started.
|
||||
* @return Nette\Caching\OutputHelper|\stdClass
|
||||
*/
|
||||
public static function createCache(Nette\Caching\IStorage $cacheStorage, string $key, ?array &$parents, array $args = null)
|
||||
{
|
||||
public static function createCache(
|
||||
Nette\Caching\Storage $cacheStorage,
|
||||
string $key,
|
||||
?array &$parents,
|
||||
array $args = null
|
||||
) {
|
||||
if ($args) {
|
||||
if (array_key_exists('if', $args) && !$args['if']) {
|
||||
return $parents[] = new \stdClass;
|
||||
@@ -119,18 +132,32 @@ final class CacheMacro implements Latte\IMacro
|
||||
* @param Nette\Caching\OutputHelper[] $parents
|
||||
*/
|
||||
public static function endCache(array &$parents, array $args = null): void
|
||||
{
|
||||
$helper = array_pop($parents);
|
||||
if (!$helper instanceof Nette\Caching\OutputHelper) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($args['dependencies'])) {
|
||||
$args += $args['dependencies']();
|
||||
}
|
||||
if (isset($args['expire'])) {
|
||||
$args['expiration'] = $args['expire']; // back compatibility
|
||||
}
|
||||
$helper->dependencies[Cache::TAGS] = $args['tags'] ?? null;
|
||||
$helper->dependencies[Cache::EXPIRATION] = $args['expiration'] ?? '+ 7 days';
|
||||
$helper->end();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Nette\Caching\OutputHelper[] $parents
|
||||
*/
|
||||
public static function rollback(array &$parents): void
|
||||
{
|
||||
$helper = array_pop($parents);
|
||||
if ($helper instanceof Nette\Caching\OutputHelper) {
|
||||
if (isset($args['dependencies'])) {
|
||||
$args += $args['dependencies']();
|
||||
}
|
||||
if (isset($args['expire'])) {
|
||||
$args['expiration'] = $args['expire']; // back compatibility
|
||||
}
|
||||
$helper->dependencies[Cache::TAGS] = $args['tags'] ?? null;
|
||||
$helper->dependencies[Cache::EXPIRATION] = $args['expiration'] ?? '+ 7 days';
|
||||
$helper->end();
|
||||
$helper->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+4
-2
@@ -13,12 +13,14 @@ namespace Nette\Caching;
|
||||
/**
|
||||
* Cache storage with a bulk read support.
|
||||
*/
|
||||
interface IBulkReader
|
||||
interface BulkReader
|
||||
{
|
||||
|
||||
/**
|
||||
* Reads from cache in bulk.
|
||||
* @return array key => value pairs, missing items are omitted
|
||||
*/
|
||||
function bulkRead(array $keys): array;
|
||||
}
|
||||
|
||||
|
||||
class_exists(IBulkReader::class);
|
||||
+50
-36
@@ -36,14 +36,14 @@ class Cache
|
||||
/** @internal */
|
||||
public const NAMESPACE_SEPARATOR = "\x00";
|
||||
|
||||
/** @var IStorage */
|
||||
/** @var Storage */
|
||||
private $storage;
|
||||
|
||||
/** @var string */
|
||||
private $namespace;
|
||||
|
||||
|
||||
public function __construct(IStorage $storage, string $namespace = null)
|
||||
public function __construct(Storage $storage, string $namespace = null)
|
||||
{
|
||||
$this->storage = $storage;
|
||||
$this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
|
||||
@@ -53,7 +53,7 @@ class Cache
|
||||
/**
|
||||
* Returns cache storage.
|
||||
*/
|
||||
final public function getStorage(): IStorage
|
||||
final public function getStorage(): Storage
|
||||
{
|
||||
return $this->storage;
|
||||
}
|
||||
@@ -74,8 +74,7 @@ class Cache
|
||||
*/
|
||||
public function derive(string $namespace)
|
||||
{
|
||||
$derived = new static($this->storage, $this->namespace . $namespace);
|
||||
return $derived;
|
||||
return new static($this->storage, $this->namespace . $namespace);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,13 +83,19 @@ class Cache
|
||||
* @param mixed $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function load($key, callable $fallback = null)
|
||||
public function load($key, callable $generator = null)
|
||||
{
|
||||
$data = $this->storage->read($this->generateKey($key));
|
||||
if ($data === null && $fallback) {
|
||||
return $this->save($key, function (&$dependencies) use ($fallback) {
|
||||
return $fallback(...[&$dependencies]);
|
||||
});
|
||||
$storageKey = $this->generateKey($key);
|
||||
$data = $this->storage->read($storageKey);
|
||||
if ($data === null && $generator) {
|
||||
$this->storage->lock($storageKey);
|
||||
try {
|
||||
$data = $generator(...[&$dependencies]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->storage->remove($storageKey);
|
||||
throw $e;
|
||||
}
|
||||
$this->save($key, $data, $dependencies);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
@@ -99,7 +104,7 @@ class Cache
|
||||
/**
|
||||
* Reads multiple items from the cache.
|
||||
*/
|
||||
public function bulkLoad(array $keys, callable $fallback = null): array
|
||||
public function bulkLoad(array $keys, callable $generator = null): array
|
||||
{
|
||||
if (count($keys) === 0) {
|
||||
return [];
|
||||
@@ -109,30 +114,31 @@ class Cache
|
||||
throw new Nette\InvalidArgumentException('Only scalar keys are allowed in bulkLoad()');
|
||||
}
|
||||
}
|
||||
$storageKeys = array_map([$this, 'generateKey'], $keys);
|
||||
if (!$this->storage instanceof IBulkReader) {
|
||||
$result = array_combine($keys, array_map([$this->storage, 'read'], $storageKeys));
|
||||
if ($fallback !== null) {
|
||||
foreach ($result as $key => $value) {
|
||||
if ($value === null) {
|
||||
$result[$key] = $this->save($key, function (&$dependencies) use ($key, $fallback) {
|
||||
return $fallback(...[$key, &$dependencies]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
if (!$this->storage instanceof BulkReader) {
|
||||
foreach ($keys as $key) {
|
||||
$result[$key] = $this->load(
|
||||
$key,
|
||||
$generator
|
||||
? function (&$dependencies) use ($key, $generator) {
|
||||
return $generator(...[$key, &$dependencies]);
|
||||
}
|
||||
: null
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
$storageKeys = array_map([$this, 'generateKey'], $keys);
|
||||
$cacheData = $this->storage->bulkRead($storageKeys);
|
||||
$result = [];
|
||||
foreach ($keys as $i => $key) {
|
||||
$storageKey = $storageKeys[$i];
|
||||
if (isset($cacheData[$storageKey])) {
|
||||
$result[$key] = $cacheData[$storageKey];
|
||||
} elseif ($fallback) {
|
||||
$result[$key] = $this->save($key, function (&$dependencies) use ($key, $fallback) {
|
||||
return $fallback(...[$key, &$dependencies]);
|
||||
} elseif ($generator) {
|
||||
$result[$key] = $this->load($key, function (&$dependencies) use ($key, $generator) {
|
||||
return $generator(...[$key, &$dependencies]);
|
||||
});
|
||||
} else {
|
||||
$result[$key] = null;
|
||||
@@ -206,7 +212,7 @@ class Cache
|
||||
// convert FILES into CALLBACKS
|
||||
if (isset($dp[self::FILES])) {
|
||||
foreach (array_unique((array) $dp[self::FILES]) as $item) {
|
||||
$dp[self::CALLBACKS][] = [[__CLASS__, 'checkFile'], $item, @filemtime($item) ?: null]; // @ - stat may fail
|
||||
$dp[self::CALLBACKS][] = [[self::class, 'checkFile'], $item, @filemtime($item) ?: null]; // @ - stat may fail
|
||||
}
|
||||
unset($dp[self::FILES]);
|
||||
}
|
||||
@@ -219,7 +225,7 @@ class Cache
|
||||
// convert CONSTS into CALLBACKS
|
||||
if (isset($dp[self::CONSTS])) {
|
||||
foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
|
||||
$dp[self::CALLBACKS][] = [[__CLASS__, 'checkConst'], $item, constant($item)];
|
||||
$dp[self::CALLBACKS][] = [[self::class, 'checkConst'], $item, constant($item)];
|
||||
}
|
||||
unset($dp[self::CONSTS]);
|
||||
}
|
||||
@@ -280,15 +286,14 @@ class Cache
|
||||
public function wrap(callable $function, array $dependencies = null): \Closure
|
||||
{
|
||||
return function () use ($function, $dependencies) {
|
||||
$key = [$function, func_get_args()];
|
||||
$key = [$function, $args = func_get_args()];
|
||||
if (is_array($function) && is_object($function[0])) {
|
||||
$key[0][0] = get_class($function[0]);
|
||||
}
|
||||
$data = $this->load($key);
|
||||
if ($data === null) {
|
||||
$data = $this->save($key, $function(...$key[1]), $dependencies);
|
||||
}
|
||||
return $data;
|
||||
return $this->load($key, function (&$deps) use ($function, $args, $dependencies) {
|
||||
$deps = $dependencies;
|
||||
return $function(...$args);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -297,7 +302,7 @@ class Cache
|
||||
* Starts the output cache.
|
||||
* @param mixed $key
|
||||
*/
|
||||
public function start($key): ?OutputHelper
|
||||
public function capture($key): ?OutputHelper
|
||||
{
|
||||
$data = $this->load($key);
|
||||
if ($data === null) {
|
||||
@@ -308,6 +313,15 @@ class Cache
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use capture()
|
||||
*/
|
||||
public function start($key): ?OutputHelper
|
||||
{
|
||||
return $this->capture($key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates internal cache key.
|
||||
*/
|
||||
|
||||
@@ -48,4 +48,14 @@ class OutputHelper
|
||||
$this->cache->save($this->key, ob_get_flush(), $dependencies + $this->dependencies);
|
||||
$this->cache = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops and throws away the output.
|
||||
*/
|
||||
public function rollback(): void
|
||||
{
|
||||
ob_end_flush();
|
||||
$this->cache = null;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+4
-2
@@ -13,9 +13,8 @@ namespace Nette\Caching;
|
||||
/**
|
||||
* Cache storage.
|
||||
*/
|
||||
interface IStorage
|
||||
interface Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* Read from cache.
|
||||
* @return mixed
|
||||
@@ -42,3 +41,6 @@ interface IStorage
|
||||
*/
|
||||
function clean(array $conditions): void;
|
||||
}
|
||||
|
||||
|
||||
class_exists(IStorage::class);
|
||||
@@ -15,7 +15,7 @@ use Nette;
|
||||
/**
|
||||
* Cache dummy storage.
|
||||
*/
|
||||
class DevNullStorage implements Nette\Caching\IStorage
|
||||
class DevNullStorage implements Nette\Caching\Storage
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
|
||||
+27
-28
@@ -16,7 +16,7 @@ use Nette\Caching\Cache;
|
||||
/**
|
||||
* Cache file storage.
|
||||
*/
|
||||
class FileStorage implements Nette\Caching\IStorage
|
||||
class FileStorage implements Nette\Caching\Storage
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
@@ -56,14 +56,14 @@ class FileStorage implements Nette\Caching\IStorage
|
||||
/** @var string */
|
||||
private $dir;
|
||||
|
||||
/** @var IJournal */
|
||||
/** @var Journal */
|
||||
private $journal;
|
||||
|
||||
/** @var array */
|
||||
private $locks;
|
||||
|
||||
|
||||
public function __construct(string $dir, IJournal $journal = null)
|
||||
public function __construct(string $dir, Journal $journal = null)
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
throw new Nette\DirectoryNotFoundException("Directory '$dir' not found.");
|
||||
@@ -81,12 +81,9 @@ class FileStorage implements Nette\Caching\IStorage
|
||||
public function read(string $key)
|
||||
{
|
||||
$meta = $this->readMetaAndLock($this->getCacheFile($key), LOCK_SH);
|
||||
if ($meta && $this->verify($meta)) {
|
||||
return $this->readData($meta); // calls fclose()
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return $meta && $this->verify($meta)
|
||||
? $this->readData($meta) // calls fclose()
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -135,10 +132,12 @@ class FileStorage implements Nette\Caching\IStorage
|
||||
@mkdir($dir); // @ - directory may already exist
|
||||
}
|
||||
$handle = fopen($cacheFile, 'c+b');
|
||||
if ($handle) {
|
||||
$this->locks[$key] = $handle;
|
||||
flock($handle, LOCK_EX);
|
||||
if (!$handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->locks[$key] = $handle;
|
||||
flock($handle, LOCK_EX);
|
||||
}
|
||||
|
||||
|
||||
@@ -272,12 +271,14 @@ class FileStorage implements Nette\Caching\IStorage
|
||||
} elseif ($namespaces) {
|
||||
foreach ($namespaces as $namespace) {
|
||||
$dir = $this->dir . '/_' . urlencode($namespace);
|
||||
if (is_dir($dir)) {
|
||||
foreach (Nette\Utils\Finder::findFiles('_*')->in($dir) as $entry) {
|
||||
$this->delete((string) $entry);
|
||||
}
|
||||
@rmdir($dir); // may already contain new files
|
||||
if (!is_dir($dir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Nette\Utils\Finder::findFiles('_*')->in($dir) as $entry) {
|
||||
$this->delete((string) $entry);
|
||||
}
|
||||
@rmdir($dir); // may already contain new files
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,11 +328,7 @@ class FileStorage implements Nette\Caching\IStorage
|
||||
flock($meta[self::HANDLE], LOCK_UN);
|
||||
fclose($meta[self::HANDLE]);
|
||||
|
||||
if (empty($meta[self::META_SERIALIZED])) {
|
||||
return $data;
|
||||
} else {
|
||||
return unserialize($data);
|
||||
}
|
||||
return empty($meta[self::META_SERIALIZED]) ? $data : unserialize($data);
|
||||
}
|
||||
|
||||
|
||||
@@ -365,12 +362,14 @@ class FileStorage implements Nette\Caching\IStorage
|
||||
if (!$handle) {
|
||||
$handle = @fopen($file, 'r+'); // @ - file may not exist
|
||||
}
|
||||
if ($handle) {
|
||||
flock($handle, LOCK_EX);
|
||||
ftruncate($handle, 0);
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
@unlink($file); // @ - file may not already exist
|
||||
if (!$handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
flock($handle, LOCK_EX);
|
||||
ftruncate($handle, 0);
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
@unlink($file); // @ - file may not already exist
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -13,9 +13,8 @@ namespace Nette\Caching\Storages;
|
||||
/**
|
||||
* Cache journal provider.
|
||||
*/
|
||||
interface IJournal
|
||||
interface Journal
|
||||
{
|
||||
|
||||
/**
|
||||
* Writes entry information into the journal.
|
||||
*/
|
||||
@@ -27,3 +26,6 @@ interface IJournal
|
||||
*/
|
||||
function clean(array $conditions): ?array;
|
||||
}
|
||||
|
||||
|
||||
class_exists(IJournal::class);
|
||||
@@ -16,7 +16,7 @@ use Nette\Caching\Cache;
|
||||
/**
|
||||
* Memcached storage using memcached extension.
|
||||
*/
|
||||
class MemcachedStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
|
||||
class MemcachedStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
@@ -32,7 +32,7 @@ class MemcachedStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkRea
|
||||
/** @var string */
|
||||
private $prefix;
|
||||
|
||||
/** @var IJournal */
|
||||
/** @var Journal */
|
||||
private $journal;
|
||||
|
||||
|
||||
@@ -45,8 +45,12 @@ class MemcachedStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkRea
|
||||
}
|
||||
|
||||
|
||||
public function __construct(string $host = 'localhost', int $port = 11211, string $prefix = '', IJournal $journal = null)
|
||||
{
|
||||
public function __construct(
|
||||
string $host = 'localhost',
|
||||
int $port = 11211,
|
||||
string $prefix = '',
|
||||
Journal $journal = null
|
||||
) {
|
||||
if (!static::isAvailable()) {
|
||||
throw new Nette\NotSupportedException("PHP extension 'memcached' is not loaded.");
|
||||
}
|
||||
@@ -62,7 +66,7 @@ class MemcachedStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkRea
|
||||
|
||||
public function addServer(string $host = 'localhost', int $port = 11211): void
|
||||
{
|
||||
if ($this->memcached->addServer($host, $port, 1) === false) {
|
||||
if (@$this->memcached->addServer($host, $port, 1) === false) { // @ is escalated to exception
|
||||
$error = error_get_last();
|
||||
throw new Nette\InvalidStateException("Memcached::addServer(): $error[message].");
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use Nette;
|
||||
/**
|
||||
* Memory cache storage.
|
||||
*/
|
||||
class MemoryStorage implements Nette\Caching\IStorage
|
||||
class MemoryStorage implements Nette\Caching\Storage
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use Nette\Caching\Cache;
|
||||
/**
|
||||
* SQLite based journal.
|
||||
*/
|
||||
class SQLiteJournal implements IJournal
|
||||
class SQLiteJournal implements Journal
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
|
||||
+14
-8
@@ -16,7 +16,7 @@ use Nette\Caching\Cache;
|
||||
/**
|
||||
* SQLite storage.
|
||||
*/
|
||||
class SQLiteStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
|
||||
class SQLiteStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
@@ -56,12 +56,14 @@ class SQLiteStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT data, slide FROM cache WHERE key=? AND (expire IS NULL OR expire >= ?)');
|
||||
$stmt->execute([$key, time()]);
|
||||
if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
if ($row['slide'] !== null) {
|
||||
$this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key=?')->execute([time(), $key]);
|
||||
}
|
||||
return unserialize($row['data']);
|
||||
if (!$row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($row['slide'] !== null) {
|
||||
$this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key=?')->execute([time(), $key]);
|
||||
}
|
||||
return unserialize($row['data']);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +94,12 @@ class SQLiteStorage implements Nette\Caching\IStorage, Nette\Caching\IBulkReader
|
||||
|
||||
public function write(string $key, $data, array $dependencies): void
|
||||
{
|
||||
$expire = isset($dependencies[Cache::EXPIRATION]) ? $dependencies[Cache::EXPIRATION] + time() : null;
|
||||
$slide = isset($dependencies[Cache::SLIDING]) ? $dependencies[Cache::EXPIRATION] : null;
|
||||
$expire = isset($dependencies[Cache::EXPIRATION])
|
||||
? $dependencies[Cache::EXPIRATION] + time()
|
||||
: null;
|
||||
$slide = isset($dependencies[Cache::SLIDING])
|
||||
? $dependencies[Cache::EXPIRATION]
|
||||
: null;
|
||||
|
||||
$this->pdo->exec('BEGIN TRANSACTION');
|
||||
$this->pdo->prepare('REPLACE INTO cache (key, data, expire, slide) VALUES (?, ?, ?, ?)')
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?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\Caching;
|
||||
|
||||
if (false) {
|
||||
/** @deprecated use Nette\Caching\BulkReader */
|
||||
interface IBulkReader extends BulkReader
|
||||
{
|
||||
}
|
||||
} elseif (!interface_exists(IBulkReader::class)) {
|
||||
class_alias(BulkReader::class, IBulkReader::class);
|
||||
}
|
||||
|
||||
if (false) {
|
||||
/** @deprecated use Nette\Caching\Storage */
|
||||
interface IStorage extends Storage
|
||||
{
|
||||
}
|
||||
} elseif (!interface_exists(IStorage::class)) {
|
||||
class_alias(Storage::class, IStorage::class);
|
||||
}
|
||||
|
||||
namespace Nette\Caching\Storages;
|
||||
|
||||
if (false) {
|
||||
/** @deprecated use Nette\Caching\Storages\Journal */
|
||||
interface IJournal extends Journal
|
||||
{
|
||||
}
|
||||
} elseif (!interface_exists(IJournal::class)) {
|
||||
class_alias(Journal::class, IJournal::class);
|
||||
}
|
||||
Vendored
+7
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
|
||||
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
|
||||
"keywords": ["nette", "images", "json", "password", "validation", "utility", "string", "array", "core", "slugify", "utf-8", "unicode", "paginator", "datetime"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
|
||||
@@ -15,13 +15,16 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2 <8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "~2.0",
|
||||
"tracy/tracy": "^2.3",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/di": "<3.0.6"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
|
||||
"ext-json": "to use Nette\\Utils\\Json",
|
||||
@@ -36,12 +39,12 @@
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse --level 5 --configuration tests/phpstan.neon src",
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.1-dev"
|
||||
"dev-master": "3.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+20
-9
@@ -2,7 +2,7 @@ Nette Utility Classes
|
||||
=====================
|
||||
|
||||
[](https://packagist.org/packages/nette/utils)
|
||||
[](https://travis-ci.org/nette/utils)
|
||||
[](https://github.com/nette/utils/actions)
|
||||
[](https://coveralls.io/github/nette/utils?branch=master)
|
||||
[](https://github.com/nette/utils/releases)
|
||||
[](https://github.com/nette/utils/blob/master/license.md)
|
||||
@@ -11,23 +11,23 @@ Nette Utility Classes
|
||||
Introduction
|
||||
------------
|
||||
|
||||
In package nette/utils you will find a set of useful classes for everyday use:
|
||||
In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use:
|
||||
|
||||
- [Arrays](https://doc.nette.org/arrays) - manipulate arrays
|
||||
- [Callback](https://doc.nette.org/callback) - PHP callbacks
|
||||
- [Date and Time](https://doc.nette.org/datetime) - modify times and dates
|
||||
- [Filesystem](https://doc.nette.org/filesystem) - copying, renaming, …
|
||||
- [Helper Functions](https://doc.nette.org/helpers)
|
||||
- [HTML elements](https://doc.nette.org/html-elements) - generate HTML
|
||||
- [Images](https://doc.nette.org/images) - crop, resize, rotate images
|
||||
- [JSON](https://doc.nette.org/json) - encoding and decoding
|
||||
- [Generating Random Strings](https://doc.nette.org/random)
|
||||
- [Pagination](https://doc.nette.org/pagination) - comfort pagination
|
||||
- [Strings](https://doc.nette.org/strings) - useful text transpilers
|
||||
- [SmartObject](https://doc.nette.org/smartobject) - PHP Object Enhancements
|
||||
- [Paginator](https://doc.nette.org/paginator) - pagination math
|
||||
- [PHP Reflection](https://doc.nette.org/reflection)
|
||||
- [Strings](https://doc.nette.org/strings) - useful text functions
|
||||
- [SmartObject](https://doc.nette.org/smartobject) - PHP object enhancements
|
||||
- [Validation](https://doc.nette.org/validators) - validate inputs
|
||||
|
||||
Documentation can be found on the [website](https://doc.nette.org/utils).
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -38,5 +38,16 @@ The recommended way to install is via Composer:
|
||||
composer require nette/utils
|
||||
```
|
||||
|
||||
- Nette Utils 3.0 is compatible with PHP 7.1 to 7.4
|
||||
- Nette Utils 2.5 is compatible with PHP 5.6 to 7.4
|
||||
- Nette Utils 3.2 is compatible with PHP 7.2 to 8.0
|
||||
- Nette Utils 3.1 is compatible with PHP 7.1 to 8.0
|
||||
- Nette Utils 3.0 is compatible with PHP 7.1 to 8.0
|
||||
- Nette Utils 2.5 is compatible with PHP 5.6 to 8.0
|
||||
|
||||
[Support Me](https://github.com/sponsors/dg)
|
||||
--------------------------------------------
|
||||
|
||||
Do you like Nette Utils? Are you looking forward to the new features?
|
||||
|
||||
[](https://github.com/sponsors/dg)
|
||||
|
||||
Thank you!
|
||||
|
||||
+5
-2
@@ -7,13 +7,16 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
namespace Nette;
|
||||
|
||||
|
||||
interface IHtmlString
|
||||
interface HtmlStringable
|
||||
{
|
||||
/**
|
||||
* Returns string in HTML format
|
||||
*/
|
||||
function __toString(): string;
|
||||
}
|
||||
|
||||
|
||||
interface_exists(Utils\IHtmlString::class);
|
||||
+1
-1
@@ -47,7 +47,7 @@ class CachingIterator extends \CachingIterator implements \Countable
|
||||
} elseif ($iterator instanceof \Traversable) {
|
||||
$iterator = new \IteratorIterator($iterator);
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', __CLASS__, is_object($iterator) ? get_class($iterator) : gettype($iterator)));
|
||||
throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, is_object($iterator) ? get_class($iterator) : gettype($iterator)));
|
||||
}
|
||||
|
||||
parent::__construct($iterator, 0);
|
||||
|
||||
+10
-11
@@ -22,20 +22,20 @@ use Nette\Utils\ObjectHelpers;
|
||||
trait SmartObject
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$class = get_class($this);
|
||||
$class = static::class;
|
||||
|
||||
if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers
|
||||
if (is_iterable($this->$name)) {
|
||||
foreach ($this->$name as $handler) {
|
||||
$handlers = $this->$name ?? null;
|
||||
if (is_iterable($handlers)) {
|
||||
foreach ($handlers as $handler) {
|
||||
$handler(...$args);
|
||||
}
|
||||
} elseif ($this->$name !== null) {
|
||||
throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($this->$name) . ' given.');
|
||||
} elseif ($handlers !== null) {
|
||||
throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.');
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -45,7 +45,6 @@ trait SmartObject
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args)
|
||||
@@ -60,7 +59,7 @@ trait SmartObject
|
||||
*/
|
||||
public function &__get(string $name)
|
||||
{
|
||||
$class = get_class($this);
|
||||
$class = static::class;
|
||||
|
||||
if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter
|
||||
if (!($prop & 0b0001)) {
|
||||
@@ -86,7 +85,7 @@ trait SmartObject
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
$class = get_class($this);
|
||||
$class = static::class;
|
||||
|
||||
if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property
|
||||
$this->$name = $value;
|
||||
@@ -109,7 +108,7 @@ trait SmartObject
|
||||
*/
|
||||
public function __unset(string $name)
|
||||
{
|
||||
$class = get_class($this);
|
||||
$class = static::class;
|
||||
if (!ObjectHelpers::hasProperty($class, $name)) {
|
||||
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
|
||||
}
|
||||
@@ -118,6 +117,6 @@ trait SmartObject
|
||||
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return isset(ObjectHelpers::getMagicProperties(get_class($this))[$name]);
|
||||
return isset(ObjectHelpers::getMagicProperties(static::class)[$name]);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ trait StaticClass
|
||||
/** @throws \Error */
|
||||
final public function __construct()
|
||||
{
|
||||
throw new \Error('Class ' . get_class($this) . ' is static and cannot be instantiated.');
|
||||
throw new \Error('Class ' . static::class . ' is static and cannot be instantiated.');
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -13,7 +13,7 @@ namespace Nette\Localization;
|
||||
/**
|
||||
* Translator adapter.
|
||||
*/
|
||||
interface ITranslator
|
||||
interface Translator
|
||||
{
|
||||
/**
|
||||
* Translates the given string.
|
||||
@@ -22,3 +22,6 @@ interface ITranslator
|
||||
*/
|
||||
function translate($message, ...$parameters): string;
|
||||
}
|
||||
|
||||
|
||||
interface_exists(Nette\Localization\ITranslator::class);
|
||||
+9
-8
@@ -17,16 +17,17 @@ use Nette;
|
||||
*/
|
||||
class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
/** @return static */
|
||||
public static function from(array $arr, bool $recursive = true)
|
||||
/**
|
||||
* Transforms array to ArrayHash.
|
||||
* @return static
|
||||
*/
|
||||
public static function from(array $array, bool $recursive = true)
|
||||
{
|
||||
$obj = new static;
|
||||
foreach ($arr as $key => $value) {
|
||||
if ($recursive && is_array($value)) {
|
||||
$obj->$key = static::from($value, true);
|
||||
} else {
|
||||
$obj->$key = $value;
|
||||
}
|
||||
foreach ($array as $key => $value) {
|
||||
$obj->$key = $recursive && is_array($value)
|
||||
? static::from($value, true)
|
||||
: $value;
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
+184
-74
@@ -21,17 +21,17 @@ class Arrays
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Returns item from array or $default if item is not set.
|
||||
* @param string|int|array $key one or more keys
|
||||
* Returns item from array. If it does not exist, it throws an exception, unless a default value is set.
|
||||
* @param string|int|array $key one or more keys
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function get(array $arr, $key, $default = null)
|
||||
public static function get(array $array, $key, $default = null)
|
||||
{
|
||||
foreach (is_array($key) ? $key : [$key] as $k) {
|
||||
if (is_array($arr) && array_key_exists($k, $arr)) {
|
||||
$arr = $arr[$k];
|
||||
if (is_array($array) && array_key_exists($k, $array)) {
|
||||
$array = $array[$k];
|
||||
} else {
|
||||
if (func_num_args() < 3) {
|
||||
throw new Nette\InvalidArgumentException("Missing item '$k'.");
|
||||
@@ -39,38 +39,40 @@ class Arrays
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns reference to array item.
|
||||
* @param string|int|array $key one or more keys
|
||||
* Returns reference to array item. If the index does not exist, new one is created with value null.
|
||||
* @param string|int|array $key one or more keys
|
||||
* @return mixed
|
||||
* @throws Nette\InvalidArgumentException if traversed item is not an array
|
||||
*/
|
||||
public static function &getRef(array &$arr, $key)
|
||||
public static function &getRef(array &$array, $key)
|
||||
{
|
||||
foreach (is_array($key) ? $key : [$key] as $k) {
|
||||
if (is_array($arr) || $arr === null) {
|
||||
$arr = &$arr[$k];
|
||||
if (is_array($array) || $array === null) {
|
||||
$array = &$array[$k];
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Traversed item is not an array.');
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively appends elements of remaining keys from the second array to the first.
|
||||
* Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
|
||||
* the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
|
||||
* the value from the first array in the case of a key collision.
|
||||
*/
|
||||
public static function mergeTree(array $arr1, array $arr2): array
|
||||
public static function mergeTree(array $array1, array $array2): array
|
||||
{
|
||||
$res = $arr1 + $arr2;
|
||||
foreach (array_intersect_key($arr1, $arr2) as $k => $v) {
|
||||
if (is_array($v) && is_array($arr2[$k])) {
|
||||
$res[$k] = self::mergeTree($v, $arr2[$k]);
|
||||
$res = $array1 + $array2;
|
||||
foreach (array_intersect_key($array1, $array2) as $k => $v) {
|
||||
if (is_array($v) && is_array($array2[$k])) {
|
||||
$res[$k] = self::mergeTree($v, $array2[$k]);
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
@@ -78,37 +80,82 @@ class Arrays
|
||||
|
||||
|
||||
/**
|
||||
* Searches the array for a given key and returns the offset if successful.
|
||||
* Returns zero-indexed position of given array key. Returns null if key is not found.
|
||||
* @param string|int $key
|
||||
* @return int|null offset if it is found, null otherwise
|
||||
*/
|
||||
public static function searchKey(array $arr, $key): ?int
|
||||
public static function getKeyOffset(array $array, $key): ?int
|
||||
{
|
||||
$foo = [$key => null];
|
||||
return Helpers::falseToNull(array_search(key($foo), array_keys($arr), true));
|
||||
return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts new array before item specified by key.
|
||||
* @param string|int $key
|
||||
* @deprecated use getKeyOffset()
|
||||
*/
|
||||
public static function insertBefore(array &$arr, $key, array $inserted): void
|
||||
public static function searchKey(array $array, $key): ?int
|
||||
{
|
||||
$offset = (int) self::searchKey($arr, $key);
|
||||
$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
|
||||
return self::getKeyOffset($array, $key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts new array after item specified by key.
|
||||
* @param string|int $key
|
||||
* Tests an array for the presence of value.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function insertAfter(array &$arr, $key, array $inserted): void
|
||||
public static function contains(array $array, $value): bool
|
||||
{
|
||||
$offset = self::searchKey($arr, $key);
|
||||
$offset = $offset === null ? count($arr) : $offset + 1;
|
||||
$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
|
||||
return in_array($value, $array, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first item from the array or null if array is empty.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function first(array $array)
|
||||
{
|
||||
return count($array) ? reset($array) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last item from the array or null if array is empty.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function last(array $array)
|
||||
{
|
||||
return count($array) ? end($array) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array immediately after the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the beginning.
|
||||
* @param string|int|null $key
|
||||
*/
|
||||
public static function insertBefore(array &$array, $key, array $inserted): void
|
||||
{
|
||||
$offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
|
||||
$array = array_slice($array, 0, $offset, true)
|
||||
+ $inserted
|
||||
+ array_slice($array, $offset, count($array), true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array before the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the end.
|
||||
* @param string|int|null $key
|
||||
*/
|
||||
public static function insertAfter(array &$array, $key, array $inserted): void
|
||||
{
|
||||
if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
|
||||
$offset = count($array) - 1;
|
||||
}
|
||||
$array = array_slice($array, 0, $offset + 1, true)
|
||||
+ $inserted
|
||||
+ array_slice($array, $offset + 1, count($array), true);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,42 +164,47 @@ class Arrays
|
||||
* @param string|int $oldKey
|
||||
* @param string|int $newKey
|
||||
*/
|
||||
public static function renameKey(array &$arr, $oldKey, $newKey): void
|
||||
public static function renameKey(array &$array, $oldKey, $newKey): bool
|
||||
{
|
||||
$offset = self::searchKey($arr, $oldKey);
|
||||
if ($offset !== null) {
|
||||
$keys = array_keys($arr);
|
||||
$keys[$offset] = $newKey;
|
||||
$arr = array_combine($keys, $arr);
|
||||
$offset = self::getKeyOffset($array, $oldKey);
|
||||
if ($offset === null) {
|
||||
return false;
|
||||
}
|
||||
$val = &$array[$oldKey];
|
||||
$keys = array_keys($array);
|
||||
$keys[$offset] = $newKey;
|
||||
$array = array_combine($keys, $array);
|
||||
$array[$newKey] = &$val;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns array entries that match the pattern.
|
||||
* Returns only those array items, which matches a regular expression $pattern.
|
||||
* @throws Nette\RegexpException on compilation or runtime error
|
||||
*/
|
||||
public static function grep(array $arr, string $pattern, int $flags = 0): array
|
||||
public static function grep(array $array, string $pattern, int $flags = 0): array
|
||||
{
|
||||
return Strings::pcre('preg_grep', [$pattern, $arr, $flags]);
|
||||
return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns flattened array.
|
||||
* Transforms multidimensional array to flat array.
|
||||
*/
|
||||
public static function flatten(array $arr, bool $preserveKeys = false): array
|
||||
public static function flatten(array $array, bool $preserveKeys = false): array
|
||||
{
|
||||
$res = [];
|
||||
$cb = $preserveKeys
|
||||
? function ($v, $k) use (&$res): void { $res[$k] = $v; }
|
||||
: function ($v) use (&$res): void { $res[] = $v; };
|
||||
array_walk_recursive($arr, $cb);
|
||||
array_walk_recursive($array, $cb);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a variable is a zero-based integer indexed array.
|
||||
* Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isList($value): bool
|
||||
@@ -166,7 +218,7 @@ class Arrays
|
||||
* @param string|string[] $path
|
||||
* @return array|\stdClass
|
||||
*/
|
||||
public static function associate(array $arr, $path)
|
||||
public static function associate(array $array, $path)
|
||||
{
|
||||
$parts = is_array($path)
|
||||
? $path
|
||||
@@ -178,7 +230,7 @@ class Arrays
|
||||
|
||||
$res = $parts[0] === '->' ? new \stdClass : [];
|
||||
|
||||
foreach ($arr as $rowOrig) {
|
||||
foreach ($array as $rowOrig) {
|
||||
$row = (array) $rowOrig;
|
||||
$x = &$res;
|
||||
|
||||
@@ -218,13 +270,13 @@ class Arrays
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes to associative array.
|
||||
* Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
|
||||
* @param mixed $filling
|
||||
*/
|
||||
public static function normalize(array $arr, $filling = null): array
|
||||
public static function normalize(array $array, $filling = null): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($arr as $k => $v) {
|
||||
foreach ($array as $k => $v) {
|
||||
$res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
|
||||
}
|
||||
return $res;
|
||||
@@ -232,17 +284,18 @@ class Arrays
|
||||
|
||||
|
||||
/**
|
||||
* Picks element from the array by key and return its value.
|
||||
* Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
|
||||
* or returns $default, if provided.
|
||||
* @param string|int $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function pick(array &$arr, $key, $default = null)
|
||||
public static function pick(array &$array, $key, $default = null)
|
||||
{
|
||||
if (array_key_exists($key, $arr)) {
|
||||
$value = $arr[$key];
|
||||
unset($arr[$key]);
|
||||
if (array_key_exists($key, $array)) {
|
||||
$value = $array[$key];
|
||||
unset($array[$key]);
|
||||
return $value;
|
||||
|
||||
} elseif (func_num_args() < 3) {
|
||||
@@ -255,12 +308,13 @@ class Arrays
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether some element in the array passes the callback test.
|
||||
* Tests whether at least one element in the array passes the test implemented by the
|
||||
* provided callback with signature `function ($value, $key, array $array): bool`.
|
||||
*/
|
||||
public static function some(array $arr, callable $callback): bool
|
||||
public static function some(iterable $array, callable $callback): bool
|
||||
{
|
||||
foreach ($arr as $k => $v) {
|
||||
if ($callback($v, $k, $arr)) {
|
||||
foreach ($array as $k => $v) {
|
||||
if ($callback($v, $k, $array)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -269,12 +323,13 @@ class Arrays
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether all elements in the array pass the callback test.
|
||||
* Tests whether all elements in the array pass the test implemented by the provided function,
|
||||
* which has the signature `function ($value, $key, array $array): bool`.
|
||||
*/
|
||||
public static function every(array $arr, callable $callback): bool
|
||||
public static function every(iterable $array, callable $callback): bool
|
||||
{
|
||||
foreach ($arr as $k => $v) {
|
||||
if (!$callback($v, $k, $arr)) {
|
||||
foreach ($array as $k => $v) {
|
||||
if (!$callback($v, $k, $array)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -283,28 +338,83 @@ class Arrays
|
||||
|
||||
|
||||
/**
|
||||
* Applies the callback to the elements of the array.
|
||||
* Calls $callback on all elements in the array and returns the array of return values.
|
||||
* The callback has the signature `function ($value, $key, array $array): bool`.
|
||||
*/
|
||||
public static function map(array $arr, callable $callback): array
|
||||
public static function map(iterable $array, callable $callback): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($arr as $k => $v) {
|
||||
$res[$k] = $callback($v, $k, $arr);
|
||||
foreach ($array as $k => $v) {
|
||||
$res[$k] = $callback($v, $k, $array);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts array to object
|
||||
* @param object $obj
|
||||
* Invokes all callbacks and returns array of results.
|
||||
* @param callable[] $callbacks
|
||||
*/
|
||||
public static function invoke(iterable $callbacks, ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($callbacks as $k => $cb) {
|
||||
$res[$k] = $cb(...$args);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes method on every object in an array and returns array of results.
|
||||
* @param object[] $objects
|
||||
*/
|
||||
public static function invokeMethod(iterable $objects, string $method, ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($objects as $k => $obj) {
|
||||
$res[$k] = $obj->$method(...$args);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies the elements of the $array array to the $object object and then returns it.
|
||||
* @param object $object
|
||||
* @return object
|
||||
*/
|
||||
public static function toObject(array $arr, $obj)
|
||||
public static function toObject(iterable $array, $object)
|
||||
{
|
||||
foreach ($arr as $k => $v) {
|
||||
$obj->$k = $v;
|
||||
foreach ($array as $k => $v) {
|
||||
$object->$k = $v;
|
||||
}
|
||||
return $obj;
|
||||
return $object;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts value to array key.
|
||||
* @param mixed $value
|
||||
* @return int|string
|
||||
*/
|
||||
public static function toKey($value)
|
||||
{
|
||||
return key([$value => null]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns copy of the $array where every item is converted to string
|
||||
* and prefixed by $prefix and suffixed by $suffix.
|
||||
* @return string[]
|
||||
*/
|
||||
public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($array as $k => $v) {
|
||||
$res[$k] = $prefix . $v . $suffix;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-7
@@ -26,6 +26,7 @@ final class Callback
|
||||
*/
|
||||
public static function closure($callable, string $method = null): \Closure
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use Closure::fromCallable().', E_USER_DEPRECATED);
|
||||
try {
|
||||
return \Closure::fromCallable($method === null ? $callable : [$callable, $method]);
|
||||
} catch (\TypeError $e) {
|
||||
@@ -68,8 +69,10 @@ final class Callback
|
||||
{
|
||||
$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
|
||||
if ($file === __FILE__) {
|
||||
$msg = ini_get('html_errors') ? Html::htmlToText($message) : $message;
|
||||
$msg = preg_replace("#^$function\(.*?\): #", '', $msg);
|
||||
$msg = ini_get('html_errors')
|
||||
? Html::htmlToText($message)
|
||||
: $message;
|
||||
$msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
|
||||
if ($onError($msg, $severity) !== false) {
|
||||
return null;
|
||||
}
|
||||
@@ -86,13 +89,17 @@ final class Callback
|
||||
|
||||
|
||||
/**
|
||||
* Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
|
||||
* that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
|
||||
* @param mixed $callable
|
||||
* @return callable
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function check($callable, bool $syntax = false)
|
||||
{
|
||||
if (!is_callable($callable, $syntax)) {
|
||||
throw new Nette\InvalidArgumentException($syntax
|
||||
throw new Nette\InvalidArgumentException(
|
||||
$syntax
|
||||
? 'Given value is not a callable type.'
|
||||
: sprintf("Callback '%s' is not callable.", self::toString($callable))
|
||||
);
|
||||
@@ -102,7 +109,8 @@ final class Callback
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $callable may be syntactically correct but not callable
|
||||
* Converts PHP callback to textual form. Class or method may not exists.
|
||||
* @param mixed $callable
|
||||
*/
|
||||
public static function toString($callable): string
|
||||
{
|
||||
@@ -119,8 +127,10 @@ final class Callback
|
||||
|
||||
|
||||
/**
|
||||
* @param callable $callable is escalated to ReflectionException
|
||||
* Returns reflection for method or function used in PHP callback.
|
||||
* @param callable $callable type check is escalated to ReflectionException
|
||||
* @return \ReflectionMethod|\ReflectionFunction
|
||||
* @throws \ReflectionException if callback is not valid
|
||||
*/
|
||||
public static function toReflection($callable): \ReflectionFunctionAbstract
|
||||
{
|
||||
@@ -140,6 +150,9 @@ final class Callback
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether PHP callback is function or static method.
|
||||
*/
|
||||
public static function isStatic(callable $callable): bool
|
||||
{
|
||||
return is_array($callable) ? is_string($callable[0]) : is_string($callable);
|
||||
@@ -147,8 +160,7 @@ final class Callback
|
||||
|
||||
|
||||
/**
|
||||
* Unwraps closure created by Closure::fromCallable()
|
||||
* @internal
|
||||
* Unwraps closure created by Closure::fromCallable().
|
||||
*/
|
||||
public static function unwrap(\Closure $closure): callable
|
||||
{
|
||||
|
||||
+28
-6
@@ -39,9 +39,10 @@ class DateTime extends \DateTime implements \JsonSerializable
|
||||
|
||||
|
||||
/**
|
||||
* DateTime object factory.
|
||||
* Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object.
|
||||
* @param string|int|\DateTimeInterface $time
|
||||
* @return static
|
||||
* @throws \Exception if the date and time are not valid.
|
||||
*/
|
||||
public static function from($time)
|
||||
{
|
||||
@@ -63,11 +64,26 @@ class DateTime extends \DateTime implements \JsonSerializable
|
||||
/**
|
||||
* Creates DateTime object.
|
||||
* @return static
|
||||
* @throws Nette\InvalidArgumentException if the date and time are not valid.
|
||||
*/
|
||||
public static function fromParts(int $year, int $month, int $day, int $hour = 0, int $minute = 0, float $second = 0.0)
|
||||
{
|
||||
$s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5f', $year, $month, $day, $hour, $minute, $second);
|
||||
if (!checkdate($month, $day, $year) || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 || $second < 0 || $second >= 60) {
|
||||
public static function fromParts(
|
||||
int $year,
|
||||
int $month,
|
||||
int $day,
|
||||
int $hour = 0,
|
||||
int $minute = 0,
|
||||
float $second = 0.0
|
||||
) {
|
||||
$s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second);
|
||||
if (
|
||||
!checkdate($month, $day, $year)
|
||||
|| $hour < 0
|
||||
|| $hour > 23
|
||||
|| $minute < 0
|
||||
|| $minute > 59
|
||||
|| $second < 0
|
||||
|| $second >= 60
|
||||
) {
|
||||
throw new Nette\InvalidArgumentException("Invalid date '$s'");
|
||||
}
|
||||
return new static($s);
|
||||
@@ -107,13 +123,19 @@ class DateTime extends \DateTime implements \JsonSerializable
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the date and time in the format 'Y-m-d H:i:s'.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
/**
|
||||
* Creates a copy with a modified time.
|
||||
* @return static
|
||||
*/
|
||||
public function modifyClone(string $modify = '')
|
||||
{
|
||||
$dolly = clone $this;
|
||||
|
||||
+46
-41
@@ -20,54 +20,59 @@ final class FileSystem
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Creates a directory.
|
||||
* @throws Nette\IOException
|
||||
* Creates a directory if it doesn't exist.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function createDir(string $dir, int $mode = 0777): void
|
||||
{
|
||||
if (!is_dir($dir) && !@mkdir($dir, $mode, true) && !is_dir($dir)) { // @ - dir may already exist
|
||||
throw new Nette\IOException("Unable to create directory '$dir'. " . Helpers::getLastError());
|
||||
throw new Nette\IOException("Unable to create directory '$dir' with mode " . decoct($mode) . '. ' . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies a file or directory.
|
||||
* @throws Nette\IOException
|
||||
* Copies a file or a directory. Overwrites existing files and directories by default.
|
||||
* @throws Nette\IOException on error occurred
|
||||
* @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
|
||||
*/
|
||||
public static function copy(string $source, string $dest, bool $overwrite = true): void
|
||||
public static function copy(string $origin, string $target, bool $overwrite = true): void
|
||||
{
|
||||
if (stream_is_local($source) && !file_exists($source)) {
|
||||
throw new Nette\IOException("File or directory '$source' not found.");
|
||||
if (stream_is_local($origin) && !file_exists($origin)) {
|
||||
throw new Nette\IOException("File or directory '$origin' not found.");
|
||||
|
||||
} elseif (!$overwrite && file_exists($dest)) {
|
||||
throw new Nette\InvalidStateException("File or directory '$dest' already exists.");
|
||||
} elseif (!$overwrite && file_exists($target)) {
|
||||
throw new Nette\InvalidStateException("File or directory '$target' already exists.");
|
||||
|
||||
} elseif (is_dir($source)) {
|
||||
static::createDir($dest);
|
||||
foreach (new \FilesystemIterator($dest) as $item) {
|
||||
} elseif (is_dir($origin)) {
|
||||
static::createDir($target);
|
||||
foreach (new \FilesystemIterator($target) as $item) {
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
|
||||
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
|
||||
if ($item->isDir()) {
|
||||
static::createDir($dest . '/' . $iterator->getSubPathName());
|
||||
static::createDir($target . '/' . $iterator->getSubPathName());
|
||||
} else {
|
||||
static::copy($item->getPathname(), $dest . '/' . $iterator->getSubPathName());
|
||||
static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
static::createDir(dirname($dest));
|
||||
if (($s = @fopen($source, 'rb')) && ($d = @fopen($dest, 'wb')) && @stream_copy_to_stream($s, $d) === false) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to copy file '$source' to '$dest'. " . Helpers::getLastError());
|
||||
static::createDir(dirname($target));
|
||||
if (
|
||||
($s = @fopen($origin, 'rb'))
|
||||
&& ($d = @fopen($target, 'wb'))
|
||||
&& @stream_copy_to_stream($s, $d) === false
|
||||
) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to copy file '$origin' to '$target'. " . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a file or directory.
|
||||
* @throws Nette\IOException
|
||||
* Deletes a file or directory if exists.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function delete(string $path): void
|
||||
{
|
||||
@@ -89,33 +94,33 @@ final class FileSystem
|
||||
|
||||
|
||||
/**
|
||||
* Renames a file or directory.
|
||||
* @throws Nette\IOException
|
||||
* @throws Nette\InvalidStateException if the target file or directory already exist
|
||||
* Renames or moves a file or a directory. Overwrites existing files and directories by default.
|
||||
* @throws Nette\IOException on error occurred
|
||||
* @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
|
||||
*/
|
||||
public static function rename(string $name, string $newName, bool $overwrite = true): void
|
||||
public static function rename(string $origin, string $target, bool $overwrite = true): void
|
||||
{
|
||||
if (!$overwrite && file_exists($newName)) {
|
||||
throw new Nette\InvalidStateException("File or directory '$newName' already exists.");
|
||||
if (!$overwrite && file_exists($target)) {
|
||||
throw new Nette\InvalidStateException("File or directory '$target' already exists.");
|
||||
|
||||
} elseif (!file_exists($name)) {
|
||||
throw new Nette\IOException("File or directory '$name' not found.");
|
||||
} elseif (!file_exists($origin)) {
|
||||
throw new Nette\IOException("File or directory '$origin' not found.");
|
||||
|
||||
} else {
|
||||
static::createDir(dirname($newName));
|
||||
if (realpath($name) !== realpath($newName)) {
|
||||
static::delete($newName);
|
||||
static::createDir(dirname($target));
|
||||
if (realpath($origin) !== realpath($target)) {
|
||||
static::delete($target);
|
||||
}
|
||||
if (!@rename($name, $newName)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to rename file or directory '$name' to '$newName'. " . Helpers::getLastError());
|
||||
if (!@rename($origin, $target)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to rename file or directory '$origin' to '$target'. " . Helpers::getLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads file content.
|
||||
* @throws Nette\IOException
|
||||
* Reads the content of a file.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function read(string $file): string
|
||||
{
|
||||
@@ -128,8 +133,8 @@ final class FileSystem
|
||||
|
||||
|
||||
/**
|
||||
* Writes a string to a file.
|
||||
* @throws Nette\IOException
|
||||
* Writes the string to a file.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function write(string $file, string $content, ?int $mode = 0666): void
|
||||
{
|
||||
@@ -144,7 +149,7 @@ final class FileSystem
|
||||
|
||||
|
||||
/**
|
||||
* Is path absolute?
|
||||
* Determines if the path is absolute.
|
||||
*/
|
||||
public static function isAbsolute(string $path): bool
|
||||
{
|
||||
@@ -153,7 +158,7 @@ final class FileSystem
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes ../. and directory separators in path.
|
||||
* Normalizes `..` and `.` and directory separators in path.
|
||||
*/
|
||||
public static function normalizePath(string $path): string
|
||||
{
|
||||
@@ -173,7 +178,7 @@ final class FileSystem
|
||||
|
||||
|
||||
/**
|
||||
* Joins all given path segments then normalizes the resulting path.
|
||||
* Joins all segments of the path and normalizes the result.
|
||||
*/
|
||||
public static function joinPaths(string ...$paths): string
|
||||
{
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?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;
|
||||
|
||||
|
||||
/**
|
||||
* Floating-point numbers comparison.
|
||||
*/
|
||||
class Floats
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
private const EPSILON = 1e-10;
|
||||
|
||||
|
||||
public static function isZero(float $value): bool
|
||||
{
|
||||
return abs($value) < self::EPSILON;
|
||||
}
|
||||
|
||||
|
||||
public static function isInteger(float $value): bool
|
||||
{
|
||||
return abs(round($value) - $value) < self::EPSILON;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare two floats. If $a < $b it returns -1, if they are equal it returns 0 and if $a > $b it returns 1
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function compare(float $a, float $b): int
|
||||
{
|
||||
if (is_nan($a) || is_nan($b)) {
|
||||
throw new \LogicException('Trying to compare NAN');
|
||||
|
||||
} elseif (!is_finite($a) && !is_finite($b) && $a === $b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$diff = abs($a - $b);
|
||||
if (($diff < self::EPSILON || ($diff / max(abs($a), abs($b)) < self::EPSILON))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a < $b ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a = $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function areEqual(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a < $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isLessThan(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) < 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a <= $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isLessThanOrEqualTo(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) <= 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a > $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isGreaterThan(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a >= $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isGreaterThanOrEqualTo(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) >= 0;
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -13,7 +13,7 @@ namespace Nette\Utils;
|
||||
class Helpers
|
||||
{
|
||||
/**
|
||||
* Captures PHP output into a string.
|
||||
* Executes a callback and returns the captured output as a string.
|
||||
*/
|
||||
public static function capture(callable $func): string
|
||||
{
|
||||
@@ -29,7 +29,8 @@ class Helpers
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last PHP error as plain string.
|
||||
* Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(),
|
||||
* it is nit affected by the PHP directive html_errors and always returns text, not HTML.
|
||||
*/
|
||||
public static function getLastError(): string
|
||||
{
|
||||
@@ -41,18 +42,18 @@ class Helpers
|
||||
|
||||
|
||||
/**
|
||||
* Converts false to null.
|
||||
* @param mixed $val
|
||||
* Converts false to null, does not change other values.
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public static function falseToNull($val)
|
||||
public static function falseToNull($value)
|
||||
{
|
||||
return $val === false ? null : $val;
|
||||
return $value === false ? null : $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the best suggestion (for 8-bit encoding).
|
||||
* Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding).
|
||||
* @param string[] $possibilities
|
||||
*/
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
|
||||
+15
-10
@@ -10,6 +10,7 @@ declare(strict_types=1);
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use Nette\HtmlStringable;
|
||||
use function is_array, is_float, is_object, is_string;
|
||||
|
||||
|
||||
@@ -230,7 +231,7 @@ use function is_array, is_float, is_object, is_string;
|
||||
* @method self width(?int $val)
|
||||
* @method self wrap(?string $val)
|
||||
*/
|
||||
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
@@ -538,7 +539,9 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
if (func_num_args() === 1) {
|
||||
$this->attrs['data'] = $name;
|
||||
} else {
|
||||
$this->attrs["data-$name"] = is_bool($value) ? json_encode($value) : $value;
|
||||
$this->attrs["data-$name"] = is_bool($value)
|
||||
? json_encode($value)
|
||||
: $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
@@ -546,7 +549,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
|
||||
/**
|
||||
* Sets element's HTML content.
|
||||
* @param IHtmlString|string $html
|
||||
* @param HtmlStringable|string $html
|
||||
* @return static
|
||||
*/
|
||||
final public function setHtml($html)
|
||||
@@ -567,12 +570,12 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
|
||||
/**
|
||||
* Sets element's textual content.
|
||||
* @param IHtmlString|string|int|float $text
|
||||
* @param HtmlStringable|string|int|float $text
|
||||
* @return static
|
||||
*/
|
||||
final public function setText($text)
|
||||
{
|
||||
if (!$text instanceof IHtmlString) {
|
||||
if (!$text instanceof HtmlStringable) {
|
||||
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
$this->children = [(string) $text];
|
||||
@@ -591,7 +594,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
|
||||
/**
|
||||
* Adds new element's child.
|
||||
* @param IHtmlString|string $child Html node or raw HTML string
|
||||
* @param HtmlStringable|string $child Html node or raw HTML string
|
||||
* @return static
|
||||
*/
|
||||
final public function addHtml($child)
|
||||
@@ -602,12 +605,12 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
|
||||
/**
|
||||
* Appends plain-text string to element content.
|
||||
* @param IHtmlString|string|int|float $text
|
||||
* @param HtmlStringable|string|int|float $text
|
||||
* @return static
|
||||
*/
|
||||
public function addText($text)
|
||||
{
|
||||
if (!$text instanceof IHtmlString) {
|
||||
if (!$text instanceof HtmlStringable) {
|
||||
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
return $this->insert(null, $text);
|
||||
@@ -628,7 +631,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
|
||||
/**
|
||||
* Inserts child node.
|
||||
* @param IHtmlString|string $child Html node or raw HTML string
|
||||
* @param HtmlStringable|string $child Html node or raw HTML string
|
||||
* @return static
|
||||
*/
|
||||
public function insert(?int $index, $child, bool $replace = false)
|
||||
@@ -823,7 +826,9 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
|
||||
foreach ($value as $k => $v) {
|
||||
if ($v != null) { // intentionally ==, skip nulls & empty string
|
||||
// composite 'style' vs. 'others'
|
||||
$tmp[] = $v === true ? $k : (is_string($k) ? $k . ':' . $v : $v);
|
||||
$tmp[] = $v === true
|
||||
? $k
|
||||
: (is_string($k) ? $k . ':' . $v : $v);
|
||||
}
|
||||
}
|
||||
if ($tmp === null) {
|
||||
|
||||
+98
-38
@@ -13,7 +13,7 @@ use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Basic manipulation with images.
|
||||
* Basic manipulation with images. Supported types are JPEG, PNG, GIF, WEBP and BMP.
|
||||
*
|
||||
* <code>
|
||||
* $image = Image::fromFile('nette.jpg');
|
||||
@@ -22,6 +22,9 @@ use Nette;
|
||||
* $image->send();
|
||||
* </code>
|
||||
*
|
||||
* @method Image affine(array $affine, array $clip = null)
|
||||
* @method array affineMatrixConcat(array $m1, array $m2)
|
||||
* @method array affineMatrixGet(int $type, mixed $options = null)
|
||||
* @method void alphaBlending(bool $on)
|
||||
* @method void antialias(bool $on)
|
||||
* @method void arc($x, $y, $w, $h, $start, $end, $color)
|
||||
@@ -50,7 +53,6 @@ use Nette;
|
||||
* @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
|
||||
* @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
|
||||
* @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
|
||||
* @method void dashedLine($x1, $y1, $x2, $y2, $color)
|
||||
* @method void ellipse($cx, $cy, $w, $h, $color)
|
||||
* @method void fill($x, $y, $color)
|
||||
* @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
|
||||
@@ -79,6 +81,7 @@ use Nette;
|
||||
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
|
||||
* @method void setBrush(Image $brush)
|
||||
* @method void setClip(int $x1, int $y1, int $x2, int $y2)
|
||||
* @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
|
||||
* @method void setPixel($x, $y, $color)
|
||||
* @method void setStyle(array $style)
|
||||
* @method void setThickness($thickness)
|
||||
@@ -89,7 +92,7 @@ use Nette;
|
||||
* @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
|
||||
* @property-read int $width
|
||||
* @property-read int $height
|
||||
* @property-read resource $imageResource
|
||||
* @property-read resource|\GdImage $imageResource
|
||||
*/
|
||||
class Image
|
||||
{
|
||||
@@ -122,7 +125,7 @@ class Image
|
||||
|
||||
private const FORMATS = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::BMP => 'bmp'];
|
||||
|
||||
/** @var resource */
|
||||
/** @var resource|\GdImage */
|
||||
private $image;
|
||||
|
||||
|
||||
@@ -141,42 +144,44 @@ class Image
|
||||
|
||||
|
||||
/**
|
||||
* Opens image from file.
|
||||
* Reads an image from a file and returns its type in $type.
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws UnknownImageFileException if file not found or file type is not known
|
||||
* @return static
|
||||
*/
|
||||
public static function fromFile(string $file, int &$detectedFormat = null)
|
||||
public static function fromFile(string $file, int &$type = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
$detectedFormat = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error
|
||||
if (!isset(self::FORMATS[$detectedFormat])) {
|
||||
$detectedFormat = null;
|
||||
$type = self::detectTypeFromFile($file);
|
||||
if (!$type) {
|
||||
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
|
||||
}
|
||||
return new static(Callback::invokeSafe('imagecreatefrom' . image_type_to_extension($detectedFormat, false), [$file], function (string $message): void {
|
||||
|
||||
$method = 'imagecreatefrom' . self::FORMATS[$type];
|
||||
return new static(Callback::invokeSafe($method, [$file], function (string $message): void {
|
||||
throw new ImageException($message);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new image from the image stream in the string.
|
||||
* Reads an image from a string and returns its type in $type.
|
||||
* @return static
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws ImageException
|
||||
*/
|
||||
public static function fromString(string $s, int &$detectedFormat = null)
|
||||
public static function fromString(string $s, int &$type = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
$tmp = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error
|
||||
$detectedFormat = isset(self::FORMATS[$tmp]) ? $tmp : null;
|
||||
$type = self::detectTypeFromString($s);
|
||||
if (!$type) {
|
||||
throw new UnknownImageFileException('Unknown type of image.');
|
||||
}
|
||||
|
||||
return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function (string $message): void {
|
||||
@@ -186,8 +191,9 @@ class Image
|
||||
|
||||
|
||||
/**
|
||||
* Creates blank image.
|
||||
* Creates a new true color image of the given dimensions. The default color is black.
|
||||
* @return static
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
*/
|
||||
public static function fromBlank(int $width, int $height, array $color = null)
|
||||
{
|
||||
@@ -211,6 +217,29 @@ class Image
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of image from file.
|
||||
*/
|
||||
public static function detectTypeFromFile(string $file): ?int
|
||||
{
|
||||
$type = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error
|
||||
return isset(self::FORMATS[$type]) ? $type : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of image from string.
|
||||
*/
|
||||
public static function detectTypeFromString(string $s): ?int
|
||||
{
|
||||
$type = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error
|
||||
return isset(self::FORMATS[$type]) ? $type : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the file extension for the given `Image::XXX` constant.
|
||||
*/
|
||||
public static function typeToExtension(int $type): string
|
||||
{
|
||||
if (!isset(self::FORMATS[$type])) {
|
||||
@@ -220,6 +249,9 @@ class Image
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mime type for the given `Image::XXX` constant.
|
||||
*/
|
||||
public static function typeToMimeType(int $type): string
|
||||
{
|
||||
return 'image/' . self::typeToExtension($type);
|
||||
@@ -228,7 +260,7 @@ class Image
|
||||
|
||||
/**
|
||||
* Wraps GD image.
|
||||
* @param resource $image
|
||||
* @param resource|\GdImage $image
|
||||
*/
|
||||
public function __construct($image)
|
||||
{
|
||||
@@ -257,12 +289,12 @@ class Image
|
||||
|
||||
/**
|
||||
* Sets image resource.
|
||||
* @param resource $image
|
||||
* @param resource|\GdImage $image
|
||||
* @return static
|
||||
*/
|
||||
protected function setImageResource($image)
|
||||
{
|
||||
if (!is_resource($image) || get_resource_type($image) !== 'gd') {
|
||||
if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) {
|
||||
throw new Nette\InvalidArgumentException('Image is not valid.');
|
||||
}
|
||||
$this->image = $image;
|
||||
@@ -272,7 +304,7 @@ class Image
|
||||
|
||||
/**
|
||||
* Returns image GD resource.
|
||||
* @return resource
|
||||
* @return resource|\GdImage
|
||||
*/
|
||||
public function getImageResource()
|
||||
{
|
||||
@@ -281,7 +313,7 @@ class Image
|
||||
|
||||
|
||||
/**
|
||||
* Resizes image.
|
||||
* Scales an image.
|
||||
* @param int|string|null $width in pixels or percent
|
||||
* @param int|string|null $height in pixels or percent
|
||||
* @return static
|
||||
@@ -297,9 +329,16 @@ class Image
|
||||
if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
|
||||
$newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource();
|
||||
imagecopyresampled(
|
||||
$newImage, $this->image,
|
||||
0, 0, 0, 0,
|
||||
$newWidth, $newHeight, $this->getWidth(), $this->getHeight()
|
||||
$newImage,
|
||||
$this->image,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$newWidth,
|
||||
$newHeight,
|
||||
$this->getWidth(),
|
||||
$this->getHeight()
|
||||
);
|
||||
$this->image = $newImage;
|
||||
}
|
||||
@@ -316,16 +355,23 @@ class Image
|
||||
* @param int|string|null $newWidth in pixels or percent
|
||||
* @param int|string|null $newHeight in pixels or percent
|
||||
*/
|
||||
public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $flags = self::FIT): array
|
||||
{
|
||||
if ($newWidth !== null && self::isPercent($newWidth)) {
|
||||
public static function calculateSize(
|
||||
int $srcWidth,
|
||||
int $srcHeight,
|
||||
$newWidth,
|
||||
$newHeight,
|
||||
int $flags = self::FIT
|
||||
): array {
|
||||
if ($newWidth === null) {
|
||||
} elseif (self::isPercent($newWidth)) {
|
||||
$newWidth = (int) round($srcWidth / 100 * abs($newWidth));
|
||||
$percents = true;
|
||||
} else {
|
||||
$newWidth = abs($newWidth);
|
||||
}
|
||||
|
||||
if ($newHeight !== null && self::isPercent($newHeight)) {
|
||||
if ($newHeight === null) {
|
||||
} elseif (self::isPercent($newHeight)) {
|
||||
$newHeight = (int) round($srcHeight / 100 * abs($newHeight));
|
||||
$flags |= empty($percents) ? 0 : self::STRETCH;
|
||||
} else {
|
||||
@@ -433,7 +479,7 @@ class Image
|
||||
|
||||
|
||||
/**
|
||||
* Sharpen image.
|
||||
* Sharpens image a little bit.
|
||||
* @return static
|
||||
*/
|
||||
public function sharpen()
|
||||
@@ -497,15 +543,21 @@ class Image
|
||||
}
|
||||
|
||||
imagecopy(
|
||||
$this->image, $output,
|
||||
$left, $top, 0, 0, $width, $height
|
||||
$this->image,
|
||||
$output,
|
||||
$left,
|
||||
$top,
|
||||
0,
|
||||
0,
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves image to the file. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG.
|
||||
* Saves image to the file. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9).
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function save(string $file, int $quality = null, int $type = null): void
|
||||
@@ -524,7 +576,7 @@ class Image
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to string. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG.
|
||||
* Outputs image to string. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9).
|
||||
*/
|
||||
public function toString(int $type = self::JPEG, int $quality = null): string
|
||||
{
|
||||
@@ -552,7 +604,7 @@ class Image
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to browser. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG.
|
||||
* Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9).
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function send(int $type = self::JPEG, int $quality = null): void
|
||||
@@ -610,7 +662,7 @@ class Image
|
||||
{
|
||||
$function = 'image' . $name;
|
||||
if (!function_exists($function)) {
|
||||
ObjectHelpers::strictCall(get_class($this), $name);
|
||||
ObjectHelpers::strictCall(static::class, $name);
|
||||
}
|
||||
|
||||
foreach ($args as $key => $value) {
|
||||
@@ -620,15 +672,23 @@ class Image
|
||||
} elseif (is_array($value) && isset($value['red'])) { // rgb
|
||||
$args[$key] = imagecolorallocatealpha(
|
||||
$this->image,
|
||||
$value['red'], $value['green'], $value['blue'], $value['alpha']
|
||||
$value['red'],
|
||||
$value['green'],
|
||||
$value['blue'],
|
||||
$value['alpha']
|
||||
) ?: imagecolorresolvealpha(
|
||||
$this->image,
|
||||
$value['red'], $value['green'], $value['blue'], $value['alpha']
|
||||
$value['red'],
|
||||
$value['green'],
|
||||
$value['blue'],
|
||||
$value['alpha']
|
||||
);
|
||||
}
|
||||
}
|
||||
$res = $function($this->image, ...$args);
|
||||
return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
|
||||
return $res instanceof \GdImage || (is_resource($res) && get_resource_type($res) === 'gd')
|
||||
? $this->setImageResource($res)
|
||||
: $res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
-2
@@ -27,7 +27,8 @@ final class Json
|
||||
|
||||
|
||||
/**
|
||||
* Returns the JSON representation of a value. Accepts flag Json::PRETTY.
|
||||
* Converts value to JSON format. The flag can be Json::PRETTY, which formats JSON for easier reading and clarity,
|
||||
* and Json::ESCAPE_UNICODE for ASCII output.
|
||||
* @param mixed $value
|
||||
* @throws JsonException
|
||||
*/
|
||||
@@ -47,7 +48,7 @@ final class Json
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a JSON string. Accepts flag Json::FORCE_ARRAY.
|
||||
* Parses JSON to PHP value. The flag can be Json::FORCE_ARRAY, which forces an array instead of an object as the return value.
|
||||
* @return mixed
|
||||
* @throws JsonException
|
||||
*/
|
||||
|
||||
+3
-1
@@ -87,7 +87,9 @@ final class ObjectHelpers
|
||||
$rc = new \ReflectionClass($class);
|
||||
preg_match_all(
|
||||
'~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx',
|
||||
(string) $rc->getDocComment(), $matches, PREG_SET_ORDER
|
||||
(string) $rc->getDocComment(),
|
||||
$matches,
|
||||
PREG_SET_ORDER
|
||||
);
|
||||
|
||||
$props = [];
|
||||
|
||||
+12
-4
@@ -79,7 +79,9 @@ class Paginator
|
||||
*/
|
||||
public function getLastPage(): ?int
|
||||
{
|
||||
return $this->itemCount === null ? null : $this->base + max(0, $this->getPageCount() - 1);
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: $this->base + max(0, $this->getPageCount() - 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +111,9 @@ class Paginator
|
||||
protected function getPageIndex(): int
|
||||
{
|
||||
$index = max(0, $this->page - $this->base);
|
||||
return $this->itemCount === null ? $index : min($index, max(0, $this->getPageCount() - 1));
|
||||
return $this->itemCount === null
|
||||
? $index
|
||||
: min($index, max(0, $this->getPageCount() - 1));
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +131,9 @@ class Paginator
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return $this->itemCount === null ? false : $this->getPageIndex() >= $this->getPageCount() - 1;
|
||||
return $this->itemCount === null
|
||||
? false
|
||||
: $this->getPageIndex() >= $this->getPageCount() - 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +142,9 @@ class Paginator
|
||||
*/
|
||||
public function getPageCount(): ?int
|
||||
{
|
||||
return $this->itemCount === null ? null : (int) ceil($this->itemCount / $this->itemsPerPage);
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: (int) ceil($this->itemCount / $this->itemsPerPage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ final class Random
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Generate random string.
|
||||
* Generates a random string of given length from characters specified in second argument.
|
||||
* Supports intervals, such as `0-9` or `A-Z`.
|
||||
*/
|
||||
public static function generate(int $length = 10, string $charlist = '0-9a-z'): string
|
||||
{
|
||||
|
||||
+122
-45
@@ -21,50 +21,122 @@ final class Reflection
|
||||
|
||||
private const BUILTIN_TYPES = [
|
||||
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
|
||||
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1,
|
||||
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Determines if type is PHP built-in type. Otherwise, it is the class name.
|
||||
*/
|
||||
public static function isBuiltinType(string $type): bool
|
||||
{
|
||||
return isset(self::BUILTIN_TYPES[strtolower($type)]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
|
||||
* If the function does not have a return type, it returns null.
|
||||
* If the function has union type, it throws Nette\InvalidStateException.
|
||||
*/
|
||||
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
|
||||
{
|
||||
$type = $func->getReturnType();
|
||||
return $type instanceof \ReflectionNamedType && $func instanceof \ReflectionMethod
|
||||
? self::normalizeType($type->getName(), $func)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
public static function getParameterType(\ReflectionParameter $param): ?string
|
||||
{
|
||||
$type = $param->getType();
|
||||
return $type instanceof \ReflectionNamedType
|
||||
? self::normalizeType($type->getName(), $param)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
public static function getPropertyType(\ReflectionProperty $prop): ?string
|
||||
{
|
||||
$type = PHP_VERSION_ID >= 70400 ? $prop->getType() : null;
|
||||
return $type instanceof \ReflectionNamedType
|
||||
? self::normalizeType($type->getName(), $prop)
|
||||
: null;
|
||||
return self::getType($func, $func->getReturnType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
* Returns the types of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
|
||||
*/
|
||||
public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
|
||||
{
|
||||
return self::getType($func, $func->getReturnType(), true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
|
||||
* If the parameter does not have a type, it returns null.
|
||||
* If the parameter has union type, it throws Nette\InvalidStateException.
|
||||
*/
|
||||
public static function getParameterType(\ReflectionParameter $param): ?string
|
||||
{
|
||||
return self::getType($param, $param->getType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the types of given parameter and normalizes `self` and `parent` to the actual class names.
|
||||
*/
|
||||
public static function getParameterTypes(\ReflectionParameter $param): array
|
||||
{
|
||||
return self::getType($param, $param->getType(), true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of given property and normalizes `self` and `parent` to the actual class names.
|
||||
* If the property does not have a type, it returns null.
|
||||
* If the property has union type, it throws Nette\InvalidStateException.
|
||||
*/
|
||||
public static function getPropertyType(\ReflectionProperty $prop): ?string
|
||||
{
|
||||
return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the types of given property and normalizes `self` and `parent` to the actual class names.
|
||||
*/
|
||||
public static function getPropertyTypes(\ReflectionProperty $prop): array
|
||||
{
|
||||
return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
* @return string|array|null
|
||||
*/
|
||||
private static function getType($reflection, ?\ReflectionType $type, bool $asArray = false)
|
||||
{
|
||||
if ($type === null) {
|
||||
return $asArray ? [] : null;
|
||||
|
||||
} elseif ($type instanceof \ReflectionNamedType) {
|
||||
$name = self::normalizeType($type->getName(), $reflection);
|
||||
if ($asArray) {
|
||||
return $type->allowsNull() && $type->getName() !== 'mixed'
|
||||
? [$name, 'null']
|
||||
: [$name];
|
||||
}
|
||||
return $name;
|
||||
|
||||
} elseif ($type instanceof \ReflectionUnionType) {
|
||||
if ($asArray) {
|
||||
$types = [];
|
||||
foreach ($type->getTypes() as $type) {
|
||||
$types[] = self::normalizeType($type->getName(), $reflection);
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union type.');
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
*/
|
||||
private static function normalizeType(string $type, $reflection): string
|
||||
{
|
||||
$lower = strtolower($type);
|
||||
if ($lower === 'self') {
|
||||
if ($reflection instanceof \ReflectionFunction) {
|
||||
return $type;
|
||||
} elseif ($lower === 'self' || $lower === 'static') {
|
||||
return $reflection->getDeclaringClass()->name;
|
||||
} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
|
||||
return $reflection->getDeclaringClass()->getParentClass()->name;
|
||||
@@ -75,8 +147,9 @@ final class Reflection
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default value of parameter. If it is a constant, it returns its value.
|
||||
* @return mixed
|
||||
* @throws \ReflectionException when default value is not available or resolvable
|
||||
* @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved
|
||||
*/
|
||||
public static function getParameterDefaultValue(\ReflectionParameter $param)
|
||||
{
|
||||
@@ -107,7 +180,7 @@ final class Reflection
|
||||
|
||||
|
||||
/**
|
||||
* Returns declaring class or trait.
|
||||
* Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait.
|
||||
*/
|
||||
public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
|
||||
{
|
||||
@@ -124,7 +197,8 @@ final class Reflection
|
||||
|
||||
|
||||
/**
|
||||
* Returns declaring method in class or trait.
|
||||
* Returns a reflection of a method that contains a declaration of $method.
|
||||
* Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name.
|
||||
*/
|
||||
public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod
|
||||
{
|
||||
@@ -158,14 +232,12 @@ final class Reflection
|
||||
|
||||
|
||||
/**
|
||||
* Are documentation comments available?
|
||||
* Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache.
|
||||
*/
|
||||
public static function areCommentsAvailable(): bool
|
||||
{
|
||||
static $res;
|
||||
return $res === null
|
||||
? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment()
|
||||
: $res;
|
||||
return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment();
|
||||
}
|
||||
|
||||
|
||||
@@ -174,13 +246,13 @@ final class Reflection
|
||||
if ($ref instanceof \ReflectionClass) {
|
||||
return $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionMethod) {
|
||||
return $ref->getDeclaringClass()->name . '::' . $ref->name;
|
||||
return $ref->getDeclaringClass()->name . '::' . $ref->name . '()';
|
||||
} elseif ($ref instanceof \ReflectionFunction) {
|
||||
return $ref->name;
|
||||
return $ref->name . '()';
|
||||
} elseif ($ref instanceof \ReflectionProperty) {
|
||||
return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionParameter) {
|
||||
return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction()) . '()';
|
||||
return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction());
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException;
|
||||
}
|
||||
@@ -188,10 +260,11 @@ final class Reflection
|
||||
|
||||
|
||||
/**
|
||||
* Expands class name into full name.
|
||||
* Expands the name of the class to full name in the given context of given class.
|
||||
* Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function expandClassName(string $name, \ReflectionClass $rc): string
|
||||
public static function expandClassName(string $name, \ReflectionClass $context): string
|
||||
{
|
||||
$lower = strtolower($name);
|
||||
if (empty($name)) {
|
||||
@@ -200,21 +273,21 @@ final class Reflection
|
||||
} elseif (isset(self::BUILTIN_TYPES[$lower])) {
|
||||
return $lower;
|
||||
|
||||
} elseif ($lower === 'self') {
|
||||
return $rc->name;
|
||||
} elseif ($lower === 'self' || $lower === 'static') {
|
||||
return $context->name;
|
||||
|
||||
} elseif ($name[0] === '\\') { // fully qualified name
|
||||
return ltrim($name, '\\');
|
||||
}
|
||||
|
||||
$uses = self::getUseStatements($rc);
|
||||
$uses = self::getUseStatements($context);
|
||||
$parts = explode('\\', $name, 2);
|
||||
if (isset($uses[$parts[0]])) {
|
||||
$parts[0] = $uses[$parts[0]];
|
||||
return implode('\\', $parts);
|
||||
|
||||
} elseif ($rc->inNamespace()) {
|
||||
return $rc->getNamespaceName() . '\\' . $name;
|
||||
} elseif ($context->inNamespace()) {
|
||||
return $context->getNamespaceName() . '\\' . $name;
|
||||
|
||||
} else {
|
||||
return $name;
|
||||
@@ -255,11 +328,15 @@ final class Reflection
|
||||
$namespace = $class = $classLevel = $level = null;
|
||||
$res = $uses = [];
|
||||
|
||||
$nameTokens = PHP_VERSION_ID < 80000
|
||||
? [T_STRING, T_NS_SEPARATOR]
|
||||
: [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED];
|
||||
|
||||
while ($token = current($tokens)) {
|
||||
next($tokens);
|
||||
switch (is_array($token) ? $token[0] : $token) {
|
||||
case T_NAMESPACE:
|
||||
$namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
|
||||
$namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\');
|
||||
$uses = [];
|
||||
break;
|
||||
|
||||
@@ -277,10 +354,10 @@ final class Reflection
|
||||
break;
|
||||
|
||||
case T_USE:
|
||||
while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
|
||||
while (!$class && ($name = self::fetch($tokens, $nameTokens))) {
|
||||
$name = ltrim($name, '\\');
|
||||
if (self::fetch($tokens, '{')) {
|
||||
while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
|
||||
while ($suffix = self::fetch($tokens, $nameTokens)) {
|
||||
if (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
|
||||
} else {
|
||||
|
||||
+66
-48
@@ -24,7 +24,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the string is valid for UTF-8 encoding.
|
||||
* Checks if the string is valid in UTF-8 encoding.
|
||||
*/
|
||||
public static function checkEncoding(string $s): bool
|
||||
{
|
||||
@@ -33,7 +33,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Removes invalid code unit sequences from UTF-8 string.
|
||||
* Removes all invalid UTF-8 characters from a string.
|
||||
*/
|
||||
public static function fixEncoding(string $s): string
|
||||
{
|
||||
@@ -43,7 +43,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns a specific character in UTF-8 from code point (0x0 to 0xD7FF or 0xE000 to 0x10FFFF).
|
||||
* Returns a specific character in UTF-8 from code point (number in range 0x0000..D7FF or 0xE000..10FFFF).
|
||||
* @throws Nette\InvalidArgumentException if code point is not in valid range
|
||||
*/
|
||||
public static function chr(int $code): string
|
||||
@@ -85,7 +85,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns a part of UTF-8 string.
|
||||
* Returns a part of UTF-8 string specified by starting position and length. If start is negative,
|
||||
* the returned string will start at the start'th character from the end of string.
|
||||
*/
|
||||
public static function substring(string $s, int $start, int $length = null): string
|
||||
{
|
||||
@@ -103,7 +104,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Removes special controls characters and normalizes line endings, spaces and normal form to NFC in UTF-8 string.
|
||||
* Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines,
|
||||
* trims end spaces on lines, normalizes UTF-8 to the normal form of NFC.
|
||||
*/
|
||||
public static function normalize(string $s): string
|
||||
{
|
||||
@@ -137,21 +139,26 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to ASCII.
|
||||
* Converts UTF-8 string to ASCII, ie removes diacritics etc.
|
||||
*/
|
||||
public static function toAscii(string $s): string
|
||||
{
|
||||
$iconv = defined('ICONV_IMPL') ? ICONV_IMPL : null;
|
||||
$iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null;
|
||||
static $transliterator = null;
|
||||
if ($transliterator === null && class_exists('Transliterator', false)) {
|
||||
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
|
||||
if ($transliterator === null) {
|
||||
if (class_exists('Transliterator', false)) {
|
||||
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
|
||||
} else {
|
||||
trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE);
|
||||
$transliterator = false;
|
||||
}
|
||||
}
|
||||
|
||||
// remove control characters and check UTF-8 validity
|
||||
$s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]);
|
||||
|
||||
// transliteration (by Transliterator and iconv) is not optimal, replace some characters directly
|
||||
$s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю
|
||||
$s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß
|
||||
if ($iconv !== 'libiconv') {
|
||||
$s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔
|
||||
}
|
||||
@@ -174,9 +181,11 @@ class Strings
|
||||
if ($iconv === 'glibc') {
|
||||
// glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved
|
||||
$s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s);
|
||||
$s = strtr($s,
|
||||
$s = strtr(
|
||||
$s,
|
||||
"\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
|
||||
'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.');
|
||||
'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.'
|
||||
);
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]);
|
||||
} else {
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
@@ -194,7 +203,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to web safe characters [a-z0-9-] text.
|
||||
* Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters
|
||||
* except letters of the English alphabet and numbers with a hyphens.
|
||||
*/
|
||||
public static function webalize(string $s, string $charlist = null, bool $lower = true): string
|
||||
{
|
||||
@@ -209,7 +219,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Truncates UTF-8 string to maximal length.
|
||||
* Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated,
|
||||
* an ellipsis (or something else set with third argument) is appended to the string.
|
||||
*/
|
||||
public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string
|
||||
{
|
||||
@@ -230,7 +241,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Indents UTF-8 string from the left.
|
||||
* Indents a multiline text from the left. Second argument sets how many indentation chars should be used,
|
||||
* while the indent itself is the third argument (*tab* by default).
|
||||
*/
|
||||
public static function indent(string $s, int $level = 1, string $chars = "\t"): string
|
||||
{
|
||||
@@ -242,7 +254,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to lower case.
|
||||
* Converts all characters of UTF-8 string to lower case.
|
||||
*/
|
||||
public static function lower(string $s): string
|
||||
{
|
||||
@@ -251,7 +263,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Converts first character to lower case.
|
||||
* Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged.
|
||||
*/
|
||||
public static function firstLower(string $s): string
|
||||
{
|
||||
@@ -260,7 +272,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to upper case.
|
||||
* Converts all characters of a UTF-8 string to upper case.
|
||||
*/
|
||||
public static function upper(string $s): string
|
||||
{
|
||||
@@ -269,7 +281,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Converts first character to upper case.
|
||||
* Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged.
|
||||
*/
|
||||
public static function firstUpper(string $s): string
|
||||
{
|
||||
@@ -278,7 +290,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Capitalizes UTF-8 string.
|
||||
* Converts the first character of every word of a UTF-8 string to upper case and the others to lower case.
|
||||
*/
|
||||
public static function capitalize(string $s): string
|
||||
{
|
||||
@@ -287,28 +299,30 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Case-insensitive compares UTF-8 strings.
|
||||
* Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared,
|
||||
* if it is negative, the corresponding number of characters from the end of the strings is compared,
|
||||
* otherwise the appropriate number of characters from the beginning is compared.
|
||||
*/
|
||||
public static function compare(string $left, string $right, int $len = null): bool
|
||||
public static function compare(string $left, string $right, int $length = null): bool
|
||||
{
|
||||
if (class_exists('Normalizer', false)) {
|
||||
$left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster
|
||||
$right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster
|
||||
}
|
||||
|
||||
if ($len < 0) {
|
||||
$left = self::substring($left, $len, -$len);
|
||||
$right = self::substring($right, $len, -$len);
|
||||
} elseif ($len !== null) {
|
||||
$left = self::substring($left, 0, $len);
|
||||
$right = self::substring($right, 0, $len);
|
||||
if ($length < 0) {
|
||||
$left = self::substring($left, $length, -$length);
|
||||
$right = self::substring($right, $length, -$length);
|
||||
} elseif ($length !== null) {
|
||||
$left = self::substring($left, 0, $length);
|
||||
$right = self::substring($right, 0, $length);
|
||||
}
|
||||
return self::lower($left) === self::lower($right);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the length of common prefix of strings.
|
||||
* Finds the common prefix of strings or returns empty string if the prefix was not found.
|
||||
* @param string[] $strings
|
||||
*/
|
||||
public static function findPrefix(array $strings): string
|
||||
@@ -334,12 +348,14 @@ class Strings
|
||||
*/
|
||||
public static function length(string $s): int
|
||||
{
|
||||
return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s));
|
||||
return function_exists('mb_strlen')
|
||||
? mb_strlen($s, 'UTF-8')
|
||||
: strlen(utf8_decode($s));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strips whitespace from UTF-8 string.
|
||||
* Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string.
|
||||
*/
|
||||
public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string
|
||||
{
|
||||
@@ -349,7 +365,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Pad a UTF-8 string to a certain length with another string.
|
||||
* Pads a UTF-8 string to given length by prepending the $pad string to the beginning.
|
||||
*/
|
||||
public static function padLeft(string $s, int $length, string $pad = ' '): string
|
||||
{
|
||||
@@ -360,7 +376,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Pad a UTF-8 string to a certain length with another string.
|
||||
* Pads UTF-8 string to given length by appending the $pad string to the end.
|
||||
*/
|
||||
public static function padRight(string $s, int $length, string $pad = ' '): string
|
||||
{
|
||||
@@ -371,7 +387,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Reverse string.
|
||||
* Reverses UTF-8 string.
|
||||
*/
|
||||
public static function reverse(string $s): string
|
||||
{
|
||||
@@ -383,8 +399,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns part of $haystack before $nth occurence of $needle (negative value means searching from the end).
|
||||
* @return string|null returns null if the needle was not found
|
||||
* Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found.
|
||||
* Negative value means searching from the end.
|
||||
*/
|
||||
public static function before(string $haystack, string $needle, int $nth = 1): ?string
|
||||
{
|
||||
@@ -396,8 +412,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns part of $haystack after $nth occurence of $needle (negative value means searching from the end).
|
||||
* @return string|null returns null if the needle was not found
|
||||
* Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found.
|
||||
* Negative value means searching from the end.
|
||||
*/
|
||||
public static function after(string $haystack, string $needle, int $nth = 1): ?string
|
||||
{
|
||||
@@ -409,8 +425,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns position of $nth occurence of $needle in $haystack (negative value means searching from the end).
|
||||
* @return int|null offset in characters or null if the needle was not found
|
||||
* Returns position in bytes of $nth occurence of $needle in $haystack or null if the $needle was not found.
|
||||
* Negative value of `$nth` means searching from the end.
|
||||
*/
|
||||
public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int
|
||||
{
|
||||
@@ -422,8 +438,7 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Returns position of $nth occurence of $needle in $haystack.
|
||||
* @return int|null offset in bytes or null if the needle was not found
|
||||
* Returns position in bytes of $nth occurence of $needle in $haystack or null if the needle was not found.
|
||||
*/
|
||||
private static function pos(string $haystack, string $needle, int $nth = 1): ?int
|
||||
{
|
||||
@@ -452,7 +467,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Splits string by a regular expression.
|
||||
* Splits a string into array by the regular expression.
|
||||
* Argument $flag takes same arguments as preg_split(), but PREG_SPLIT_DELIM_CAPTURE is set by default.
|
||||
*/
|
||||
public static function split(string $subject, string $pattern, int $flags = 0): array
|
||||
{
|
||||
@@ -461,7 +477,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Performs a regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes).
|
||||
* Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern.
|
||||
* Argument $flag takes same arguments as function preg_match().
|
||||
*/
|
||||
public static function match(string $subject, string $pattern, int $flags = 0, int $offset = 0): ?array
|
||||
{
|
||||
@@ -475,7 +492,8 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Performs a global regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes), PREG_SET_ORDER is default.
|
||||
* Finds all occurrences matching regular expression pattern and returns a two-dimensional array.
|
||||
* Argument $flag takes same arguments as function preg_match_all(), but PREG_SET_ORDER is set by default.
|
||||
*/
|
||||
public static function matchAll(string $subject, string $pattern, int $flags = 0, int $offset = 0): array
|
||||
{
|
||||
@@ -492,11 +510,11 @@ class Strings
|
||||
|
||||
|
||||
/**
|
||||
* Perform a regular expression search and replace.
|
||||
* Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
|
||||
* @param string|array $pattern
|
||||
* @param string|callable $replacement
|
||||
*/
|
||||
public static function replace(string $subject, $pattern, $replacement = null, int $limit = -1): string
|
||||
public static function replace(string $subject, $pattern, $replacement = '', int $limit = -1): string
|
||||
{
|
||||
if (is_object($replacement) || is_array($replacement)) {
|
||||
if (!is_callable($replacement, false, $textual)) {
|
||||
@@ -504,7 +522,7 @@ class Strings
|
||||
}
|
||||
return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]);
|
||||
|
||||
} elseif ($replacement === null && is_array($pattern)) {
|
||||
} elseif (is_array($pattern) && is_string(key($pattern))) {
|
||||
$replacement = array_values($pattern);
|
||||
$pattern = array_keys($pattern);
|
||||
}
|
||||
|
||||
+45
-37
@@ -35,14 +35,14 @@ class Validators
|
||||
'string' => 'is_string',
|
||||
|
||||
// pseudo-types
|
||||
'callable' => [__CLASS__, 'isCallable'],
|
||||
'callable' => [self::class, 'isCallable'],
|
||||
'iterable' => 'is_iterable',
|
||||
'list' => [Arrays::class, 'isList'],
|
||||
'mixed' => [__CLASS__, 'isMixed'],
|
||||
'none' => [__CLASS__, 'isNone'],
|
||||
'number' => [__CLASS__, 'isNumber'],
|
||||
'numeric' => [__CLASS__, 'isNumeric'],
|
||||
'numericint' => [__CLASS__, 'isNumericInt'],
|
||||
'mixed' => [self::class, 'isMixed'],
|
||||
'none' => [self::class, 'isNone'],
|
||||
'number' => [self::class, 'isNumber'],
|
||||
'numeric' => [self::class, 'isNumeric'],
|
||||
'numericint' => [self::class, 'isNumericInt'],
|
||||
|
||||
// string patterns
|
||||
'alnum' => 'ctype_alnum',
|
||||
@@ -51,22 +51,22 @@ class Validators
|
||||
'lower' => 'ctype_lower',
|
||||
'pattern' => null,
|
||||
'space' => 'ctype_space',
|
||||
'unicode' => [__CLASS__, 'isUnicode'],
|
||||
'unicode' => [self::class, 'isUnicode'],
|
||||
'upper' => 'ctype_upper',
|
||||
'xdigit' => 'ctype_xdigit',
|
||||
|
||||
// syntax validation
|
||||
'email' => [__CLASS__, 'isEmail'],
|
||||
'identifier' => [__CLASS__, 'isPhpIdentifier'],
|
||||
'uri' => [__CLASS__, 'isUri'],
|
||||
'url' => [__CLASS__, 'isUrl'],
|
||||
'email' => [self::class, 'isEmail'],
|
||||
'identifier' => [self::class, 'isPhpIdentifier'],
|
||||
'uri' => [self::class, 'isUri'],
|
||||
'url' => [self::class, 'isUrl'],
|
||||
|
||||
// environment validation
|
||||
'class' => 'class_exists',
|
||||
'interface' => 'interface_exists',
|
||||
'directory' => 'is_dir',
|
||||
'file' => 'is_file',
|
||||
'type' => [__CLASS__, 'isType'],
|
||||
'type' => [self::class, 'isType'],
|
||||
];
|
||||
|
||||
/** @var array<string,callable> */
|
||||
@@ -86,8 +86,9 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Throws exception if a variable is of unexpected type (separated by pipe).
|
||||
* Verifies that the value is of expected types separated by pipe.
|
||||
* @param mixed $value
|
||||
* @throws AssertionException
|
||||
*/
|
||||
public static function assert($value, string $expected, string $label = 'variable'): void
|
||||
{
|
||||
@@ -106,23 +107,28 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Throws exception if an array field is missing or of unexpected type (separated by pipe).
|
||||
* @param mixed[] $arr
|
||||
* @param int|string $field
|
||||
* Verifies that element $key in array is of expected types separated by pipe.
|
||||
* @param mixed[] $array
|
||||
* @param int|string $key
|
||||
* @throws AssertionException
|
||||
*/
|
||||
public static function assertField(array $arr, $field, string $expected = null, string $label = "item '%' in array"): void
|
||||
{
|
||||
if (!array_key_exists($field, $arr)) {
|
||||
throw new AssertionException('Missing ' . str_replace('%', $field, $label) . '.');
|
||||
public static function assertField(
|
||||
array $array,
|
||||
$key,
|
||||
string $expected = null,
|
||||
string $label = "item '%' in array"
|
||||
): void {
|
||||
if (!array_key_exists($key, $array)) {
|
||||
throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
|
||||
|
||||
} elseif ($expected) {
|
||||
static::assert($arr[$field], $expected, str_replace('%', $field, $label));
|
||||
static::assert($array[$key], $expected, str_replace('%', $key, $label));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a variable is of expected type (separated by pipe).
|
||||
* Verifies that the value is of expected types separated by pipe.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function is($value, string $expected): bool
|
||||
@@ -178,7 +184,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether all values are of expected type (separated by pipe).
|
||||
* Finds whether all values are of expected types separated by pipe.
|
||||
* @param mixed[] $values
|
||||
*/
|
||||
public static function everyIs(iterable $values, string $expected): bool
|
||||
@@ -193,7 +199,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is an integer or a float.
|
||||
* Checks if the value is an integer or a float.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumber($value): bool
|
||||
@@ -203,7 +209,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is an integer.
|
||||
* Checks if the value is an integer or a integer written in a string.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumericInt($value): bool
|
||||
@@ -213,7 +219,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a floating point number in decimal base.
|
||||
* Checks if the value is a number or a number written in a string.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumeric($value): bool
|
||||
@@ -223,7 +229,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is a syntactically correct callback.
|
||||
* Checks if the value is a syntactically correct callback.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isCallable($value): bool
|
||||
@@ -233,7 +239,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is an UTF-8 encoded string.
|
||||
* Checks if the value is a valid UTF-8 string.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isUnicode($value): bool
|
||||
@@ -243,7 +249,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a value is "falsy".
|
||||
* Checks if the value is 0, '', false or null.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNone($value): bool
|
||||
@@ -260,8 +266,9 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a variable is a zero-based integer indexed array.
|
||||
* Checks if a variable is a zero-based integer indexed array.
|
||||
* @param mixed $value
|
||||
* @deprecated use Nette\Utils\Arrays::isList
|
||||
*/
|
||||
public static function isList($value): bool
|
||||
{
|
||||
@@ -270,7 +277,8 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Is a value in specified min and max value pair?
|
||||
* Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
|
||||
* Numbers, strings and DateTime objects can be compared.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isInRange($value, array $range): bool
|
||||
@@ -295,7 +303,7 @@ class Validators
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a valid email address.
|
||||
* Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
|
||||
*/
|
||||
public static function isEmail(string $value): bool
|
||||
{
|
||||
@@ -314,7 +322,7 @@ XX
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a valid http(s) URL.
|
||||
* Checks if the value is a valid URL address.
|
||||
*/
|
||||
public static function isUrl(string $value): bool
|
||||
{
|
||||
@@ -326,11 +334,11 @@ XX
|
||||
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
|
||||
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|
||||
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
|
||||
|\[[0-9a-f:]{3,39}\] # IPv6
|
||||
|\\[[0-9a-f:]{3,39}\\] # IPv6
|
||||
)(:\\d{1,5})? # port
|
||||
(/\\S*)? # path
|
||||
(\?\\S*)? # query
|
||||
(\#\\S*)? # fragment
|
||||
(\\?\\S*)? # query
|
||||
(\\#\\S*)? # fragment
|
||||
$)Dix
|
||||
XX
|
||||
, $value);
|
||||
@@ -338,7 +346,7 @@ XX
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether a string is a valid URI according to RFC 1738.
|
||||
* Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
|
||||
*/
|
||||
public static function isUri(string $value): bool
|
||||
{
|
||||
|
||||
-101
@@ -7,107 +7,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when the value of an argument is
|
||||
* outside the allowable range of values as defined by the invoked method.
|
||||
*/
|
||||
class ArgumentOutOfRangeException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a method call is invalid for the object's
|
||||
* current state, method has been invoked at an illegal or inappropriate time.
|
||||
*/
|
||||
class InvalidStateException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is not implemented.
|
||||
*/
|
||||
class NotImplementedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an invoked method is not supported. For scenarios where
|
||||
* it is sometimes possible to perform the requested operation, see InvalidStateException.
|
||||
*/
|
||||
class NotSupportedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is deprecated.
|
||||
*/
|
||||
class DeprecatedException extends NotSupportedException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a class member (property or method) fails.
|
||||
*/
|
||||
class MemberAccessException extends \Error
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an I/O error occurs.
|
||||
*/
|
||||
class IOException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a file that does not exist on disk.
|
||||
*/
|
||||
class FileNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when part of a file or directory cannot be found.
|
||||
*/
|
||||
class DirectoryNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an argument does not match with the expected value.
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an illegal index was requested.
|
||||
*/
|
||||
class OutOfRangeException extends \OutOfRangeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a value (typically returned by function) does not match with the expected value.
|
||||
*/
|
||||
class UnexpectedValueException extends \UnexpectedValueException
|
||||
{
|
||||
}
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?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;
|
||||
|
||||
if (false) {
|
||||
/** @deprecated use Nette\HtmlStringable */
|
||||
interface IHtmlString extends Nette\HtmlStringable
|
||||
{
|
||||
}
|
||||
} elseif (!interface_exists(IHtmlString::class)) {
|
||||
class_alias(Nette\HtmlStringable::class, IHtmlString::class);
|
||||
}
|
||||
|
||||
namespace Nette\Localization;
|
||||
|
||||
if (false) {
|
||||
/** @deprecated use Nette\Localization\Translator */
|
||||
interface ITranslator extends Translator
|
||||
{
|
||||
}
|
||||
} elseif (!interface_exists(ITranslator::class)) {
|
||||
class_alias(Translator::class, ITranslator::class);
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?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;
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when the value of an argument is
|
||||
* outside the allowable range of values as defined by the invoked method.
|
||||
*/
|
||||
class ArgumentOutOfRangeException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a method call is invalid for the object's
|
||||
* current state, method has been invoked at an illegal or inappropriate time.
|
||||
*/
|
||||
class InvalidStateException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is not implemented.
|
||||
*/
|
||||
class NotImplementedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an invoked method is not supported. For scenarios where
|
||||
* it is sometimes possible to perform the requested operation, see InvalidStateException.
|
||||
*/
|
||||
class NotSupportedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is deprecated.
|
||||
*/
|
||||
class DeprecatedException extends NotSupportedException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a class member (property or method) fails.
|
||||
*/
|
||||
class MemberAccessException extends \Error
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an I/O error occurs.
|
||||
*/
|
||||
class IOException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a file that does not exist on disk.
|
||||
*/
|
||||
class FileNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when part of a file or directory cannot be found.
|
||||
*/
|
||||
class DirectoryNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an argument does not match with the expected value.
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an illegal index was requested.
|
||||
*/
|
||||
class OutOfRangeException extends \OutOfRangeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a value (typically returned by function) does not match with the expected value.
|
||||
*/
|
||||
class UnexpectedValueException extends \UnexpectedValueException
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user