This commit is contained in:
cutemeli
2025-12-22 10:35:30 +00:00
parent 0bfc6c8425
commit 5ce7ca2c5d
38927 changed files with 0 additions and 4594700 deletions

View File

@@ -1,25 +0,0 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitPlesk::getLoader();

View File

@@ -1,120 +0,0 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../laminas/laminas-servicemanager/bin/generate-deps-for-config-factory)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory');
exit(0);
}
}
include __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory';

View File

@@ -1,120 +0,0 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../laminas/laminas-servicemanager/bin/generate-factory-for-class)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-factory-for-class');
exit(0);
}
}
include __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-factory-for-class';

View File

@@ -1,120 +0,0 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../plesk/wappspector/bin/wappspector.php)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/plesk/wappspector/bin/wappspector.php');
exit(0);
}
}
include __DIR__ . '/..'.'/plesk/wappspector/bin/wappspector.php';

View File

@@ -1,120 +0,0 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../symfony/yaml/Resources/bin/yaml-lint)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint');
exit(0);
}
}
include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint';

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2014 Benjamin Jeavons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,67 +0,0 @@
Zxcvbn-PHP is a password strength estimator using pattern matching and minimum entropy calculation. Zxcvbn-PHP is based on the [the Javascript zxcvbn project](https://github.com/dropbox/zxcvbn) from [Dropbox and @lowe](https://blogs.dropbox.com/tech/2012/04/zxcvbn-realistic-password-strength-estimation/). "zxcvbn" is bad password, just like "qwerty" and "123456".
>zxcvbn attempts to give sound password advice through pattern matching and conservative entropy calculations. It finds 10k common passwords, common American names and surnames, common English words, and common patterns like dates, repeats (aaa), sequences (abcd), and QWERTY patterns.
[![Build Status](https://travis-ci.org/bjeavons/zxcvbn-php.png?branch=master)](https://travis-ci.org/bjeavons/zxcvbn-php)
[![Coverage Status](https://coveralls.io/repos/github/bjeavons/zxcvbn-php/badge.svg?branch=master)](https://coveralls.io/github/bjeavons/zxcvbn-php?branch=master)
[![Latest Stable Version](https://poser.pugx.org/bjeavons/zxcvbn-php/v/stable)](https://packagist.org/packages/bjeavons/zxcvbn-php)
[![License](https://poser.pugx.org/bjeavons/zxcvbn-php/license)](https://packagist.org/packages/bjeavons/zxcvbn-php)
## Installation
The library can be installed with [Composer](http://getcomposer.org) by adding it as a dependency to your composer.json file.
Via the command line run:
`composer require bjeavons/zxcvbn-php`
Or in your composer.json add
```json
{
"require": {
"bjeavons/zxcvbn-php": "^1.0"
}
}
```
Then run `composer update` on the command line and include the
autoloader in your PHP scripts so that the ZxcvbnPhp class is available.
```php
require_once 'vendor/autoload.php';
```
## Usage
```php
use ZxcvbnPhp\Zxcvbn;
$userData = [
'Marco',
'marco@example.com'
];
$zxcvbn = new Zxcvbn();
$weak = $zxcvbn->passwordStrength('password', $userData);
echo $weak['score']; // will print 0
$strong = $zxcvbn->passwordStrength('correct horse battery staple');
echo $strong['score']; // will print 4
echo $weak['feedback']['warning']; // will print user-facing feedback on the password, set only when score <= 2
// $weak['feedback']['suggestions'] may contain user-facing suggestions to improve the score
```
Scores are integers from 0 to 4:
* 0 means the password is extremely guessable (within 10^3 guesses), dictionary words like 'password' or 'mother' score a 0
* 1 is still very guessable (guesses < 10^6), an extra character on a dictionary word can score a 1
* 2 is somewhat guessable (guesses < 10^8), provides some protection from unthrottled online attacks
* 3 is safely unguessable (guesses < 10^10), offers moderate protection from offline slow-hash scenario
* 4 is very unguessable (guesses >= 10^10) and provides strong protection from offline slow-hash scenario
### Acknowledgements
Thanks to:
* @lowe for the original [Javascript Zxcvbn](https://github.com/lowe/zxcvbn)
* [@Dreyer's port](https://github.com/Dreyer/php-zxcvbn) for reference for initial implementation
* [@mkopinsky](https://github.com/mkopinsky) for major updates to keep in sync with upstream scoring

View File

@@ -1,34 +0,0 @@
{
"name": "bjeavons/zxcvbn-php",
"type": "library",
"description": "Realistic password strength estimation PHP library based on Zxcvbn JS",
"keywords": ["zxcvbn","password"],
"homepage": "https://github.com/bjeavons/zxcvbn-php",
"license": "MIT",
"authors": [
{
"name": "See contributors",
"homepage": "https://github.com/bjeavons/zxcvbn-php"
}
],
"require": {
"php": "^7.2 | ^8.0 | ^8.1",
"symfony/polyfill-mbstring": ">=1.3.1",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"php-coveralls/php-coveralls": "*",
"squizlabs/php_codesniffer": "3.*",
"phpstan/phpstan": "^2.0"
},
"suggest": {
"ext-gmp": "Required for optimized binomial calculations (also requires PHP >= 7.3)"
},
"autoload": {
"psr-4": { "ZxcvbnPhp\\": "src/" }
},
"autoload-dev": {
"psr-4": { "ZxcvbnPhp\\Test\\": "test/" }
}
}

View File

@@ -1,135 +0,0 @@
#!/usr/bin/python
import os
import sys
import time
import codecs
import json
from operator import itemgetter
def usage():
return '''
usage:
%s data-dir src/Matchers/frequency_lists.json
generates frequency_lists.json (zxcvbn's ranked dictionary file) from word frequency data.
data-dir should contain frequency counts, as generated by the data-scripts/count_* scripts.
DICTIONARIES controls which frequency data will be included and at maximum how many tokens
per dictionary.
If a token appears in multiple frequency lists, it will only appear once in emitted .json file,
in the dictionary where it has lowest rank.
Short tokens, if rare, are also filtered out. If a token has higher rank than 10**(token.length),
it will be excluded because a bruteforce match would have given it a lower guess score.
A warning will be printed if DICTIONARIES contains a dictionary name that doesn't appear in
passed data dir, or vice-versa.
''' % sys.argv[0]
# maps dict name to num words. None value means "include all words"
DICTIONARIES = dict(
us_tv_and_film = 30000,
english_wikipedia = 30000,
passwords = 30000,
surnames = 10000,
male_names = None,
female_names = None,
)
# returns {list_name: {token: rank}}, as tokens and ranks occur in each file.
def parse_frequency_lists(data_dir):
freq_lists = {}
for filename in os.listdir(data_dir):
freq_list_name, ext = os.path.splitext(filename)
if freq_list_name not in DICTIONARIES:
msg = 'Warning: %s appears in %s directory but not in DICTIONARY settings. Excluding.'
print msg % (freq_list_name, data_dir)
continue
token_to_rank = {}
with codecs.open(os.path.join(data_dir, filename), 'r', 'utf8') as f:
for i, line in enumerate(f):
rank = i + 1 # rank starts at 1
token = line.split()[0]
token_to_rank[token] = rank
freq_lists[freq_list_name] = token_to_rank
for freq_list_name in DICTIONARIES:
if freq_list_name not in freq_lists:
msg = 'Warning: %s appears in DICTIONARY settings but not in %s directory. Excluding.'
print msg % (freq_list, data_dir)
return freq_lists
def is_rare_and_short(token, rank):
return rank >= 10**len(token)
def has_comma_or_double_quote(token, rank, lst_name):
# hax, switch to csv or similar if this excludes too much.
# simple comma joining has the advantage of being easy to process
# client-side w/o needing a lib, and so far this only excludes a few
# very high-rank tokens eg 'ps8,000' at rank 74868 from wikipedia list.
if ',' in token or '"' in token:
return True
return False
def filter_frequency_lists(freq_lists):
'''
filters frequency data according to:
- filter out short tokens if they are too rare.
- filter out tokens if they already appear in another dict
at lower rank.
- cut off final freq_list at limits set in DICTIONARIES, if any.
'''
filtered_token_and_rank = {} # maps {name: [(token, rank), ...]}
token_count = {} # maps freq list name: current token count.
for name in freq_lists:
filtered_token_and_rank[name] = []
token_count[name] = 0
minimum_rank = {} # maps token -> lowest token rank across all freq lists
minimum_name = {} # maps token -> freq list name with lowest token rank
for name, token_to_rank in freq_lists.iteritems():
for token, rank in token_to_rank.iteritems():
if token not in minimum_rank:
assert token not in minimum_name
minimum_rank[token] = rank
minimum_name[token] = name
else:
assert token in minimum_name
assert minimum_name[token] != name, 'same token occurs multiple times in %s' % name
min_rank = minimum_rank[token]
if rank < min_rank:
minimum_rank[token] = rank
minimum_name[token] = name
for name, token_to_rank in freq_lists.iteritems():
for token, rank in token_to_rank.iteritems():
if minimum_name[token] != name:
continue
if is_rare_and_short(token, rank) or has_comma_or_double_quote(token, rank, name):
continue
filtered_token_and_rank[name].append((token, rank))
token_count[name] += 1
result = {}
for name, token_rank_pairs in filtered_token_and_rank.iteritems():
token_rank_pairs.sort(key=itemgetter(1))
cutoff_limit = DICTIONARIES[name]
if cutoff_limit and len(token_rank_pairs) > cutoff_limit:
token_rank_pairs = token_rank_pairs[:cutoff_limit]
result[name] = [pair[0] for pair in token_rank_pairs] # discard rank post-sort
return result
def to_kv(lst, lst_name):
val = '"%s".split(",")' % ','.join(lst)
return '%s: %s' % (lst_name, val)
def main():
if len(sys.argv) != 3:
print usage()
sys.exit(0)
data_dir, output_file = sys.argv[1:]
unfiltered_freq_lists = parse_frequency_lists(data_dir)
freq_lists = filter_frequency_lists(unfiltered_freq_lists)
with codecs.open(output_file, 'w', 'utf8') as f:
json.dump(freq_lists, f)
if __name__ == '__main__':
main()

View File

@@ -1,105 +0,0 @@
#!/usr/bin/python
import sys
import json as simplejson
def usage():
return '''
constructs adjacency_graphs.json from QWERTY and DVORAK keyboard layouts
usage:
%s src/Matchers/adjacency_graphs.json
''' % sys.argv[0]
qwerty = r'''
`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+
qQ wW eE rR tT yY uU iI oO pP [{ ]} \|
aA sS dD fF gG hH jJ kK lL ;: '"
zZ xX cC vV bB nN mM ,< .> /?
'''
dvorak = r'''
`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) [{ ]}
'" ,< .> pP yY fF gG cC rR lL /? =+ \|
aA oO eE uU iI dD hH tT nN sS -_
;: qQ jJ kK xX bB mM wW vV zZ
'''
keypad = r'''
/ * -
7 8 9 +
4 5 6
1 2 3
0 .
'''
mac_keypad = r'''
= / *
7 8 9 -
4 5 6 +
1 2 3
0 .
'''
def get_slanted_adjacent_coords(x, y):
'''
returns the six adjacent coordinates on a standard keyboard, where each row is slanted to the
right from the last. adjacencies are clockwise, starting with key to the left, then two keys
above, then right key, then two keys below. (that is, only near-diagonal keys are adjacent,
so g's coordinate is adjacent to those of t,y,b,v, but not those of r,u,n,c.)
'''
return [(x-1, y), (x, y-1), (x+1, y-1), (x+1, y), (x, y+1), (x-1, y+1)]
def get_aligned_adjacent_coords(x, y):
'''
returns the nine clockwise adjacent coordinates on a keypad, where each row is vert aligned.
'''
return [(x-1, y), (x-1, y-1), (x, y-1), (x+1, y-1), (x+1, y), (x+1, y+1), (x, y+1), (x-1, y+1)]
def build_graph(layout_str, slanted):
'''
builds an adjacency graph as a dictionary: {character: [adjacent_characters]}.
adjacent characters occur in a clockwise order.
for example:
* on qwerty layout, 'g' maps to ['fF', 'tT', 'yY', 'hH', 'bB', 'vV']
* on keypad layout, '7' maps to [None, None, None, '=', '8', '5', '4', None]
'''
position_table = {} # maps from tuple (x,y) -> characters at that position.
tokens = layout_str.split()
token_size = len(tokens[0])
x_unit = token_size + 1 # x position unit len is token len plus 1 for the following whitespace.
adjacency_func = get_slanted_adjacent_coords if slanted else get_aligned_adjacent_coords
assert all(len(token) == token_size for token in tokens), 'token len mismatch:\n ' + layout_str
for y, line in enumerate(layout_str.split('\n')):
# the way I illustrated keys above, each qwerty row is indented one space in from the last
slant = y - 1 if slanted else 0
for token in line.split():
x, remainder = divmod(line.index(token) - slant, x_unit)
assert remainder == 0, 'unexpected x offset for %s in:\n%s' % (token, layout_str)
position_table[(x,y)] = token
adjacency_graph = {}
for (x,y), chars in position_table.iteritems():
for char in chars:
adjacency_graph[char] = []
for coord in adjacency_func(x, y):
# position in the list indicates direction
# (for qwerty, 0 is left, 1 is top, 2 is top right, ...)
# for edge chars like 1 or m, insert None as a placeholder when needed
# so that each character in the graph has a same-length adjacency list.
adjacency_graph[char].append(position_table.get(coord, None))
return adjacency_graph
if __name__ == '__main__':
if len(sys.argv) != 2:
print usage()
sys.exit(0)
with open(sys.argv[1], 'w') as f:
data = {
'qwerty': build_graph(qwerty, True),
'dvorak': build_graph(dvorak, True),
'keypad': build_graph(keypad, False),
'mac_keypad': build_graph(mac_keypad, False),
}
simplejson.dump(data, f)
sys.exit(0)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
<?xml version="1.0"?>
<ruleset>
<rule ref="PSR12">
<exclude name="Generic.Files.LineLength.TooLong" />
</rule>
<arg name="ignore" value="vendor/"/>
<arg name="ignore" value="build/"/>
</ruleset>

View File

@@ -1,5 +0,0 @@
parameters:
level: 0
paths:
- src
- test

View File

@@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp;
use ZxcvbnPhp\Matchers\MatchInterface;
/**
* Feedback - gives some user guidance based on the strength
* of a password
*
* @see zxcvbn/src/feedback.coffee
*/
class Feedback
{
/**
* @param int $score
* @param MatchInterface[] $sequence
* @return array
*/
public function getFeedback(int $score, array $sequence): array
{
// starting feedback
if (count($sequence) === 0) {
return [
'warning' => '',
'suggestions' => [
"Use a few words, avoid common phrases",
"No need for symbols, digits, or uppercase letters",
],
];
}
// no feedback if score is good or great.
if ($score > 2) {
return [
'warning' => '',
'suggestions' => [],
];
}
// tie feedback to the longest match for longer sequences
$longestMatch = $sequence[0];
foreach (array_slice($sequence, 1) as $match) {
if (mb_strlen($match->token) > mb_strlen($longestMatch->token)) {
$longestMatch = $match;
}
}
$feedback = $longestMatch->getFeedback(count($sequence) === 1);
$extraFeedback = 'Add another word or two. Uncommon words are better.';
array_unshift($feedback['suggestions'], $extraFeedback);
return $feedback;
}
}

View File

@@ -1,115 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp;
use ZxcvbnPhp\Matchers\BaseMatch;
use ZxcvbnPhp\Matchers\MatchInterface;
class Matcher
{
private const DEFAULT_MATCHERS = [
Matchers\DateMatch::class,
Matchers\DictionaryMatch::class,
Matchers\ReverseDictionaryMatch::class,
Matchers\L33tMatch::class,
Matchers\RepeatMatch::class,
Matchers\SequenceMatch::class,
Matchers\SpatialMatch::class,
Matchers\YearMatch::class,
];
private $additionalMatchers = [];
/**
* Get matches for a password.
*
* @param string $password Password string to match
* @param array $userInputs Array of values related to the user (optional)
* @code array('Alice Smith')
* @endcode
*
* @return MatchInterface[] Array of Match objects.
*
* @see zxcvbn/src/matching.coffee::omnimatch
*/
public function getMatches(string $password, array $userInputs = []): array
{
$matches = [];
foreach ($this->getMatchers() as $matcher) {
$matched = $matcher::match($password, $userInputs);
if (is_array($matched) && !empty($matched)) {
$matches[] = $matched;
}
}
$matches = array_merge([], ...$matches);
self::usortStable($matches, [$this, 'compareMatches']);
return $matches;
}
public function addMatcher(string $className): self
{
if (!is_a($className, MatchInterface::class, true)) {
throw new \InvalidArgumentException(sprintf('Matcher class must implement %s', MatchInterface::class));
}
$this->additionalMatchers[$className] = $className;
return $this;
}
/**
* A stable implementation of usort().
*
* Whether or not the sort() function in JavaScript is stable or not is implementation-defined.
* This means it's impossible for us to match all browsers exactly, but since most browsers implement sort() using
* a stable sorting algorithm, we'll get the highest rate of accuracy by using a stable sort in our code as well.
*
* This function taken from https://github.com/vanderlee/PHP-stable-sort-functions
* Copyright © 2015-2018 Martijn van der Lee (http://martijn.vanderlee.com). MIT License applies.
*
* @param array $array
* @param callable $value_compare_func
* @return bool
*/
public static function usortStable(array &$array, callable $value_compare_func): bool
{
$index = 0;
foreach ($array as &$item) {
$item = [$index++, $item];
}
$result = usort($array, function ($a, $b) use ($value_compare_func) {
$result = $value_compare_func($a[1], $b[1]);
return $result == 0 ? $a[0] - $b[0] : $result;
});
foreach ($array as &$item) {
$item = $item[1];
}
return $result;
}
public static function compareMatches(BaseMatch $a, BaseMatch $b): int
{
$beginDiff = $a->begin - $b->begin;
if ($beginDiff) {
return $beginDiff;
}
return $a->end - $b->end;
}
/**
* Load available Match objects to match against a password.
*
* @return array Array of classes implementing MatchInterface
*/
protected function getMatchers(): array
{
return array_merge(
self::DEFAULT_MATCHERS,
array_values($this->additionalMatchers)
);
}
}

View File

@@ -1,151 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Math\Binomial;
use ZxcvbnPhp\Scorer;
abstract class BaseMatch implements MatchInterface
{
/**
* @var
*/
public $password;
/**
* @var
*/
public $begin;
/**
* @var
*/
public $end;
/**
* @var
*/
public $token;
/**
* @var
*/
public $pattern;
public function __construct(string $password, int $begin, int $end, string $token)
{
$this->password = $password;
$this->begin = $begin;
$this->end = $end;
$this->token = $token;
}
/**
* Get feedback to a user based on the match.
*
* @param bool $isSoleMatch
* Whether this is the only match in the password
* @return array{'warning': string, "suggestions": string[]}
*/
abstract public function getFeedback(bool $isSoleMatch): array;
/**
* Find all occurrences of regular expression in a string.
*
* @param string $string
* String to search.
* @param string $regex
* Regular expression with captures.
* @param int $offset
* @return array
* Array of capture groups. Captures in a group have named indexes: 'begin', 'end', 'token'.
* e.g. fishfish /(fish)/
* array(
* array(
* array('begin' => 0, 'end' => 3, 'token' => 'fish'),
* array('begin' => 0, 'end' => 3, 'token' => 'fish')
* ),
* array(
* array('begin' => 4, 'end' => 7, 'token' => 'fish'),
* array('begin' => 4, 'end' => 7, 'token' => 'fish')
* )
* )
*/
public static function findAll(string $string, string $regex, int $offset = 0): array
{
// $offset is the number of multibyte-aware number of characters to offset, but the offset parameter for
// preg_match_all counts bytes, not characters: to correct this, we need to calculate the byte offset and pass
// that in instead.
$charsBeforeOffset = mb_substr($string, 0, $offset);
$byteOffset = strlen($charsBeforeOffset);
$count = preg_match_all($regex, $string, $matches, PREG_SET_ORDER, $byteOffset);
if (!$count) {
return [];
}
$groups = [];
foreach ($matches as $group) {
$captureBegin = 0;
$match = array_shift($group);
$matchBegin = mb_strpos($string, $match, $offset);
$captures = [
[
'begin' => $matchBegin,
'end' => $matchBegin + mb_strlen($match) - 1,
'token' => $match,
],
];
foreach ($group as $capture) {
$captureBegin = mb_strpos($match, $capture, $captureBegin);
$captures[] = [
'begin' => $matchBegin + $captureBegin,
'end' => $matchBegin + $captureBegin + mb_strlen($capture) - 1,
'token' => $capture,
];
}
$groups[] = $captures;
$offset += mb_strlen($match) - 1;
}
return $groups;
}
/**
* Calculate binomial coefficient (n choose k).
*
* @param int $n
* @param int $k
* @return float
* @deprecated Use {@see Binomial::binom()} instead
*/
public static function binom(int $n, int $k): float
{
return Binomial::binom($n, $k);
}
abstract protected function getRawGuesses(): float;
public function getGuesses(): float
{
return max($this->getRawGuesses(), $this->getMinimumGuesses());
}
protected function getMinimumGuesses(): float
{
if (mb_strlen($this->token) < mb_strlen($this->password)) {
if (mb_strlen($this->token) === 1) {
return Scorer::MIN_SUBMATCH_GUESSES_SINGLE_CHAR;
} else {
return Scorer::MIN_SUBMATCH_GUESSES_MULTI_CHAR;
}
}
return 0;
}
public function getGuessesLog10(): float
{
return log10($this->getGuesses());
}
}

View File

@@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Scorer;
final class Bruteforce extends BaseMatch
{
public const BRUTEFORCE_CARDINALITY = 10;
public $pattern = 'bruteforce';
/**
* @param string $password
* @param array $userInputs
* @return Bruteforce[]
*/
public static function match(string $password, array $userInputs = []): array
{
// Matches entire string.
$match = new static($password, 0, mb_strlen($password) - 1, $password);
return [$match];
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
'warning' => "",
'suggestions' => [
]
];
}
public function getRawGuesses(): float
{
$guesses = pow(self::BRUTEFORCE_CARDINALITY, mb_strlen($this->token));
if ($guesses === INF) {
return PHP_FLOAT_MAX;
}
// small detail: make bruteforce matches at minimum one guess bigger than smallest allowed
// submatch guesses, such that non-bruteforce submatches over the same [i..j] take precedence.
if (mb_strlen($this->token) === 1) {
$minGuesses = Scorer::MIN_SUBMATCH_GUESSES_SINGLE_CHAR + 1;
} else {
$minGuesses = Scorer::MIN_SUBMATCH_GUESSES_MULTI_CHAR + 1;
}
return max($guesses, $minGuesses);
}
}

View File

@@ -1,430 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Matcher;
/** @phpstan-consistent-constructor */
class DateMatch extends BaseMatch
{
public const NUM_YEARS = 119; // Years match against 1900 - 2019
public const NUM_MONTHS = 12;
public const NUM_DAYS = 31;
public const MIN_YEAR = 1000;
public const MAX_YEAR = 2050;
public const MIN_YEAR_SPACE = 20;
public $pattern = 'date';
private static $DATE_SPLITS = [
4 => [ # For length-4 strings, eg 1191 or 9111, two ways to split:
[1, 2], # 1 1 91 (2nd split starts at index 1, 3rd at index 2)
[2, 3], # 91 1 1
],
5 => [
[1, 3], # 1 11 91
[2, 3] # 11 1 91
],
6 => [
[1, 2], # 1 1 1991
[2, 4], # 11 11 91
[4, 5], # 1991 1 1
],
7 => [
[1, 3], # 1 11 1991
[2, 3], # 11 1 1991
[4, 5], # 1991 1 11
[4, 6], # 1991 11 1
],
8 => [
[2, 4], # 11 11 1991
[4, 6], # 1991 11 11
],
];
protected const DATE_NO_SEPARATOR = '/^\d{4,8}$/u';
/**
* (\d{1,4}) # day, month, year
* ([\s\/\\\\_.-]) # separator
* (\d{1,2}) # day, month
* \2 # same separator
* (\d{1,4}) # day, month, year
*/
protected const DATE_WITH_SEPARATOR = '/^(\d{1,4})([\s\/\\\\_.-])(\d{1,2})\2(\d{1,4})$/u';
/** @var int The day portion of the date in the token. */
public $day;
/** @var int The month portion of the date in the token. */
public $month;
/** @var int The year portion of the date in the token. */
public $year;
/** @var string The separator used for the date in the token. */
public $separator;
/**
* Match occurences of dates in a password
*
* @param string $password
* @param array $userInputs
* @return DateMatch[]
*/
public static function match(string $password, array $userInputs = []): array
{
# a "date" is recognized as:
# any 3-tuple that starts or ends with a 2- or 4-digit year,
# with 2 or 0 separator chars (1.1.91 or 1191),
# maybe zero-padded (01-01-91 vs 1-1-91),
# a month between 1 and 12,
# a day between 1 and 31.
#
# note: this isn't true date parsing in that "feb 31st" is allowed,
# this doesn't check for leap years, etc.
#
# recipe:
# start with regex to find maybe-dates, then attempt to map the integers
# onto month-day-year to filter the maybe-dates into dates.
# finally, remove matches that are substrings of other matches to reduce noise.
#
# note: instead of using a lazy or greedy regex to find many dates over the full string,
# this uses a ^...$ regex against every substring of the password -- less performant but leads
# to every possible date match.
$matches = [];
$dates = static::removeRedundantMatches(array_merge(
static::datesWithoutSeparators($password),
static::datesWithSeparators($password)
));
foreach ($dates as $date) {
$matches[] = new static($password, $date['begin'], $date['end'], $date['token'], $date);
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
'warning' => "Dates are often easy to guess",
'suggestions' => [
'Avoid dates and years that are associated with you'
]
];
}
/**
* @param string $password
* @param int $begin
* @param int $end
* @param string $token
* @param array $params An array with keys: [day, month, year, separator].
*/
public function __construct(string $password, int $begin, int $end, string $token, array $params)
{
parent::__construct($password, $begin, $end, $token);
$this->day = $params['day'];
$this->month = $params['month'];
$this->year = $params['year'];
$this->separator = $params['separator'];
}
/**
* Find dates with separators in a password.
*
* @param string $password
*
* @return array
*/
protected static function datesWithSeparators(string $password): array
{
$matches = [];
$length = mb_strlen($password);
// dates with separators are between length 6 '1/1/91' and 10 '11/11/1991'
for ($begin = 0; $begin < $length - 5; $begin++) {
for ($end = $begin + 5; $end - $begin < 10 && $end < $length; $end++) {
$token = mb_substr($password, $begin, $end - $begin + 1);
if (!preg_match(static::DATE_WITH_SEPARATOR, $token, $captures)) {
continue;
}
$date = static::checkDate([
(int) $captures[1],
(int) $captures[3],
(int) $captures[4]
]);
if ($date === false) {
continue;
}
$matches[] = [
'begin' => $begin,
'end' => $end,
'token' => $token,
'separator' => $captures[2],
'day' => $date['day'],
'month' => $date['month'],
'year' => $date['year'],
];
}
}
return $matches;
}
/**
* Find dates without separators in a password.
*
* @param string $password
*
* @return array
*/
protected static function datesWithoutSeparators(string $password): array
{
$matches = [];
$length = mb_strlen($password);
// dates without separators are between length 4 '1191' and 8 '11111991'
for ($begin = 0; $begin < $length - 3; $begin++) {
for ($end = $begin + 3; $end - $begin < 8 && $end < $length; $end++) {
$token = mb_substr($password, $begin, $end - $begin + 1);
if (!preg_match(static::DATE_NO_SEPARATOR, $token)) {
continue;
}
$candidates = [];
$possibleSplits = static::$DATE_SPLITS[mb_strlen($token)];
foreach ($possibleSplits as $splitPositions) {
$day = (int)mb_substr($token, 0, $splitPositions[0]);
$month = (int)mb_substr($token, $splitPositions[0], $splitPositions[1] - $splitPositions[0]);
$year = (int)mb_substr($token, $splitPositions[1]);
$date = static::checkDate([$day, $month, $year]);
if ($date !== false) {
$candidates[] = $date;
}
}
if (empty($candidates)) {
continue;
}
// at this point: different possible dmy mappings for the same i,j substring.
// match the candidate date that likely takes the fewest guesses: a year closest to
// the current year.
//
// ie, considering '111504', prefer 11-15-04 to 1-1-1504
// (interpreting '04' as 2004)
$bestCandidate = $candidates[0];
$minDistance = self::getDistanceForMatchCandidate($bestCandidate);
foreach ($candidates as $candidate) {
$distance = self::getDistanceForMatchCandidate($candidate);
if ($distance < $minDistance) {
$bestCandidate = $candidate;
$minDistance = $distance;
}
}
$day = $bestCandidate['day'];
$month = $bestCandidate['month'];
$year = $bestCandidate['year'];
$matches[] = [
'begin' => $begin,
'end' => $end,
'token' => $token,
'separator' => '',
'day' => $day,
'month' => $month,
'year' => $year
];
}
}
return $matches;
}
/**
* @param array $candidate
* @return int Returns the number of years between the detected year and the current year for a candidate.
*/
protected static function getDistanceForMatchCandidate(array $candidate): int
{
return abs((int)$candidate['year'] - static::getReferenceYear());
}
public static function getReferenceYear(): int
{
return (int)date('Y');
}
/**
* @param int[] $ints Three numbers in an array representing day, month and year (not necessarily in that order).
* @return array|bool Returns an associative array containing 'day', 'month' and 'year' keys, or false if the
* provided date array is invalid.
*/
protected static function checkDate(array $ints)
{
# given a 3-tuple, discard if:
# middle int is over 31 (for all dmy formats, years are never allowed in the middle)
# middle int is zero
# any int is over the max allowable year
# any int is over two digits but under the min allowable year
# 2 ints are over 31, the max allowable day
# 2 ints are zero
# all ints are over 12, the max allowable month
if ($ints[1] > 31 || $ints[1] <= 0) {
return false;
}
$invalidYear = count(array_filter($ints, function (int $int): bool {
return ($int >= 100 && $int < static::MIN_YEAR)
|| ($int > static::MAX_YEAR);
}));
if ($invalidYear > 0) {
return false;
}
$over12 = count(array_filter($ints, function (int $int): bool {
return $int > 12;
}));
$over31 = count(array_filter($ints, function (int $int): bool {
return $int > 31;
}));
$under1 = count(array_filter($ints, function (int $int): bool {
return $int <= 0;
}));
if ($over31 >= 2 || $over12 == 3 || $under1 >= 2) {
return false;
}
# first look for a four digit year: yyyy + daymonth or daymonth + yyyy
$possibleYearSplits = [
[$ints[2], [$ints[0], $ints[1]]], // year last
[$ints[0], [$ints[1], $ints[2]]], // year first
];
foreach ($possibleYearSplits as [$year, $rest]) {
if ($year >= static::MIN_YEAR && $year <= static::MAX_YEAR) {
if ($dm = static::mapIntsToDayMonth($rest)) {
return [
'year' => $year,
'month' => $dm['month'],
'day' => $dm['day'],
];
}
# for a candidate that includes a four-digit year,
# when the remaining ints don't match to a day and month,
# it is not a date.
return false;
}
}
foreach ($possibleYearSplits as [$year, $rest]) {
if ($dm = static::mapIntsToDayMonth($rest)) {
return [
'year' => static::twoToFourDigitYear($year),
'month' => $dm['month'],
'day' => $dm['day'],
];
}
}
return false;
}
/**
* @param int[] $ints Two numbers in an array representing day and month (not necessarily in that order).
* @return array|bool Returns an associative array containing 'day' and 'month' keys, or false if any combination
* of the two numbers does not match a day and month.
*/
protected static function mapIntsToDayMonth(array $ints)
{
foreach ([$ints, array_reverse($ints)] as [$d, $m]) {
if ($d >= 1 && $d <= 31 && $m >= 1 && $m <= 12) {
return [
'day' => $d,
'month' => $m
];
}
}
return false;
}
/**
* @param int $year A two digit number representing a year.
* @return int Returns the most likely four digit year for the provided number.
*/
protected static function twoToFourDigitYear(int $year): int
{
if ($year > 99) {
return $year;
}
if ($year > 50) {
// 87 -> 1987
return $year + 1900;
}
// 15 -> 2015
return $year + 2000;
}
/**
* Removes date matches that are strict substrings of others.
*
* This is helpful because the match function will contain matches for all valid date strings in a way that is
* tricky to capture with regexes only. While thorough, it will contain some unintuitive noise:
*
* '2015_06_04', in addition to matching 2015_06_04, will also contain
* 5(!) other date matches: 15_06_04, 5_06_04, ..., even 2015 (matched as 5/1/2020)
*
* @param array $matches An array of matches (not Match objects)
* @return array The provided array of matches, but with matches that are strict substrings of others removed.
*/
protected static function removeRedundantMatches(array $matches): array
{
return array_filter($matches, function (array $match) use ($matches): bool {
foreach ($matches as $otherMatch) {
if ($match === $otherMatch) {
continue;
}
if ($otherMatch['begin'] <= $match['begin'] && $otherMatch['end'] >= $match['end']) {
return false;
}
}
return true;
});
}
protected function getRawGuesses(): float
{
// base guesses: (year distance from REFERENCE_YEAR) * num_days * num_years
$yearSpace = max(abs($this->year - static::getReferenceYear()), static::MIN_YEAR_SPACE);
$guesses = $yearSpace * 365;
// add factor of 4 for separator selection (one of ~4 choices)
if ($this->separator) {
$guesses *= 4;
}
return $guesses;
}
}

View File

@@ -1,236 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Matcher;
use ZxcvbnPhp\Math\Binomial;
/** @phpstan-consistent-constructor */
class DictionaryMatch extends BaseMatch
{
public $pattern = 'dictionary';
/** @var string The name of the dictionary that the token was found in. */
public $dictionaryName;
/** @var int The rank of the token in the dictionary. */
public $rank;
/** @var string The word that was matched from the dictionary. */
public $matchedWord;
/** @var bool Whether or not the matched word was reversed in the token. */
public $reversed = false;
/** @var bool Whether or not the token contained l33t substitutions. */
public $l33t = false;
/** @var array A cache of the frequency_lists json file */
protected static $rankedDictionaries = [];
protected const START_UPPER = "/^[A-Z][^A-Z]+$/u";
protected const END_UPPER = "/^[^A-Z]+[A-Z]$/u";
protected const ALL_UPPER = "/^[^a-z]+$/u";
protected const ALL_LOWER = "/^[^A-Z]+$/u";
/**
* Match occurrences of dictionary words in password.
*
* @param string $password
* @param array $userInputs
* @param array $rankedDictionaries
* @return DictionaryMatch[]
*/
public static function match(string $password, array $userInputs = [], array $rankedDictionaries = []): array
{
$matches = [];
if ($rankedDictionaries) {
$dicts = $rankedDictionaries;
} else {
$dicts = static::getRankedDictionaries();
}
if (!empty($userInputs)) {
$dicts['user_inputs'] = [];
foreach ($userInputs as $rank => $input) {
$input_lower = mb_strtolower($input);
$dicts['user_inputs'][$input_lower] = $rank + 1; // rank starts at 1, not 0
}
}
foreach ($dicts as $name => $dict) {
$results = static::dictionaryMatch($password, $dict);
foreach ($results as $result) {
$result['dictionary_name'] = $name;
$matches[] = new static($password, $result['begin'], $result['end'], $result['token'], $result);
}
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}
/**
* @param string $password
* @param int $begin
* @param int $end
* @param string $token
* @param array $params An array with keys: [dictionary_name, matched_word, rank].
*/
public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
{
parent::__construct($password, $begin, $end, $token);
if (!empty($params)) {
$this->dictionaryName = $params['dictionary_name'] ?? '';
$this->matchedWord = $params['matched_word'] ?? '';
$this->rank = $params['rank'] ?? 0;
}
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
$startUpper = '/^[A-Z][^A-Z]+$/u';
$allUpper = '/^[^a-z]+$/u';
$feedback = [
'warning' => $this->getFeedbackWarning($isSoleMatch),
'suggestions' => []
];
if (preg_match($startUpper, $this->token)) {
$feedback['suggestions'][] = "Capitalization doesn't help very much";
} elseif (preg_match($allUpper, $this->token) && mb_strtolower($this->token) != $this->token) {
$feedback['suggestions'][] = "All-uppercase is almost as easy to guess as all-lowercase";
}
return $feedback;
}
public function getFeedbackWarning(bool $isSoleMatch): string
{
switch ($this->dictionaryName) {
case 'passwords':
if ($isSoleMatch && !$this->l33t && !$this->reversed) {
if ($this->rank <= 10) {
return 'This is a top-10 common password';
} elseif ($this->rank <= 100) {
return 'This is a top-100 common password';
} else {
return 'This is a very common password';
}
} elseif ($this->getGuessesLog10() <= 4) {
return 'This is similar to a commonly used password';
}
break;
case 'english_wikipedia':
if ($isSoleMatch) {
return 'A word by itself is easy to guess';
}
break;
case 'surnames':
case 'male_names':
case 'female_names':
if ($isSoleMatch) {
return 'Names and surnames by themselves are easy to guess';
} else {
return 'Common names and surnames are easy to guess';
}
}
return '';
}
/**
* Attempts to find the provided password (as well as all possible substrings) in a dictionary.
*
* @param string $password
* @param array $dict
* @return array
*/
protected static function dictionaryMatch(string $password, array $dict): array
{
$result = [];
$length = mb_strlen($password);
$pw_lower = mb_strtolower($password);
foreach (range(0, $length - 1) as $i) {
foreach (range($i, $length - 1) as $j) {
$word = mb_substr($pw_lower, $i, $j - $i + 1);
if (isset($dict[$word])) {
$result[] = [
'begin' => $i,
'end' => $j,
'token' => mb_substr($password, $i, $j - $i + 1),
'matched_word' => $word,
'rank' => $dict[$word],
];
}
}
}
return $result;
}
/**
* Load ranked frequency dictionaries.
*
* @return array
*/
protected static function getRankedDictionaries(): array
{
if (empty(self::$rankedDictionaries)) {
$json = file_get_contents(dirname(__FILE__) . '/frequency_lists.json');
$data = json_decode($json, true);
$rankedLists = [];
foreach ($data as $name => $words) {
$rankedLists[$name] = array_combine($words, range(1, count($words)));
}
self::$rankedDictionaries = $rankedLists;
}
return self::$rankedDictionaries;
}
protected function getRawGuesses(): float
{
$guesses = $this->rank;
$guesses *= $this->getUppercaseVariations();
return $guesses;
}
protected function getUppercaseVariations(): float
{
$word = $this->token;
if (preg_match(self::ALL_LOWER, $word) || mb_strtolower($word) === $word) {
return 1;
}
// a capitalized word is the most common capitalization scheme,
// so it only doubles the search space (uncapitalized + capitalized).
// allcaps and end-capitalized are common enough too, underestimate as 2x factor to be safe.
foreach (array(self::START_UPPER, self::END_UPPER, self::ALL_UPPER) as $regex) {
if (preg_match($regex, $word)) {
return 2;
}
}
// otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters
// with U uppercase letters or less. or, if there's more uppercase than lower (for eg. PASSwORD),
// the number of ways to lowercase U+L letters with L lowercase letters or less.
$uppercase = count(array_filter(preg_split('//u', $word, -1, PREG_SPLIT_NO_EMPTY), 'ctype_upper'));
$lowercase = count(array_filter(preg_split('//u', $word, -1, PREG_SPLIT_NO_EMPTY), 'ctype_lower'));
$variations = 0;
for ($i = 1; $i <= min($uppercase, $lowercase); $i++) {
$variations += Binomial::binom($uppercase + $lowercase, $i);
}
return $variations;
}
}

View File

@@ -1,244 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Matcher;
use ZxcvbnPhp\Math\Binomial;
/**
* Class L33tMatch extends DictionaryMatch to translate l33t into dictionary words for matching.
* @package ZxcvbnPhp\Matchers
*/
class L33tMatch extends DictionaryMatch
{
/** @var array An array of substitutions made to get from the token to the dictionary word. */
public $sub = [];
/** @var string A user-readable string that shows which substitutions were detected. */
public $subDisplay;
/** @var bool Whether or not the token contained l33t substitutions. */
public $l33t = true;
/**
* Match occurences of l33t words in password to dictionary words.
*
* @param string $password
* @param array $userInputs
* @param array $rankedDictionaries
* @return L33tMatch[]
*/
public static function match(string $password, array $userInputs = [], array $rankedDictionaries = []): array
{
// Translate l33t password and dictionary match the translated password.
$maps = array_filter(static::getL33tSubstitutions(static::getL33tSubtable($password)));
if (empty($maps)) {
return [];
}
$matches = [];
if (!$rankedDictionaries) {
$rankedDictionaries = static::getRankedDictionaries();
}
foreach ($maps as $map) {
$translatedWord = static::translate($password, $map);
/** @var L33tMatch[] $results */
$results = parent::match($translatedWord, $userInputs, $rankedDictionaries);
foreach ($results as $match) {
$token = mb_substr($password, $match->begin, $match->end - $match->begin + 1);
# only return the matches that contain an actual substitution
if (mb_strtolower($token) === $match->matchedWord) {
continue;
}
# filter single-character l33t matches to reduce noise.
# otherwise '1' matches 'i', '4' matches 'a', both very common English words
# with low dictionary rank.
if (mb_strlen($token) === 1) {
continue;
}
$display = [];
foreach ($map as $i => $t) {
if (mb_strpos($token, (string)$i) !== false) {
$match->sub[$i] = $t;
$display[] = "$i -> $t";
}
}
$match->token = $token;
$match->subDisplay = implode(', ', $display);
$matches[] = $match;
}
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}
/**
* @param string $password
* @param int $begin
* @param int $end
* @param string $token
* @param array $params An array with keys: [sub, sub_display].
*/
public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
{
parent::__construct($password, $begin, $end, $token, $params);
if (!empty($params)) {
$this->sub = $params['sub'] ?? [];
$this->subDisplay = $params['sub_display'] ?? null;
}
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
$feedback = parent::getFeedback($isSoleMatch);
$feedback['suggestions'][] = "Predictable substitutions like '@' instead of 'a' don't help very much";
return $feedback;
}
/**
* @param string $string
* @param array $map
* @return string
*/
protected static function translate(string $string, array $map): string
{
return str_replace(array_keys($map), array_values($map), $string);
}
protected static function getL33tTable(): array
{
return [
'a' => ['4', '@'],
'b' => ['8'],
'c' => ['(', '{', '[', '<'],
'e' => ['3'],
'g' => ['6', '9'],
'i' => ['1', '!', '|'],
'l' => ['1', '|', '7'],
'o' => ['0'],
's' => ['$', '5'],
't' => ['+', '7'],
'x' => ['%'],
'z' => ['2'],
];
}
protected static function getL33tSubtable(string $password): array
{
// The preg_split call below is a multibyte compatible version of str_split
$passwordChars = array_unique(preg_split('//u', $password, -1, PREG_SPLIT_NO_EMPTY));
$subTable = [];
$table = static::getL33tTable();
foreach ($table as $letter => $substitutions) {
foreach ($substitutions as $sub) {
if (in_array($sub, $passwordChars)) {
$subTable[$letter][] = $sub;
}
}
}
return $subTable;
}
protected static function getL33tSubstitutions(array $subtable): array
{
$keys = array_keys($subtable);
$substitutions = self::substitutionTableHelper($subtable, $keys, [[]]);
// Converts the substitution arrays from [ [a, b], [c, d] ] to [ a => b, c => d ]
$substitutions = array_map(function (array $subArray): array {
return array_combine(array_column($subArray, 0), array_column($subArray, 1));
}, $substitutions);
return $substitutions;
}
protected static function substitutionTableHelper(array $table, array $keys, array $subs): array
{
if (empty($keys)) {
return $subs;
}
$firstKey = array_shift($keys);
$otherKeys = $keys;
$nextSubs = [];
foreach ($table[$firstKey] as $l33tCharacter) {
foreach ($subs as $sub) {
$dupL33tIndex = false;
foreach ($sub as $index => $char) {
if ($char[0] === $l33tCharacter) {
$dupL33tIndex = $index;
break;
}
}
if ($dupL33tIndex === false) {
$subExtension = $sub;
$subExtension[] = [$l33tCharacter, $firstKey];
$nextSubs[] = $subExtension;
} else {
$subAlternative = $sub;
array_splice($subAlternative, $dupL33tIndex, 1);
$subAlternative[] = [$l33tCharacter, $firstKey];
$nextSubs[] = $sub;
$nextSubs[] = $subAlternative;
}
}
}
$nextSubs = array_unique($nextSubs, SORT_REGULAR);
return self::substitutionTableHelper($table, $otherKeys, $nextSubs);
}
protected function getRawGuesses(): float
{
return parent::getRawGuesses() * $this->getL33tVariations();
}
protected function getL33tVariations(): float
{
$variations = 1;
foreach ($this->sub as $substitution => $letter) {
$characters = preg_split('//u', mb_strtolower($this->token), -1, PREG_SPLIT_NO_EMPTY);
$subbed = count(array_filter($characters, function ($character) use ($substitution) {
return (string)$character === (string)$substitution;
}));
$unsubbed = count(array_filter($characters, function ($character) use ($letter) {
return (string)$character === (string)$letter;
}));
if ($subbed === 0 || $unsubbed === 0) {
// for this sub, password is either fully subbed (444) or fully unsubbed (aaa)
// treat that as doubling the space (attacker needs to try fully subbed chars in addition to
// unsubbed.)
$variations *= 2;
} else {
$possibilities = 0;
for ($i = 1; $i <= min($subbed, $unsubbed); $i++) {
$possibilities += Binomial::binom($subbed + $unsubbed, $i);
}
$variations *= $possibilities;
}
}
return $variations;
}
}

View File

@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
interface MatchInterface
{
/**
* Match this password.
*
* @param string $password Password to check for match
* @param array $userInputs Array of values related to the user (optional)
* @code array('Alice Smith')
* @endcode
*
* @return array|BaseMatch[] Array of Match objects
*/
public static function match(string $password, array $userInputs = []): array;
public function getGuesses(): float;
public function getGuessesLog10(): float;
}

View File

@@ -1,127 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Matcher;
use ZxcvbnPhp\Scorer;
/** @phpstan-consistent-constructor */
class RepeatMatch extends BaseMatch
{
public const GREEDY_MATCH = '/(.+)\1+/u';
public const LAZY_MATCH = '/(.+?)\1+/u';
public const ANCHORED_LAZY_MATCH = '/^(.+?)\1+$/u';
public $pattern = 'repeat';
/** @var MatchInterface[] An array of matches for the repeated section itself. */
public $baseMatches = [];
/** @var int The number of guesses required for the repeated section itself. */
public $baseGuesses;
/** @var int The number of times the repeated section is repeated. */
public $repeatCount;
/** @var string The string that was repeated in the token. */
public $repeatedChar;
/**
* Match 3 or more repeated characters.
*
* @param string $password
* @param array $userInputs
* @return RepeatMatch[]
*/
public static function match(string $password, array $userInputs = []): array
{
$matches = [];
$lastIndex = 0;
while ($lastIndex < mb_strlen($password)) {
$greedyMatches = self::findAll($password, self::GREEDY_MATCH, $lastIndex);
$lazyMatches = self::findAll($password, self::LAZY_MATCH, $lastIndex);
if (empty($greedyMatches)) {
break;
}
if (mb_strlen($greedyMatches[0][0]['token']) > mb_strlen($lazyMatches[0][0]['token'])) {
$match = $greedyMatches[0];
preg_match(self::ANCHORED_LAZY_MATCH, $match[0]['token'], $anchoredMatch);
$repeatedChar = $anchoredMatch[1];
} else {
$match = $lazyMatches[0];
$repeatedChar = $match[1]['token'];
}
$scorer = new Scorer();
$matcher = new Matcher();
$baseAnalysis = $scorer->getMostGuessableMatchSequence($repeatedChar, $matcher->getMatches($repeatedChar));
$baseMatches = $baseAnalysis['sequence'];
$baseGuesses = $baseAnalysis['guesses'];
$repeatCount = mb_strlen($match[0]['token']) / mb_strlen($repeatedChar);
$matches[] = new static(
$password,
$match[0]['begin'],
$match[0]['end'],
$match[0]['token'],
[
'repeated_char' => $repeatedChar,
'base_guesses' => $baseGuesses,
'base_matches' => $baseMatches,
'repeat_count' => $repeatCount,
]
);
$lastIndex = $match[0]['end'] + 1;
}
return $matches;
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
$warning = mb_strlen($this->repeatedChar) == 1
? 'Repeats like "aaa" are easy to guess'
: 'Repeats like "abcabcabc" are only slightly harder to guess than "abc"';
return [
'warning' => $warning,
'suggestions' => [
'Avoid repeated words and characters',
],
];
}
/**
* @param string $password
* @param int $begin
* @param int $end
* @param string $token
* @param array $params An array with keys: [repeated_char, base_guesses, base_matches, repeat_count].
*/
public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
{
parent::__construct($password, $begin, $end, $token);
if (!empty($params)) {
$this->repeatedChar = $params['repeated_char'] ?? '';
$this->baseGuesses = $params['base_guesses'] ?? 0;
$this->baseMatches = $params['base_matches'] ?? [];
$this->repeatCount = $params['repeat_count'] ?? 0;
}
}
protected function getRawGuesses(): float
{
return $this->baseGuesses * $this->repeatCount;
}
}

View File

@@ -1,71 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Matcher;
class ReverseDictionaryMatch extends DictionaryMatch
{
/** @var bool Whether or not the matched word was reversed in the token. */
public $reversed = true;
/**
* Match occurences of reversed dictionary words in password.
*
* @param $password
* @param array $userInputs
* @param array $rankedDictionaries
* @return ReverseDictionaryMatch[]
*/
public static function match(string $password, array $userInputs = [], array $rankedDictionaries = []): array
{
/** @var ReverseDictionaryMatch[] $matches */
$matches = parent::match(self::mbStrRev($password), $userInputs, $rankedDictionaries);
foreach ($matches as $match) {
$tempBegin = $match->begin;
// Change the token, password and [begin, end] values to match the original password
$match->token = self::mbStrRev($match->token);
$match->password = self::mbStrRev($match->password);
$match->begin = mb_strlen($password) - 1 - $match->end;
$match->end = mb_strlen($password) - 1 - $tempBegin;
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}
protected function getRawGuesses(): float
{
return parent::getRawGuesses() * 2;
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
$feedback = parent::getFeedback($isSoleMatch);
if (mb_strlen($this->token) >= 4) {
$feedback['suggestions'][] = "Reversed words aren't much harder to guess";
}
return $feedback;
}
public static function mbStrRev(string $string, ?string $encoding = null): string
{
if ($encoding === null) {
$encoding = mb_detect_encoding($string) ?: 'UTF-8';
}
$length = mb_strlen($string, $encoding);
$reversed = '';
while ($length-- > 0) {
$reversed .= mb_substr($string, $length, 1, $encoding);
}
return $reversed;
}
}

View File

@@ -1,142 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
/** @phpstan-consistent-constructor */
class SequenceMatch extends BaseMatch
{
public const MAX_DELTA = 5;
public $pattern = 'sequence';
/** @var string The name of the detected sequence. */
public $sequenceName;
/** @var int The number of characters in the complete sequence space. */
public $sequenceSpace;
/** @var bool True if the sequence is ascending, and false if it is descending. */
public $ascending;
/**
* Match sequences of three or more characters.
*
* @param string $password
* @param array $userInputs
* @return SequenceMatch[]
*/
public static function match(string $password, array $userInputs = []): array
{
$matches = [];
$passwordLength = mb_strlen($password);
if ($passwordLength <= 1) {
return [];
}
$begin = 0;
$lastDelta = null;
for ($index = 1; $index < $passwordLength; $index++) {
$delta = mb_ord(mb_substr($password, $index, 1)) - mb_ord(mb_substr($password, $index - 1, 1));
if ($lastDelta === null) {
$lastDelta = $delta;
}
if ($lastDelta === $delta) {
continue;
}
static::findSequenceMatch($password, $begin, $index - 1, $lastDelta, $matches);
$begin = $index - 1;
$lastDelta = $delta;
}
static::findSequenceMatch($password, $begin, $passwordLength - 1, $lastDelta, $matches);
return $matches;
}
public static function findSequenceMatch(string $password, int $begin, int $end, int $delta, array &$matches)
{
if ($end - $begin > 1 || abs($delta) === 1) {
if (abs($delta) > 0 && abs($delta) <= self::MAX_DELTA) {
$token = mb_substr($password, $begin, $end - $begin + 1);
if (preg_match('/^[a-z]+$/u', $token)) {
$sequenceName = 'lower';
$sequenceSpace = 26;
} elseif (preg_match('/^[A-Z]+$/u', $token)) {
$sequenceName = 'upper';
$sequenceSpace = 26;
} elseif (preg_match('/^\d+$/u', $token)) {
$sequenceName = 'digits';
$sequenceSpace = 10;
} else {
$sequenceName = 'unicode';
$sequenceSpace = 26;
}
$matches[] = new static($password, $begin, $end, $token, [
'sequenceName' => $sequenceName,
'sequenceSpace' => $sequenceSpace,
'ascending' => $delta > 0,
]);
}
}
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
'warning' => "Sequences like abc or 6543 are easy to guess",
'suggestions' => [
'Avoid sequences'
]
];
}
/**
* @param string $password
* @param int $begin
* @param int $end
* @param string $token
* @param array $params An array with keys: [sequenceName, sequenceSpace, ascending].
*/
public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
{
parent::__construct($password, $begin, $end, $token);
if (!empty($params)) {
$this->sequenceName = $params['sequenceName'] ?? '';
$this->sequenceSpace = $params['sequenceSpace'] ?? 0;
$this->ascending = $params['ascending'] ?? false;
}
}
protected function getRawGuesses(): float
{
$firstCharacter = mb_substr($this->token, 0, 1);
$guesses = 0;
if (in_array($firstCharacter, array('a', 'A', 'z', 'Z', '0', '1', '9'), true)) {
$guesses += 4; // lower guesses for obvious starting points
} elseif (ctype_digit($firstCharacter)) {
$guesses += 10; // digits
} else {
// could give a higher base for uppercase,
// assigning 26 to both upper and lower sequences is more conservative
$guesses += 26;
}
if (!$this->ascending) {
// need to try a descending sequence in addition to every ascending sequence ->
// 2x guesses
$guesses *= 2;
}
return $guesses * mb_strlen($this->token);
}
}

View File

@@ -1,265 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Matcher;
use ZxcvbnPhp\Math\Binomial;
/** @phpstan-consistent-constructor */
class SpatialMatch extends BaseMatch
{
public const SHIFTED_CHARACTERS = '~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?';
// Preset properties since adjacency graph is constant for qwerty keyboard and keypad.
public const KEYBOARD_STARTING_POSITION = 94;
public const KEYPAD_STARTING_POSITION = 15;
public const KEYBOARD_AVERAGE_DEGREES = 4.5957446809; // 432 / 94
public const KEYPAD_AVERAGE_DEGREES = 5.0666666667; // 76 / 15
public $pattern = 'spatial';
/** @var int The number of characters the shift key was held for in the token. */
public $shiftedCount;
/** @var int The number of turns on the keyboard required to complete the token. */
public $turns;
/** @var string The keyboard layout that the token is a spatial match on. */
public $graph;
/** @var array A cache of the adjacency_graphs json file */
protected static $adjacencyGraphs = [];
/**
* Match spatial patterns based on keyboard layouts (e.g. qwerty, dvorak, keypad).
*
* @param string $password
* @param array $userInputs
* @param array $graphs
* @return SpatialMatch[]
*/
public static function match(string $password, array $userInputs = [], array $graphs = []): array
{
$matches = [];
if (!$graphs) {
$graphs = static::getAdjacencyGraphs();
}
foreach ($graphs as $name => $graph) {
$results = static::graphMatch($password, $graph, $name);
foreach ($results as $result) {
$result['graph'] = $name;
$matches[] = new static($password, $result['begin'], $result['end'], $result['token'], $result);
}
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
$warning = $this->turns == 1
? 'Straight rows of keys are easy to guess'
: 'Short keyboard patterns are easy to guess';
return [
'warning' => $warning,
'suggestions' => [
'Use a longer keyboard pattern with more turns'
]
];
}
/**
* @param string $password
* @param int $begin
* @param int $end
* @param string $token
* @param array $params An array with keys: [graph (required), shifted_count, turns].
*/
public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
{
parent::__construct($password, $begin, $end, $token);
$this->graph = $params['graph'];
if (!empty($params)) {
$this->shiftedCount = $params['shifted_count'] ?? null;
$this->turns = $params['turns'] ?? null;
}
}
/**
* Match spatial patterns in a adjacency graph.
* @param string $password
* @param array $graph
* @param string $graphName
* @return array
*/
protected static function graphMatch(string $password, array $graph, string $graphName): array
{
$result = [];
$i = 0;
$passwordLength = mb_strlen($password);
while ($i < $passwordLength - 1) {
$j = $i + 1;
$lastDirection = null;
$turns = 0;
$shiftedCount = 0;
// Check if the initial character is shifted
if ($graphName === 'qwerty' || $graphName === 'dvorak') {
if (mb_strpos(self::SHIFTED_CHARACTERS, mb_substr($password, $i, 1)) !== false) {
$shiftedCount++;
}
}
while (true) {
$prevChar = mb_substr($password, $j - 1, 1);
$found = false;
$curDirection = -1;
$adjacents = $graph[$prevChar] ?? [];
// Consider growing pattern by one character if j hasn't gone over the edge.
if ($j < $passwordLength) {
$curChar = mb_substr($password, $j, 1);
foreach ($adjacents as $adj) {
$curDirection += 1;
if ($adj === null) {
continue;
}
$curCharPos = static::indexOf($adj, $curChar);
if ($curCharPos !== -1) {
$found = true;
$foundDirection = $curDirection;
if ($curCharPos === 1) {
// index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc.
// for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted.
$shiftedCount += 1;
}
if ($lastDirection !== $foundDirection) {
// adding a turn is correct even in the initial case when last_direction is null:
// every spatial pattern starts with a turn.
$turns += 1;
$lastDirection = $foundDirection;
}
break;
}
}
}
// if the current pattern continued, extend j and try to grow again
if ($found) {
$j += 1;
} else {
// otherwise push the pattern discovered so far, if any...
// Ignore length 1 or 2 chains.
if ($j - $i > 2) {
$result[] = [
'begin' => $i,
'end' => $j - 1,
'token' => mb_substr($password, $i, $j - $i),
'turns' => $turns,
'shifted_count' => $shiftedCount
];
}
// ...and then start a new search for the rest of the password.
$i = $j;
break;
}
}
}
return $result;
}
/**
* Get the index of a string a character first
*
* @param string $string
* @param string $char
*
* @return int
*/
protected static function indexOf(string $string, string $char): int
{
$pos = mb_strpos($string, $char);
return ($pos === false ? -1 : $pos);
}
/**
* Load adjacency graphs.
*
* @return array
*/
public static function getAdjacencyGraphs(): array
{
if (empty(self::$adjacencyGraphs)) {
$json = file_get_contents(dirname(__FILE__) . '/adjacency_graphs.json');
$data = json_decode($json, true);
// This seems pointless, but the data file is not guaranteed to be in any particular order.
// We want to be in the exact order below so as to match most closely with upstream, because when a match
// can be found in multiple graphs (such as 789), the one that's listed first is that one that will be picked.
$data = [
'qwerty' => $data['qwerty'],
'dvorak' => $data['dvorak'],
'keypad' => $data['keypad'],
'mac_keypad' => $data['mac_keypad'],
];
self::$adjacencyGraphs = $data;
}
return self::$adjacencyGraphs;
}
protected function getRawGuesses(): float
{
if ($this->graph === 'qwerty' || $this->graph === 'dvorak') {
$startingPosition = self::KEYBOARD_STARTING_POSITION;
$averageDegree = self::KEYBOARD_AVERAGE_DEGREES;
} else {
$startingPosition = self::KEYPAD_STARTING_POSITION;
$averageDegree = self::KEYPAD_AVERAGE_DEGREES;
}
$guesses = 0;
$length = mb_strlen($this->token);
$turns = $this->turns;
// estimate the number of possible patterns w/ length L or less with t turns or less.
for ($i = 2; $i <= $length; $i++) {
$possibleTurns = min($turns, $i - 1);
for ($j = 1; $j <= $possibleTurns; $j++) {
$guesses += Binomial::binom($i - 1, $j - 1) * $startingPosition * pow($averageDegree, $j);
}
}
// add extra guesses for shifted keys. (% instead of 5, A instead of a.)
// math is similar to extra guesses of l33t substitutions in dictionary matches.
if ($this->shiftedCount > 0) {
$shifted = $this->shiftedCount;
$unshifted = $length - $shifted;
if ($unshifted === 0) {
$guesses *= 2;
} else {
$variations = 0;
for ($i = 1; $i <= min($shifted, $unshifted); $i++) {
$variations += Binomial::binom($shifted + $unshifted, $i);
}
$guesses *= $variations;
}
}
return $guesses;
}
}

View File

@@ -1,54 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Matchers;
use ZxcvbnPhp\Matcher;
final class YearMatch extends BaseMatch
{
public const NUM_YEARS = 119;
public $pattern = 'regex';
public $regexName = 'recent_year';
/**
* Match occurrences of years in a password
*
* @param string $password
* @param array $userInputs
* @return YearMatch[]
*/
public static function match(string $password, array $userInputs = []): array
{
$matches = [];
$groups = static::findAll($password, "/(19\d\d|20\d\d)/u");
foreach ($groups as $captures) {
$matches[] = new static($password, $captures[1]['begin'], $captures[1]['end'], $captures[1]['token']);
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}
/**
* @return array{'warning': string, "suggestions": string[]}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
'warning' => "Recent years are easy to guess",
'suggestions' => [
'Avoid recent years',
'Avoid years that are associated with you',
]
];
}
protected function getRawGuesses(): float
{
$yearSpace = abs($this->token - DateMatch::getReferenceYear());
return max($yearSpace, DateMatch::MIN_YEAR_SPACE);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math;
use ZxcvbnPhp\Math\Impl\BinomialProviderPhp73Gmp;
use ZxcvbnPhp\Math\Impl\BinomialProviderFloat64;
use ZxcvbnPhp\Math\Impl\BinomialProviderInt64;
class Binomial
{
private static $provider = null;
private function __construct()
{
throw new \LogicException(__CLASS__ . " is static");
}
/**
* Calculate binomial coefficient (n choose k).
*
* @param int $n
* @param int $k
* @return float
*/
public static function binom(int $n, int $k): float
{
return self::getProvider()->binom($n, $k);
}
public static function getProvider(): BinomialProvider
{
if (self::$provider === null) {
self::$provider = self::initProvider();
}
return self::$provider;
}
/**
* @return string[]
*/
public static function getUsableProviderClasses(): array
{
// In order of priority. The first provider with a value of true will be used.
$possibleProviderClasses = [
BinomialProviderPhp73Gmp::class => function_exists('gmp_binomial'),
BinomialProviderInt64::class => PHP_INT_SIZE >= 8,
BinomialProviderFloat64::class => PHP_FLOAT_DIG >= 15,
];
$possibleProviderClasses = array_filter($possibleProviderClasses);
return array_keys($possibleProviderClasses);
}
private static function initProvider(): BinomialProvider
{
$providerClasses = self::getUsableProviderClasses();
if (!$providerClasses) {
throw new \LogicException("No valid providers");
}
$bestProviderClass = reset($providerClasses);
return new $bestProviderClass();
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math;
interface BinomialProvider
{
/**
* Calculate binomial coefficient (n choose k).
*
* @param int $n
* @param int $k
* @return float
*/
public function binom(int $n, int $k): float;
}

View File

@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
use ZxcvbnPhp\Math\BinomialProvider;
abstract class AbstractBinomialProvider implements BinomialProvider
{
public function binom(int $n, int $k): float
{
if ($k < 0 || $n < 0) {
throw new \DomainException("n and k must be non-negative");
}
if ($k > $n) {
return 0;
}
// $k and $n - $k will always produce the same value, so use smaller of the two
$k = min($k, $n - $k);
return $this->calculate($n, $k);
}
abstract protected function calculate(int $n, int $k): float;
}

View File

@@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
abstract class AbstractBinomialProviderWithFallback extends AbstractBinomialProvider
{
/**
* @var AbstractBinomialProvider|null
*/
private $fallback = null;
protected function calculate(int $n, int $k): float
{
return $this->tryCalculate($n, $k) ?? $this->getFallbackProvider()->calculate($n, $k);
}
abstract protected function tryCalculate(int $n, int $k): ?float;
abstract protected function initFallbackProvider(): AbstractBinomialProvider;
protected function getFallbackProvider(): AbstractBinomialProvider
{
if ($this->fallback === null) {
$this->fallback = $this->initFallbackProvider();
}
return $this->fallback;
}
}

View File

@@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
class BinomialProviderFloat64 extends AbstractBinomialProvider
{
protected function calculate(int $n, int $k): float
{
$c = 1.0;
for ($i = 1; $i <= $k; $i++, $n--) {
// We're aiming for $c * $n / $i, but the $c * $n part could cause us to lose precision, so use $c / $i * $n instead. The caveat
// here is that in order to get a precise answer, we need to minimize the chances of going above ~2^52. This is mitigated
// somewhat by dealing with whole part and the remainder separately, but it's not perfect and could overflow in practice, which
// would result in a loss of precision.
$c = floor($c / $i) * $n + floor(fmod($c, $i) * $n / $i);
}
return $c;
}
}

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
use TypeError;
class BinomialProviderInt64 extends AbstractBinomialProviderWithFallback
{
protected function initFallbackProvider(): AbstractBinomialProvider
{
return new BinomialProviderFloat64();
}
protected function tryCalculate(int $n, int $k): ?float
{
try {
$c = 1;
for ($i = 1; $i <= $k; $i++, $n--) {
// We're aiming for $c * $n / $i, but the $c * $n part could overflow, so use $c / $i * $n instead. The caveat here is that in
// order to get a precise answer, we need to avoid floats, which means we need to deal with whole part and the remainder
// separately.
$c = intdiv($c, $i) * $n + intdiv($c % $i * $n, $i);
}
return (float)$c;
} catch (TypeError $ex) {
return null;
}
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
class BinomialProviderPhp73Gmp extends AbstractBinomialProvider
{
/**
* @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection
* @noinspection PhpComposerExtensionStubsInspection
*/
protected function calculate(int $n, int $k): float
{
return (float)gmp_strval(gmp_binomial($n, $k));
}
}

View File

@@ -1,274 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp;
use ZxcvbnPhp\Matchers\Bruteforce;
use ZxcvbnPhp\Matchers\BaseMatch;
use ZxcvbnPhp\Matchers\MatchInterface;
/**
* scorer - takes a list of potential matches, ranks and evaluates them,
* and figures out how many guesses it would take to crack the password
*
* @see zxcvbn/src/scoring.coffee
*/
class Scorer
{
public const MIN_GUESSES_BEFORE_GROWING_SEQUENCE = 10000;
public const MIN_SUBMATCH_GUESSES_SINGLE_CHAR = 10;
public const MIN_SUBMATCH_GUESSES_MULTI_CHAR = 50;
protected $password;
protected $excludeAdditive;
protected $optimal = [];
/**
* ------------------------------------------------------------------------------
* search --- most guessable match sequence -------------------------------------
* ------------------------------------------------------------------------------
*
* takes a sequence of overlapping matches, returns the non-overlapping sequence with
* minimum guesses. the following is a O(l_max * (n + m)) dynamic programming algorithm
* for a length-n password with m candidate matches. l_max is the maximum optimal
* sequence length spanning each prefix of the password. In practice it rarely exceeds 5 and the
* search terminates rapidly.
*
* the optimal "minimum guesses" sequence is here defined to be the sequence that
* minimizes the following function:
*
* g = l! * Product(m.guesses for m in sequence) + D^(l - 1)
*
* where l is the length of the sequence.
*
* the factorial term is the number of ways to order l patterns.
*
* the D^(l-1) term is another length penalty, roughly capturing the idea that an
* attacker will try lower-length sequences first before trying length-l sequences.
*
* for example, consider a sequence that is date-repeat-dictionary.
* - an attacker would need to try other date-repeat-dictionary combinations,
* hence the product term.
* - an attacker would need to try repeat-date-dictionary, dictionary-repeat-date,
* ..., hence the factorial term.
* - an attacker would also likely try length-1 (dictionary) and length-2 (dictionary-date)
* sequences before length-3. assuming at minimum D guesses per pattern type,
* D^(l-1) approximates Sum(D^i for i in [1..l-1]
*
* @param string $password
* @param MatchInterface[] $matches
* @param bool $excludeAdditive
* @return array Returns an array with these keys: [password, guesses, guesses_log10, sequence]
*/
public function getMostGuessableMatchSequence(string $password, array $matches, bool $excludeAdditive = false): array
{
$this->password = $password;
$this->excludeAdditive = $excludeAdditive;
$length = mb_strlen($password);
$emptyArray = $length > 0 ? array_fill(0, $length, []) : [];
// partition matches into sublists according to ending index j
$matchesByEndIndex = $emptyArray;
foreach ($matches as $match) {
$matchesByEndIndex[$match->end][] = $match;
}
// small detail: for deterministic output, sort each sublist by i.
foreach ($matchesByEndIndex as &$matches) {
usort($matches, function ($a, $b) {
/** @var $a BaseMatch */
/** @var $b BaseMatch */
return $a->begin - $b->begin;
});
}
$this->optimal = [
// optimal.m[k][l] holds final match in the best length-l match sequence covering the
// password prefix up to k, inclusive.
// if there is no length-l sequence that scores better (fewer guesses) than
// a shorter match sequence spanning the same prefix, optimal.m[k][l] is undefined.
'm' => $emptyArray,
// same structure as optimal.m -- holds the product term Prod(m.guesses for m in sequence).
// optimal.pi allows for fast (non-looping) updates to the minimization function.
'pi' => $emptyArray,
// same structure as optimal.m -- holds the overall metric.
'g' => $emptyArray,
];
for ($k = 0; $k < $length; $k++) {
/** @var BaseMatch $match */
foreach ($matchesByEndIndex[$k] as $match) {
if ($match->begin > 0) {
foreach ($this->optimal['m'][$match->begin - 1] as $l => $null) {
$l = (int)$l;
$this->update($match, $l + 1);
}
} else {
$this->update($match, 1);
}
}
$this->bruteforceUpdate($k);
}
if ($length === 0) {
$guesses = 1.0;
$optimalSequence = [];
} else {
$optimalSequence = $this->unwind($length);
$optimalSequenceLength = count($optimalSequence);
$guesses = $this->optimal['g'][$length - 1][$optimalSequenceLength];
}
return [
'password' => $password,
'guesses' => $guesses,
'guesses_log10' => log10($guesses),
'sequence' => $optimalSequence,
];
}
/**
* helper: considers whether a length-l sequence ending at match m is better (fewer guesses)
* than previously encountered sequences, updating state if so.
* @param BaseMatch $match
* @param int $length
*/
protected function update(BaseMatch $match, int $length): void
{
$k = $match->end;
// Upstream has a call to estimateGuesses for this line (which contains some extra logic), but due to our
// object-oriented approach we can just call getGuesses on the match directly.
$pi = $match->getGuesses();
if ($length > 1) {
// we're considering a length-l sequence ending with match m:
// obtain the product term in the minimization function by multiplying m's guesses
// by the product of the length-(l-1) sequence ending just before m, at m.i - 1.
$pi *= $this->optimal['pi'][$match->begin - 1][$length - 1];
}
// calculate the minimization func
$g = $this->factorial($length) * $pi;
if (!$this->excludeAdditive) {
$g += pow(self::MIN_GUESSES_BEFORE_GROWING_SEQUENCE, $length - 1);
}
// update state if new best.
// first see if any competing sequences covering this prefix, with l or fewer matches,
// fare better than this sequence. if so, skip it and return.
foreach ($this->optimal['g'][$k] as $competingL => $competingG) {
if ($competingL > $length) {
continue;
}
if ($competingG <= $g) {
return;
}
}
$this->optimal['g'][$k][$length] = $g;
$this->optimal['m'][$k][$length] = $match;
$this->optimal['pi'][$k][$length] = $pi;
// Sort the arrays by key after each insert to match how JavaScript objects work
// Failing to do this results in slightly different matches in some scenarios
ksort($this->optimal['g'][$k]);
ksort($this->optimal['m'][$k]);
ksort($this->optimal['pi'][$k]);
}
/**
* helper: evaluate bruteforce matches ending at k
* @param int $end
*/
protected function bruteforceUpdate(int $end): void
{
// see if a single bruteforce match spanning the k-prefix is optimal.
$match = $this->makeBruteforceMatch(0, $end);
$this->update($match, 1);
// generate k bruteforce matches, spanning from (i=1, j=k) up to (i=k, j=k).
// see if adding these new matches to any of the sequences in optimal[i-1]
// leads to new bests.
for ($i = 1; $i <= $end; $i++) {
$match = $this->makeBruteforceMatch($i, $end);
foreach ($this->optimal['m'][$i - 1] as $l => $lastM) {
$l = (int)$l;
// corner: an optimal sequence will never have two adjacent bruteforce matches.
// it is strictly better to have a single bruteforce match spanning the same region:
// same contribution to the guess product with a lower length.
// --> safe to skip those cases.
if ($lastM->pattern === 'bruteforce') {
continue;
}
$this->update($match, $l + 1);
}
}
}
/**
* helper: make bruteforce match objects spanning i to j, inclusive.
* @param int $begin
* @param int $end
* @return Bruteforce
*/
protected function makeBruteforceMatch(int $begin, int $end): Bruteforce
{
return new Bruteforce($this->password, $begin, $end, mb_substr($this->password, $begin, $end - $begin + 1));
}
/**
* helper: step backwards through optimal.m starting at the end, constructing the final optimal match sequence.
* @param int $n
* @return MatchInterface[]
*/
protected function unwind(int $n): array
{
$optimalSequence = [];
$k = $n - 1;
// find the final best sequence length and score
$l = null;
$g = INF;
foreach ($this->optimal['g'][$k] as $candidateL => $candidateG) {
if ($candidateG < $g) {
$l = $candidateL;
$g = $candidateG;
}
}
while ($k >= 0) {
$m = $this->optimal['m'][$k][$l];
array_unshift($optimalSequence, $m);
$k = $m->begin - 1;
$l--;
}
return $optimalSequence;
}
/**
* unoptimized, called only on small n
* @param int $n
* @return int
*/
protected function factorial(int $n): float
{
if ($n < 2) {
return 1;
}
$f = 1;
for ($i = 2; $i <= $n; $i++) {
$f *= $i;
}
return $f;
}
}

View File

@@ -1,124 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp;
/**
* Feedback - gives some user guidance based on the strength
* of a password
*
* @see zxcvbn/src/time_estimates.coffee
*/
class TimeEstimator
{
/**
* @param int|float $guesses
* @return array
*/
public function estimateAttackTimes(float $guesses): array
{
$crack_times_seconds = [
'online_throttling_100_per_hour' => $guesses / (100 / 3600),
'online_no_throttling_10_per_second' => $guesses / 10,
'offline_slow_hashing_1e4_per_second' => $guesses / 1e4,
'offline_fast_hashing_1e10_per_second' => $guesses / 1e10
];
$crack_times_display = array_map(
[ $this, 'displayTime' ],
$crack_times_seconds
);
return [
'crack_times_seconds' => $crack_times_seconds,
'crack_times_display' => $crack_times_display,
'score' => $this->guessesToScore($guesses)
];
}
protected function guessesToScore(float $guesses): int
{
$DELTA = 5;
if ($guesses < 1e3 + $DELTA) {
# risky password: "too guessable"
return 0;
}
if ($guesses < 1e6 + $DELTA) {
# modest protection from throttled online attacks: "very guessable"
return 1;
}
if ($guesses < 1e8 + $DELTA) {
# modest protection from unthrottled online attacks: "somewhat guessable"
return 2;
}
if ($guesses < 1e10 + $DELTA) {
# modest protection from offline attacks: "safely unguessable"
# assuming a salted, slow hash function like bcrypt, scrypt, PBKDF2, argon, etc
return 3;
}
# strong protection from offline attacks under same scenario: "very unguessable"
return 4;
}
protected function displayTime(float $seconds): string
{
$callback = function (float $seconds): array {
$minute = 60;
$hour = $minute * 60;
$day = $hour * 24;
$month = $day * 31;
$year = $month * 12;
$century = $year * 100;
if ($seconds < 1) {
return [null, 'less than a second'];
}
if ($seconds < $minute) {
$base = round($seconds);
return [$base, "$base second"];
}
if ($seconds < $hour) {
$base = round($seconds / $minute);
return [$base, "$base minute"];
}
if ($seconds < $day) {
$base = round($seconds / $hour);
return [$base, "$base hour"];
}
if ($seconds < $month) {
$base = round($seconds / $day);
return [$base, "$base day"];
}
if ($seconds < $year) {
$base = round($seconds / $month);
return [$base, "$base month"];
}
if ($seconds < $century) {
$base = round($seconds / $year);
return [$base, "$base year"];
}
return [null, 'centuries'];
};
[$display_num, $display_str] = $callback($seconds);
if ($display_num > 1) {
$display_str .= 's';
}
return $display_str;
}
}

View File

@@ -1,90 +0,0 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp;
/**
* The main entry point.
*
* @see zxcvbn/src/main.coffee
*/
class Zxcvbn
{
/**
* @var
*/
protected $matcher;
/**
* @var
*/
protected $scorer;
/**
* @var
*/
protected $timeEstimator;
/**
* @var
*/
protected $feedback;
public function __construct()
{
$this->matcher = new \ZxcvbnPhp\Matcher();
$this->scorer = new \ZxcvbnPhp\Scorer();
$this->timeEstimator = new \ZxcvbnPhp\TimeEstimator();
$this->feedback = new \ZxcvbnPhp\Feedback();
}
public function addMatcher(string $className): self
{
$this->matcher->addMatcher($className);
return $this;
}
/**
* Calculate password strength via non-overlapping minimum entropy patterns.
*
* @param string $password Password to measure
* @param array $userInputs Optional user inputs
*
* @return array Strength result array with keys:
* password
* entropy
* match_sequence
* score
*/
public function passwordStrength(string $password, array $userInputs = []): array
{
$timeStart = microtime(true);
$sanitizedInputs = array_map(
function ($input) {
return mb_strtolower((string) $input);
},
$userInputs
);
// Get matches for $password.
// Although the coffeescript upstream sets $sanitizedInputs as a property,
// doing this immutably makes more sense and is a bit easier
$matches = $this->matcher->getMatches($password, $sanitizedInputs);
$result = $this->scorer->getMostGuessableMatchSequence($password, $matches);
$attackTimes = $this->timeEstimator->estimateAttackTimes($result['guesses']);
$feedback = $this->feedback->getFeedback($attackTimes['score'], $result['sequence']);
return array_merge(
$result,
$attackTimes,
[
'feedback' => $feedback,
'calc_time' => microtime(true) - $timeStart
]
);
}
}

View File

@@ -1,97 +0,0 @@
# Changelog
## 1.7.0 (2023-12-20)
* Feature: Full PHP 8.3 and PHP 8.2 compatibility.
(#51 by @yadaiio and #50 by @clue)
* Feature / Fix: Improve error reporting when custom error handler is used.
(#47 by @SimonFrings)
* Improve test suite and ensure 100% code coverage.
(#46 by @SimonFrings, #48 and #50 by @clue and #51 by @yadaiio)
## 1.6.0 (2022-02-21)
* Feature: Support PHP 8.1 release.
(#45 by @clue)
* Improve documentation to use fully-qualified function names.
(#43 by @SimonFrings and #42 by @PaulRotmann)
* Improve test suite and use GitHub actions for continuous integration (CI).
(#39 and #40 by @SimonFrings)
## 1.5.0 (2020-10-02)
* Feature: Improve performance by using global imports.
(#38 by @clue)
* Improve API documentation and add support / sponsorship info.
(#30 by @clue and #35 by @SimonFrings)
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
(#32 and #37 by @clue and #34 and #36 by @SimonFrings)
## 1.4.1 (2019-04-09)
* Fix: Check if the function is declared before declaring it.
(#23 by @Niko9911)
* Improve test suite to also test against PHP 7.2 and
add test for base64 encoding and decoding filters.
(#22 by @arubacao and #25 by @Nyholm and @clue)
## 1.4.0 (2017-08-18)
* Feature / Fix: The `fun()` function does not pass filter parameter `null`
to underlying `stream_filter_append()` by default
(#15 by @Nyholm)
Certain filters (such as `convert.quoted-printable-encode`) do not accept
a filter parameter at all. If no explicit filter parameter is given, we no
longer pass a default `null` value.
```php
$encode = Filter\fun('convert.quoted-printable-encode');
assert('t=C3=A4st' === $encode('täst'));
```
* Add examples and improve documentation
(#13 and #20 by @clue and #18 by @Nyholm)
* Improve test suite by adding PHPUnit to require-dev,
fix HHVM build for now again and ignore future HHVM build errors,
lock Travis distro so new future defaults will not break the build
and test on PHP 7.1
(#12, #14 and #19 by @clue and #16 by @Nyholm)
## 1.3.0 (2015-11-08)
* Feature: Support accessing built-in filters as callbacks
(#5 by @clue)
```php
$fun = Filter\fun('zlib.deflate');
$ret = $fun('hello') . $fun('world') . $fun();
assert('helloworld' === gzinflate($ret));
```
## 1.2.0 (2015-10-23)
* Feature: Invoke close event when closing filter (flush buffer)
(#9 by @clue)
## 1.1.0 (2015-10-22)
* Feature: Abort filter operation when catching an Exception
(#10 by @clue)
* Feature: Additional safeguards to prevent filter state corruption
(#7 by @clue)
## 1.0.0 (2015-10-18)
* First tagged release

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Christian Lück
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,326 +0,0 @@
# clue/stream-filter
[![CI status](https://github.com/clue/stream-filter/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/stream-filter/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/clue/stream-filter?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/stream-filter)
A simple and modern approach to stream filtering in PHP
**Table of contents**
* [Why?](#why)
* [Support us](#support-us)
* [Usage](#usage)
* [append()](#append)
* [prepend()](#prepend)
* [fun()](#fun)
* [remove()](#remove)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## Why?
PHP's stream filtering system is great!
It offers very powerful stream filtering options and comes with a useful set of built-in filters.
These filters can be used to easily and efficiently perform various transformations on-the-fly, such as:
* read from a gzip'ed input file,
* transcode from ISO-8859-1 (Latin1) to UTF-8,
* write to a bzip output file
* and much more.
But let's face it:
Its API is [*difficult to work with*](https://www.php.net/manual/en/php-user-filter.filter.php)
and its documentation is [*subpar*](https://stackoverflow.com/questions/27103269/what-is-a-bucket-brigade).
This combined means its powerful features are often neglected.
This project aims to make these features more accessible to a broader audience.
* **Lightweight, SOLID design** -
Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
and does not get in your way.
Custom filters require trivial effort.
* **Good test coverage** -
Comes with an automated tests suite and is regularly tested in the *real world*
## Support us
We invest a lot of time developing, maintaining and updating our awesome
open-source projects. You can help us sustain this high-quality of our work by
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
for details.
Let's take these projects to the next level together! 🚀
## Usage
This lightweight library consists only of a few simple functions.
All functions reside under the `Clue\StreamFilter` namespace.
The below examples refer to all functions with their fully-qualified names like this:
```php
Clue\StreamFilter\append();
```
As of PHP 5.6+ you can also import each required function into your code like this:
```php
use function Clue\StreamFilter\append;
append();
```
Alternatively, you can also use an import statement similar to this:
```php
use Clue\StreamFilter as Filter;
Filter\append();
```
### append()
The `append(resource<stream> $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource<stream filter>` function can be used to
append a filter callback to the given stream.
Each stream can have a list of filters attached.
This function appends a filter to the end of this list.
If the given filter can not be added, it throws an `Exception`.
The `$stream` can be any valid stream resource, such as:
```php
$stream = fopen('demo.txt', 'w+');
```
The `$callback` should be a valid callable function which accepts
an individual chunk of data and should return the updated chunk:
```php
$filter = Clue\StreamFilter\append($stream, function ($chunk) {
// will be called each time you read or write a $chunk to/from the stream
return $chunk;
});
```
As such, you can also use native PHP functions or any other `callable`:
```php
Clue\StreamFilter\append($stream, 'strtoupper');
// will write "HELLO" to the underlying stream
fwrite($stream, 'hello');
```
If the `$callback` accepts invocation without parameters,
then this signature will be invoked once ending (flushing) the filter:
```php
Clue\StreamFilter\append($stream, function ($chunk = null) {
if ($chunk === null) {
// will be called once ending the filter
return 'end';
}
// will be called each time you read or write a $chunk to/from the stream
return $chunk;
});
fclose($stream);
```
> Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data
from the end signal handler if the stream is being closed.
If your callback throws an `Exception`, then the filter process will be aborted.
In order to play nice with PHP's stream handling,
the `Exception` will be transformed to a PHP warning instead:
```php
Clue\StreamFilter\append($stream, function ($chunk) {
throw new \RuntimeException('Unexpected chunk');
});
// raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk"
fwrite($stream, 'hello');
```
The optional `$read_write` parameter can be used to only invoke the `$callback`
when either writing to the stream or only when reading from the stream:
```php
Clue\StreamFilter\append($stream, function ($chunk) {
// will be called each time you write to the stream
return $chunk;
}, STREAM_FILTER_WRITE);
Clue\StreamFilter\append($stream, function ($chunk) {
// will be called each time you read from the stream
return $chunk;
}, STREAM_FILTER_READ);
```
This function returns a filter resource which can be passed to [`remove()`](#remove).
> Note that once a filter has been added to stream, the stream can no longer be passed to
> [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
> (and family).
>
> > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line}
>
> This is due to limitations of PHP's stream filter support, as it can no longer reliably
> tell when the underlying stream resource is actually ready.
> As an alternative, consider calling `stream_select()` on the unfiltered stream and
> then pass the unfiltered data through the [`fun()`](#fun) function.
### prepend()
The `prepend(resource<stream> $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource<stream filter>` function can be used to
prepend a filter callback to the given stream.
Each stream can have a list of filters attached.
This function prepends a filter to the start of this list.
If the given filter can not be added, it throws an `Exception`.
```php
$filter = Clue\StreamFilter\prepend($stream, function ($chunk) {
// will be called each time you read or write a $chunk to/from the stream
return $chunk;
});
```
This function returns a filter resource which can be passed to [`remove()`](#remove).
Except for the position in the list of filters, this function behaves exactly
like the [`append()`](#append) function.
For more details about its behavior, see also the [`append()`](#append) function.
### fun()
The `fun(string $filter, mixed $parameters = null): callable` function can be used to
create a filter function which uses the given built-in `$filter`.
PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php).
Using `fun()` makes accessing these as easy as passing an input string to filter
and getting the filtered output string.
```php
$fun = Clue\StreamFilter\fun('string.rot13');
assert('grfg' === $fun('test'));
assert('test' === $fun($fun('test'));
```
Please note that not all filter functions may be available depending
on installed PHP extensions and the PHP version in use.
In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions
or parameters as Zend PHP.
Accessing an unknown filter function will result in a `RuntimeException`:
```php
Clue\StreamFilter\fun('unknown'); // throws RuntimeException
```
Some filters may accept or require additional filter parameters most
filters do not require filter parameters.
If given, the optional `$parameters` argument will be passed to the
underlying filter handler as-is.
In particular, note how *not passing* this parameter at all differs from
explicitly passing a `null` value (which many filters do not accept).
Please refer to the individual filter definition for more details.
For example, the `string.strip_tags` filter can be invoked like this:
```php
$fun = Clue\StreamFilter\fun('string.strip_tags', '<a><b>');
$ret = $fun('<b>h<br>i</b>');
assert('<b>hi</b>' === $ret);
```
Under the hood, this function allocates a temporary memory stream, so it's
recommended to clean up the filter function after use.
Also, some filter functions (in particular the
[zlib compression filters](https://www.php.net/manual/en/filters.compression.php))
may use internal buffers and may emit a final data chunk on close.
The filter function can be closed by invoking without any arguments:
```php
$fun = Clue\StreamFilter\fun('zlib.deflate');
$ret = $fun('hello') . $fun('world') . $fun();
assert('helloworld' === gzinflate($ret));
```
The filter function must not be used anymore after it has been closed.
Doing so will result in a `RuntimeException`:
```php
$fun = Clue\StreamFilter\fun('string.rot13');
$fun();
$fun('test'); // throws RuntimeException
```
> Note: If you're using the zlib compression filters, then you should be wary
about engine inconsistencies between different PHP versions and HHVM.
These inconsistencies exist in the underlying PHP engines and there's little we
can do about this in this library.
[Our test suite](tests/) contains several test cases that exhibit these issues.
If you feel some test case is missing or outdated, we're happy to accept PRs! :)
### remove()
The `remove(resource<stream filter> $filter): bool` function can be used to
remove a filter previously added via [`append()`](#append) or [`prepend()`](#prepend).
```php
$filter = Clue\StreamFilter\append($stream, function () {
// …
});
Clue\StreamFilter\remove($filter);
```
## Install
The recommended way to install this library is [through Composer](https://getcomposer.org/).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This project follows [SemVer](https://semver.org/).
This will install the latest supported version:
```bash
$ composer require clue/stream-filter:^1.7
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
HHVM.
It's *highly recommended to use the latest supported PHP version* for this project.
Older PHP versions may suffer from a number of inconsistencies documented above.
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):
```bash
$ composer install
```
To run the test suite, go to the project root and run:
```bash
$ vendor/bin/phpunit
```
## License
This project is released under the permissive [MIT license](LICENSE).
> Did you know that I offer custom development services and issuing invoices for
sponsorships of releases and for contributions? Contact me (@clue) for details.

View File

@@ -1,32 +0,0 @@
{
"name": "clue/stream-filter",
"description": "A simple and modern approach to stream filtering in PHP",
"keywords": ["stream", "callback", "filter", "php_user_filter", "stream_filter_append", "stream_filter_register", "bucket brigade"],
"homepage": "https://github.com/clue/stream-filter",
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering"
}
],
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"autoload": {
"psr-4": {
"Clue\\StreamFilter\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"autoload-dev": {
"psr-4": {
"Clue\\Tests\\StreamFilter\\": "tests/"
}
}
}

View File

@@ -1,120 +0,0 @@
<?php
namespace Clue\StreamFilter;
/**
* @internal
* @see append()
* @see prepend()
*/
class CallbackFilter extends \php_user_filter
{
private $callback;
private $closed = true;
private $supportsClose = false;
/** @return bool */
#[\ReturnTypeWillChange]
public function onCreate()
{
$this->closed = false;
if (!\is_callable($this->params)) {
throw new \InvalidArgumentException('No valid callback parameter given to stream_filter_(append|prepend)');
}
$this->callback = $this->params;
// callback supports end event if it accepts invocation without arguments
$ref = new \ReflectionFunction($this->callback);
$this->supportsClose = ($ref->getNumberOfRequiredParameters() === 0);
return true;
}
/** @return void */
#[\ReturnTypeWillChange]
public function onClose()
{
$this->closed = true;
// callback supports closing and is not already closed
if ($this->supportsClose) {
$this->supportsClose = false;
// invoke without argument to signal end and discard resulting buffer
try {
\call_user_func($this->callback);
} catch (\Exception $ignored) {
// this might be called during engine shutdown, so it's not safe
// to raise any errors or exceptions here
// trigger_error('Error closing filter: ' . $ignored->getMessage(), E_USER_WARNING);
}
}
$this->callback = null;
}
/** @return int */
#[\ReturnTypeWillChange]
public function filter($in, $out, &$consumed, $closing)
{
// concatenate whole buffer from input brigade
$data = '';
while ($bucket = \stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
$data .= $bucket->data;
}
// skip processing callback that already ended
if ($this->closed) {
return \PSFS_FEED_ME;
}
// only invoke filter function if buffer is not empty
// this may skip flushing a closing filter
if ($data !== '') {
try {
$data = \call_user_func($this->callback, $data);
} catch (\Exception $e) {
// exception should mark filter as closed
$this->onClose();
\trigger_error('Error invoking filter: ' . $e->getMessage(), \E_USER_WARNING);
return \PSFS_ERR_FATAL;
}
}
// mark filter as closed after processing closing chunk
if ($closing) {
$this->closed = true;
// callback supports closing and is not already closed
if ($this->supportsClose) {
$this->supportsClose = false;
// invoke without argument to signal end and append resulting buffer
try {
$data .= \call_user_func($this->callback);
} catch (\Exception $e) {
\trigger_error('Error ending filter: ' . $e->getMessage(), \E_USER_WARNING);
return \PSFS_ERR_FATAL;
}
}
}
if ($data !== '') {
// create a new bucket for writing the resulting buffer to the output brigade
// reusing an existing bucket turned out to be bugged in some environments (ancient PHP versions and HHVM)
$bucket = @\stream_bucket_new($this->stream, $data);
// legacy PHP versions (PHP < 5.4) do not support passing data from the event signal handler
// because closing the stream invalidates the stream and its stream bucket brigade before
// invoking the filter close handler.
if ($bucket !== false) {
\stream_bucket_append($out, $bucket);
}
}
return \PSFS_PASS_ON;
}
}

View File

@@ -1,380 +0,0 @@
<?php
namespace Clue\StreamFilter;
/**
* Append a filter callback to the given stream.
*
* Each stream can have a list of filters attached.
* This function appends a filter to the end of this list.
*
* If the given filter can not be added, it throws an `Exception`.
*
* The `$stream` can be any valid stream resource, such as:
*
* ```php
* $stream = fopen('demo.txt', 'w+');
* ```
*
* The `$callback` should be a valid callable function which accepts
* an individual chunk of data and should return the updated chunk:
*
* ```php
* $filter = Clue\StreamFilter\append($stream, function ($chunk) {
* // will be called each time you read or write a $chunk to/from the stream
* return $chunk;
* });
* ```
*
* As such, you can also use native PHP functions or any other `callable`:
*
* ```php
* Clue\StreamFilter\append($stream, 'strtoupper');
*
* // will write "HELLO" to the underlying stream
* fwrite($stream, 'hello');
* ```
*
* If the `$callback` accepts invocation without parameters,
* then this signature will be invoked once ending (flushing) the filter:
*
* ```php
* Clue\StreamFilter\append($stream, function ($chunk = null) {
* if ($chunk === null) {
* // will be called once ending the filter
* return 'end';
* }
* // will be called each time you read or write a $chunk to/from the stream
* return $chunk;
* });
*
* fclose($stream);
* ```
*
* > Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data
* from the end signal handler if the stream is being closed.
*
* If your callback throws an `Exception`, then the filter process will be aborted.
* In order to play nice with PHP's stream handling,
* the `Exception` will be transformed to a PHP warning instead:
*
* ```php
* Clue\StreamFilter\append($stream, function ($chunk) {
* throw new \RuntimeException('Unexpected chunk');
* });
*
* // raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk"
* fwrite($stream, 'hello');
* ```
*
* The optional `$read_write` parameter can be used to only invoke the `$callback`
* when either writing to the stream or only when reading from the stream:
*
* ```php
* Clue\StreamFilter\append($stream, function ($chunk) {
* // will be called each time you write to the stream
* return $chunk;
* }, STREAM_FILTER_WRITE);
*
* Clue\StreamFilter\append($stream, function ($chunk) {
* // will be called each time you read from the stream
* return $chunk;
* }, STREAM_FILTER_READ);
* ```
*
* This function returns a filter resource which can be passed to [`remove()`](#remove).
*
* > Note that once a filter has been added to stream, the stream can no longer be passed to
* > [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
* > (and family).
* >
* > > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line}
* >
* > This is due to limitations of PHP's stream filter support, as it can no longer reliably
* > tell when the underlying stream resource is actually ready.
* > As an alternative, consider calling `stream_select()` on the unfiltered stream and
* > then pass the unfiltered data through the [`fun()`](#fun) function.
*
* @param resource $stream
* @param callable $callback
* @param int $read_write
* @return resource filter resource which can be used for `remove()`
* @throws \Exception on error
* @uses stream_filter_append()
*/
function append($stream, $callback, $read_write = STREAM_FILTER_ALL)
{
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errstr) {
// Match errstr from PHP's warning message.
// stream_filter_append() expects parameter 1 to be resource,...
$errstr = $error; // @codeCoverageIgnore
});
try {
$ret = \stream_filter_append($stream, register(), $read_write, $callback);
} catch (\TypeError $e) { // @codeCoverageIgnoreStart
// Throws TypeError on PHP 8.0+
\restore_error_handler();
throw $e;
} // @codeCoverageIgnoreEnd
\restore_error_handler();
// PHP 8 throws above on type errors, older PHP and memory issues can throw here
// @codeCoverageIgnoreStart
if ($ret === false) {
throw new \RuntimeException('Unable to append filter: ' . $errstr);
}
// @codeCoverageIgnoreEnd
return $ret;
}
/**
* Prepend a filter callback to the given stream.
*
* Each stream can have a list of filters attached.
* This function prepends a filter to the start of this list.
*
* If the given filter can not be added, it throws an `Exception`.
*
* ```php
* $filter = Clue\StreamFilter\prepend($stream, function ($chunk) {
* // will be called each time you read or write a $chunk to/from the stream
* return $chunk;
* });
* ```
*
* This function returns a filter resource which can be passed to [`remove()`](#remove).
*
* Except for the position in the list of filters, this function behaves exactly
* like the [`append()`](#append) function.
* For more details about its behavior, see also the [`append()`](#append) function.
*
* @param resource $stream
* @param callable $callback
* @param int $read_write
* @return resource filter resource which can be used for `remove()`
* @throws \Exception on error
* @uses stream_filter_prepend()
*/
function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL)
{
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errstr) {
// Match errstr from PHP's warning message.
// stream_filter_prepend() expects parameter 1 to be resource,...
$errstr = $error; // @codeCoverageIgnore
});
try {
$ret = \stream_filter_prepend($stream, register(), $read_write, $callback);
} catch (\TypeError $e) { // @codeCoverageIgnoreStart
// Throws TypeError on PHP 8.0+
\restore_error_handler();
throw $e;
} // @codeCoverageIgnoreEnd
\restore_error_handler();
// PHP 8 throws above on type errors, older PHP and memory issues can throw here
// @codeCoverageIgnoreStart
if ($ret === false) {
throw new \RuntimeException('Unable to prepend filter: ' . $errstr);
}
// @codeCoverageIgnoreEnd
return $ret;
}
/**
* Create a filter function which uses the given built-in `$filter`.
*
* PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php).
* Using `fun()` makes accessing these as easy as passing an input string to filter
* and getting the filtered output string.
*
* ```php
* $fun = Clue\StreamFilter\fun('string.rot13');
*
* assert('grfg' === $fun('test'));
* assert('test' === $fun($fun('test'));
* ```
*
* Please note that not all filter functions may be available depending
* on installed PHP extensions and the PHP version in use.
* In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions
* or parameters as Zend PHP.
* Accessing an unknown filter function will result in a `RuntimeException`:
*
* ```php
* Clue\StreamFilter\fun('unknown'); // throws RuntimeException
* ```
*
* Some filters may accept or require additional filter parameters most
* filters do not require filter parameters.
* If given, the optional `$parameters` argument will be passed to the
* underlying filter handler as-is.
* In particular, note how *not passing* this parameter at all differs from
* explicitly passing a `null` value (which many filters do not accept).
* Please refer to the individual filter definition for more details.
* For example, the `string.strip_tags` filter can be invoked like this:
*
* ```php
* $fun = Clue\StreamFilter\fun('string.strip_tags', '<a><b>');
*
* $ret = $fun('<b>h<br>i</b>');
* assert('<b>hi</b>' === $ret);
* ```
*
* Under the hood, this function allocates a temporary memory stream, so it's
* recommended to clean up the filter function after use.
* Also, some filter functions (in particular the
* [zlib compression filters](https://www.php.net/manual/en/filters.compression.php))
* may use internal buffers and may emit a final data chunk on close.
* The filter function can be closed by invoking without any arguments:
*
* ```php
* $fun = Clue\StreamFilter\fun('zlib.deflate');
*
* $ret = $fun('hello') . $fun('world') . $fun();
* assert('helloworld' === gzinflate($ret));
* ```
*
* The filter function must not be used anymore after it has been closed.
* Doing so will result in a `RuntimeException`:
*
* ```php
* $fun = Clue\StreamFilter\fun('string.rot13');
* $fun();
*
* $fun('test'); // throws RuntimeException
* ```
*
* > Note: If you're using the zlib compression filters, then you should be wary
* about engine inconsistencies between different PHP versions and HHVM.
* These inconsistencies exist in the underlying PHP engines and there's little we
* can do about this in this library.
* [Our test suite](tests/) contains several test cases that exhibit these issues.
* If you feel some test case is missing or outdated, we're happy to accept PRs! :)
*
* @param string $filter built-in filter name. See stream_get_filters() or http://php.net/manual/en/filters.php
* @param mixed $parameters (optional) parameters to pass to the built-in filter as-is
* @return callable a filter callback which can be append()'ed or prepend()'ed
* @throws \RuntimeException on error
* @link http://php.net/manual/en/filters.php
* @see stream_get_filters()
* @see append()
*/
function fun($filter, $parameters = null)
{
$fp = \fopen('php://memory', 'w');
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errstr) {
// Match errstr from PHP's warning message.
// stream_filter_append() expects parameter 1 to be resource,...
$errstr = $error;
});
if (\func_num_args() === 1) {
$filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE);
} else {
$filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters);
}
\restore_error_handler();
if ($filter === false) {
\fclose($fp);
throw new \RuntimeException('Unable to access built-in filter: ' . $errstr);
}
// append filter function which buffers internally
$buffer = '';
append($fp, function ($chunk) use (&$buffer) {
$buffer .= $chunk;
// always return empty string in order to skip actually writing to stream resource
return '';
}, \STREAM_FILTER_WRITE);
$closed = false;
return function ($chunk = null) use ($fp, $filter, &$buffer, &$closed) {
if ($closed) {
throw new \RuntimeException('Unable to perform operation on closed stream');
}
if ($chunk === null) {
$closed = true;
$buffer = '';
\fclose($fp);
return $buffer;
}
// initialize buffer and invoke filters by attempting to write to stream
$buffer = '';
\fwrite($fp, $chunk);
// buffer now contains everything the filter function returned
return $buffer;
};
}
/**
* Remove a filter previously added via `append()` or `prepend()`.
*
* ```php
* $filter = Clue\StreamFilter\append($stream, function () {
* // …
* });
* Clue\StreamFilter\remove($filter);
* ```
*
* @param resource $filter
* @return bool true on success or false on error
* @throws \RuntimeException on error
* @uses stream_filter_remove()
*/
function remove($filter)
{
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errstr) {
// Match errstr from PHP's warning message.
// stream_filter_remove() expects parameter 1 to be resource,...
$errstr = $error;
});
try {
$ret = \stream_filter_remove($filter);
} catch (\TypeError $e) { // @codeCoverageIgnoreStart
// Throws TypeError on PHP 8.0+
\restore_error_handler();
throw $e;
} // @codeCoverageIgnoreEnd
\restore_error_handler();
if ($ret === false) {
// PHP 8 throws above on type errors, older PHP and memory issues can throw here
throw new \RuntimeException('Unable to remove filter: ' . $errstr);
}
}
/**
* Registers the callback filter and returns the resulting filter name
*
* There should be little reason to call this function manually.
*
* @return string filter name
* @uses CallbackFilter
*/
function register()
{
static $registered = null;
if ($registered === null) {
$registered = 'stream-callback';
\stream_filter_register($registered, __NAMESPACE__ . '\CallbackFilter');
}
return $registered;
}

View File

@@ -1,9 +0,0 @@
<?php
namespace Clue\StreamFilter;
// @codeCoverageIgnoreStart
if (!\function_exists(__NAMESPACE__ . '\\append')) {
require __DIR__ . '/functions.php';
}
// @codeCoverageIgnoreEnd

View File

@@ -1,585 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

View File

@@ -1,352 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View File

@@ -1,21 +0,0 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,27 +0,0 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname(dirname($vendorDir))));
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'DateError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateError.php',
'DateException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateException.php',
'DateInvalidOperationException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php',
'DateInvalidTimeZoneException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php',
'DateMalformedIntervalStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php',
'DateMalformedPeriodStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php',
'DateMalformedStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php',
'DateObjectError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php',
'DateRangeError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Override' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/Override.php',
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'SQLite3Exception' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);

View File

@@ -1,30 +0,0 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname(dirname($vendorDir))));
return array(
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => $vendorDir . '/laminas/laminas-servicemanager/src/autoload.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
'9c67151ae59aff4788964ce8eb2a0f43' => $vendorDir . '/clue/stream-filter/src/functions_include.php',
'07d7f1a47144818725fd8d91a907ac57' => $vendorDir . '/laminas/laminas-diactoros/src/functions/create_uploaded_file.php',
'da94ac5d3ca7d2dbab84ce561ce72bfd' => $vendorDir . '/laminas/laminas-diactoros/src/functions/marshal_headers_from_sapi.php',
'3d97c8dcdfba8cb85d3b34f116bb248b' => $vendorDir . '/laminas/laminas-diactoros/src/functions/marshal_method_from_sapi.php',
'e6f3bc6883e449ab367280b34158c05b' => $vendorDir . '/laminas/laminas-diactoros/src/functions/marshal_protocol_version_from_sapi.php',
'de95e0ac670b27c84ef8c5ac41fc1b34' => $vendorDir . '/laminas/laminas-diactoros/src/functions/normalize_server.php',
'b6c2870932b0250c10334a86dcb33c7f' => $vendorDir . '/laminas/laminas-diactoros/src/functions/normalize_uploaded_files.php',
'd02cf21124526632320d6f20b1bbf905' => $vendorDir . '/laminas/laminas-diactoros/src/functions/parse_cookie_header.php',
'253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php',
'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'8cff32064859f4559445b89279f3199c' => $vendorDir . '/php-http/message/src/filters.php',
);

View File

@@ -1,14 +0,0 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname(dirname($vendorDir))));
return array(
'Zend_' => array($vendorDir . '/plesk/zf1/library'),
'ZendSearch' => array($vendorDir . '/plesk/zendsearch/library'),
'URI_' => array($vendorDir . '/plesk/uri_template/src'),
'Net_' => array($vendorDir . '/plesk/net_whois/src'),
'Mustache' => array($vendorDir . '/plesk/mustache/src'),
);

View File

@@ -1,75 +0,0 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname(dirname($vendorDir))));
return array(
'enshrined\\svgSanitize\\' => array($vendorDir . '/enshrined/svg-sanitize/src'),
'ZxcvbnPhp\\' => array($vendorDir . '/bjeavons/zxcvbn-php/src'),
'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'Test\\' => array($vendorDir . '/plesk/wappspector/tests'),
'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'),
'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'),
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
'Slim\\Psr7\\' => array($vendorDir . '/slim/psr7/src'),
'Slim\\' => array($vendorDir . '/slim/slim/Slim'),
'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
'React\\Http\\' => array($vendorDir . '/react/http/src'),
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
'Ratchet\\RFC6455\\' => array($vendorDir . '/ratchet/rfc6455/src'),
'Ratchet\\' => array($vendorDir . '/plesk/ratchetphp/src/Ratchet'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'Plesk\\Wappspector\\' => array($vendorDir . '/plesk/wappspector/src'),
'PHPStan\\PhpDocParser\\' => array($vendorDir . '/phpstan/phpdoc-parser/src'),
'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'),
'Metadata\\' => array($vendorDir . '/jms/metadata/src'),
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\Local\\' => array($vendorDir . '/league/flysystem-local'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Laminas\\Validator\\' => array($vendorDir . '/laminas/laminas-validator/src'),
'Laminas\\Stdlib\\' => array($vendorDir . '/laminas/laminas-stdlib/src'),
'Laminas\\ServiceManager\\' => array($vendorDir . '/laminas/laminas-servicemanager/src'),
'Laminas\\Mime\\' => array($vendorDir . '/plesk/laminas-mime/src'),
'Laminas\\Mail\\' => array($vendorDir . '/plesk/laminas-mail/src'),
'Laminas\\Loader\\' => array($vendorDir . '/laminas/laminas-loader/src'),
'Laminas\\Diactoros\\' => array($vendorDir . '/laminas/laminas-diactoros/src'),
'JMS\\Serializer\\' => array($vendorDir . '/jms/serializer/src'),
'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'),
'Http\\Message\\' => array($vendorDir . '/php-http/message/src'),
'Http\\Client\\Socket\\' => array($vendorDir . '/plesk/socket-client/src'),
'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GraphQL\\Upload\\' => array($vendorDir . '/ecodev/graphql-upload/src'),
'GraphQL\\' => array($vendorDir . '/webonyx/graphql-php/src'),
'GoetasWebservices\\Xsd\\XsdToPhpRuntime\\' => array($vendorDir . '/goetas-webservices/xsd2php-runtime/src'),
'Fig\\Http\\Message\\' => array($vendorDir . '/fig/http-message-util/src'),
'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'),
'Evenement\\' => array($vendorDir . '/evenement/evenement/src'),
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'),
'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
'DI\\' => array($vendorDir . '/php-di/php-di/src'),
'Clue\\StreamFilter\\' => array($vendorDir . '/clue/stream-filter/src'),
);

View File

@@ -1,54 +0,0 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitPlesk
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitPlesk', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitPlesk', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php';
$includePaths[] = get_include_path();
set_include_path(implode(PATH_SEPARATOR, $includePaths));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitPlesk::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInitPlesk::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

View File

@@ -1,491 +0,0 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitPlesk
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/autoload.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
'9c67151ae59aff4788964ce8eb2a0f43' => __DIR__ . '/..' . '/clue/stream-filter/src/functions_include.php',
'07d7f1a47144818725fd8d91a907ac57' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/create_uploaded_file.php',
'da94ac5d3ca7d2dbab84ce561ce72bfd' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/marshal_headers_from_sapi.php',
'3d97c8dcdfba8cb85d3b34f116bb248b' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/marshal_method_from_sapi.php',
'e6f3bc6883e449ab367280b34158c05b' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/marshal_protocol_version_from_sapi.php',
'de95e0ac670b27c84ef8c5ac41fc1b34' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/normalize_server.php',
'b6c2870932b0250c10334a86dcb33c7f' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/normalize_uploaded_files.php',
'd02cf21124526632320d6f20b1bbf905' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/parse_cookie_header.php',
'253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php',
'b33e3d135e5d9e47d845c576147bda89' => __DIR__ . '/..' . '/php-di/php-di/src/functions.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'8cff32064859f4559445b89279f3199c' => __DIR__ . '/..' . '/php-http/message/src/filters.php',
);
public static $prefixLengthsPsr4 = array (
'e' =>
array (
'enshrined\\svgSanitize\\' => 22,
),
'Z' =>
array (
'ZxcvbnPhp\\' => 10,
'ZipStream\\' => 10,
),
'W' =>
array (
'Webmozart\\Assert\\' => 17,
),
'T' =>
array (
'Test\\' => 5,
),
'S' =>
array (
'Symfony\\Polyfill\\Php83\\' => 23,
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\Routing\\' => 26,
'Symfony\\Component\\OptionsResolver\\' => 34,
'Symfony\\Component\\HttpFoundation\\' => 33,
'Slim\\Psr7\\' => 10,
'Slim\\' => 5,
),
'R' =>
array (
'React\\Stream\\' => 13,
'React\\Socket\\' => 13,
'React\\Promise\\' => 14,
'React\\Http\\' => 11,
'React\\EventLoop\\' => 16,
'React\\Dns\\' => 10,
'React\\Cache\\' => 12,
'Ratchet\\RFC6455\\' => 16,
'Ratchet\\' => 8,
),
'P' =>
array (
'Psr\\SimpleCache\\' => 16,
'Psr\\Log\\' => 8,
'Psr\\Http\\Server\\' => 16,
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
'Psr\\Container\\' => 14,
'Psr\\Cache\\' => 10,
'Plesk\\Wappspector\\' => 18,
'PHPStan\\PhpDocParser\\' => 21,
),
'N' =>
array (
'Nyholm\\Psr7\\' => 12,
),
'M' =>
array (
'Metadata\\' => 9,
),
'L' =>
array (
'League\\MimeTypeDetection\\' => 25,
'League\\Flysystem\\Local\\' => 23,
'League\\Flysystem\\' => 17,
'Laravel\\SerializableClosure\\' => 28,
'Laminas\\Validator\\' => 18,
'Laminas\\Stdlib\\' => 15,
'Laminas\\ServiceManager\\' => 23,
'Laminas\\Mime\\' => 13,
'Laminas\\Mail\\' => 13,
'Laminas\\Loader\\' => 15,
'Laminas\\Diactoros\\' => 18,
),
'J' =>
array (
'JMS\\Serializer\\' => 15,
),
'I' =>
array (
'Invoker\\' => 8,
),
'H' =>
array (
'Http\\Promise\\' => 13,
'Http\\Message\\' => 13,
'Http\\Client\\Socket\\' => 19,
'Http\\Client\\' => 12,
),
'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
'GraphQL\\Upload\\' => 15,
'GraphQL\\' => 8,
'GoetasWebservices\\Xsd\\XsdToPhpRuntime\\' => 38,
),
'F' =>
array (
'Fig\\Http\\Message\\' => 17,
'FastRoute\\' => 10,
),
'E' =>
array (
'Evenement\\' => 10,
),
'D' =>
array (
'Doctrine\\Instantiator\\' => 22,
'Doctrine\\Common\\Lexer\\' => 22,
'Doctrine\\Common\\Annotations\\' => 28,
'DI\\' => 3,
),
'C' =>
array (
'Clue\\StreamFilter\\' => 18,
),
);
public static $prefixDirsPsr4 = array (
'enshrined\\svgSanitize\\' =>
array (
0 => __DIR__ . '/..' . '/enshrined/svg-sanitize/src',
),
'ZxcvbnPhp\\' =>
array (
0 => __DIR__ . '/..' . '/bjeavons/zxcvbn-php/src',
),
'ZipStream\\' =>
array (
0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
),
'Webmozart\\Assert\\' =>
array (
0 => __DIR__ . '/..' . '/webmozart/assert/src',
),
'Test\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/wappspector/tests',
),
'Symfony\\Polyfill\\Php83\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php83',
),
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
),
'Symfony\\Polyfill\\Intl\\Idn\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
'Symfony\\Component\\Routing\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/routing',
),
'Symfony\\Component\\OptionsResolver\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/options-resolver',
),
'Symfony\\Component\\HttpFoundation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/http-foundation',
),
'Slim\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/slim/psr7/src',
),
'Slim\\' =>
array (
0 => __DIR__ . '/..' . '/slim/slim/Slim',
),
'React\\Stream\\' =>
array (
0 => __DIR__ . '/..' . '/react/stream/src',
),
'React\\Socket\\' =>
array (
0 => __DIR__ . '/..' . '/react/socket/src',
),
'React\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/react/promise/src',
),
'React\\Http\\' =>
array (
0 => __DIR__ . '/..' . '/react/http/src',
),
'React\\EventLoop\\' =>
array (
0 => __DIR__ . '/..' . '/react/event-loop/src',
),
'React\\Dns\\' =>
array (
0 => __DIR__ . '/..' . '/react/dns/src',
),
'React\\Cache\\' =>
array (
0 => __DIR__ . '/..' . '/react/cache/src',
),
'Ratchet\\RFC6455\\' =>
array (
0 => __DIR__ . '/..' . '/ratchet/rfc6455/src',
),
'Ratchet\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/ratchetphp/src/Ratchet',
),
'Psr\\SimpleCache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'Psr\\Http\\Server\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-server-handler/src',
1 => __DIR__ . '/..' . '/psr/http-server-middleware/src',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-message/src',
1 => __DIR__ . '/..' . '/psr/http-factory/src',
),
'Psr\\Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'Psr\\Cache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/cache/src',
),
'Plesk\\Wappspector\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/wappspector/src',
),
'PHPStan\\PhpDocParser\\' =>
array (
0 => __DIR__ . '/..' . '/phpstan/phpdoc-parser/src',
),
'Nyholm\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/nyholm/psr7/src',
),
'Metadata\\' =>
array (
0 => __DIR__ . '/..' . '/jms/metadata/src',
),
'League\\MimeTypeDetection\\' =>
array (
0 => __DIR__ . '/..' . '/league/mime-type-detection/src',
),
'League\\Flysystem\\Local\\' =>
array (
0 => __DIR__ . '/..' . '/league/flysystem-local',
),
'League\\Flysystem\\' =>
array (
0 => __DIR__ . '/..' . '/league/flysystem/src',
),
'Laravel\\SerializableClosure\\' =>
array (
0 => __DIR__ . '/..' . '/laravel/serializable-closure/src',
),
'Laminas\\Validator\\' =>
array (
0 => __DIR__ . '/..' . '/laminas/laminas-validator/src',
),
'Laminas\\Stdlib\\' =>
array (
0 => __DIR__ . '/..' . '/laminas/laminas-stdlib/src',
),
'Laminas\\ServiceManager\\' =>
array (
0 => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src',
),
'Laminas\\Mime\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/laminas-mime/src',
),
'Laminas\\Mail\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/laminas-mail/src',
),
'Laminas\\Loader\\' =>
array (
0 => __DIR__ . '/..' . '/laminas/laminas-loader/src',
),
'Laminas\\Diactoros\\' =>
array (
0 => __DIR__ . '/..' . '/laminas/laminas-diactoros/src',
),
'JMS\\Serializer\\' =>
array (
0 => __DIR__ . '/..' . '/jms/serializer/src',
),
'Invoker\\' =>
array (
0 => __DIR__ . '/..' . '/php-di/invoker/src',
),
'Http\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/php-http/promise/src',
),
'Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/php-http/message/src',
),
'Http\\Client\\Socket\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/socket-client/src',
),
'Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/php-http/httplug/src',
),
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
'GraphQL\\Upload\\' =>
array (
0 => __DIR__ . '/..' . '/ecodev/graphql-upload/src',
),
'GraphQL\\' =>
array (
0 => __DIR__ . '/..' . '/webonyx/graphql-php/src',
),
'GoetasWebservices\\Xsd\\XsdToPhpRuntime\\' =>
array (
0 => __DIR__ . '/..' . '/goetas-webservices/xsd2php-runtime/src',
),
'Fig\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/fig/http-message-util/src',
),
'FastRoute\\' =>
array (
0 => __DIR__ . '/..' . '/nikic/fast-route/src',
),
'Evenement\\' =>
array (
0 => __DIR__ . '/..' . '/evenement/evenement/src',
),
'Doctrine\\Instantiator\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator',
),
'Doctrine\\Common\\Lexer\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/lexer/src',
),
'Doctrine\\Common\\Annotations\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations',
),
'DI\\' =>
array (
0 => __DIR__ . '/..' . '/php-di/php-di/src',
),
'Clue\\StreamFilter\\' =>
array (
0 => __DIR__ . '/..' . '/clue/stream-filter/src',
),
);
public static $prefixesPsr0 = array (
'Z' =>
array (
'Zend_' =>
array (
0 => __DIR__ . '/..' . '/plesk/zf1/library',
),
'ZendSearch' =>
array (
0 => __DIR__ . '/..' . '/plesk/zendsearch/library',
),
),
'U' =>
array (
'URI_' =>
array (
0 => __DIR__ . '/..' . '/plesk/uri_template/src',
),
),
'N' =>
array (
'Net_' =>
array (
0 => __DIR__ . '/..' . '/plesk/net_whois/src',
),
),
'M' =>
array (
'Mustache' =>
array (
0 => __DIR__ . '/..' . '/plesk/mustache/src',
),
),
);
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'DateError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateError.php',
'DateException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateException.php',
'DateInvalidOperationException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php',
'DateInvalidTimeZoneException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php',
'DateMalformedIntervalStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php',
'DateMalformedPeriodStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php',
'DateMalformedStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php',
'DateObjectError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php',
'DateRangeError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Override' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/Override.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'SQLite3Exception' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitPlesk::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitPlesk::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInitPlesk::$prefixesPsr0;
$loader->classMap = ComposerStaticInitPlesk::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -1,10 +0,0 @@
<?php
// include_paths.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname(dirname($vendorDir))));
return array(
$vendorDir . '/plesk/zf1/library',
);

File diff suppressed because it is too large Load Diff

View File

@@ -1,734 +0,0 @@
<?php return array(
'root' => array(
'name' => 'plesk/plesk',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'fba2aeae16ab10459e13b5009f87454becec5ca8',
'type' => 'library',
'install_path' => __DIR__ . '/../../../../../',
'aliases' => array(),
'dev' => false,
),
'versions' => array(
'bjeavons/zxcvbn-php' => array(
'pretty_version' => '1.4.2',
'version' => '1.4.2.0',
'reference' => '426f664501a0747beb8f3ee17ac30c7dd6327ffa',
'type' => 'library',
'install_path' => __DIR__ . '/../bjeavons/zxcvbn-php',
'aliases' => array(),
'dev_requirement' => false,
),
'clue/stream-filter' => array(
'pretty_version' => 'v1.7.0',
'version' => '1.7.0.0',
'reference' => '049509fef80032cb3f051595029ab75b49a3c2f7',
'type' => 'library',
'install_path' => __DIR__ . '/../clue/stream-filter',
'aliases' => array(),
'dev_requirement' => false,
),
'container-interop/container-interop' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '^1.2.0',
),
),
'doctrine/annotations' => array(
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
'reference' => '901c2ee5d26eb64ff43c47976e114bf00843acf7',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/annotations',
'aliases' => array(),
'dev_requirement' => false,
),
'doctrine/instantiator' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
'reference' => 'c6222283fa3f4ac679f8b9ced9a4e23f163e80d0',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/instantiator',
'aliases' => array(),
'dev_requirement' => false,
),
'doctrine/lexer' => array(
'pretty_version' => '3.0.1',
'version' => '3.0.1.0',
'reference' => '31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/lexer',
'aliases' => array(),
'dev_requirement' => false,
),
'ecodev/graphql-upload' => array(
'pretty_version' => '8.0.0',
'version' => '8.0.0.0',
'reference' => '793b2f76856eb3d727f061847cc7984d8fd4b417',
'type' => 'library',
'install_path' => __DIR__ . '/../ecodev/graphql-upload',
'aliases' => array(),
'dev_requirement' => false,
),
'enshrined/svg-sanitize' => array(
'pretty_version' => '0.22.0',
'version' => '0.22.0.0',
'reference' => '0afa95ea74be155a7bcd6c6fb60c276c39984500',
'type' => 'library',
'install_path' => __DIR__ . '/../enshrined/svg-sanitize',
'aliases' => array(),
'dev_requirement' => false,
),
'evenement/evenement' => array(
'pretty_version' => 'v3.0.2',
'version' => '3.0.2.0',
'reference' => '0a16b0d71ab13284339abb99d9d2bd813640efbc',
'type' => 'library',
'install_path' => __DIR__ . '/../evenement/evenement',
'aliases' => array(),
'dev_requirement' => false,
),
'fig/http-message-util' => array(
'pretty_version' => '1.1.5',
'version' => '1.1.5.0',
'reference' => '9d94dc0154230ac39e5bf89398b324a86f63f765',
'type' => 'library',
'install_path' => __DIR__ . '/../fig/http-message-util',
'aliases' => array(),
'dev_requirement' => false,
),
'goetas-webservices/xsd2php-runtime' => array(
'pretty_version' => 'v0.2.17',
'version' => '0.2.17.0',
'reference' => 'be15c48cda6adfab82e180a69dfa1937e208cfe1',
'type' => 'library',
'install_path' => __DIR__ . '/../goetas-webservices/xsd2php-runtime',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.8.0',
'version' => '2.8.0.0',
'reference' => '21dc724a0583619cd1652f673303492272778051',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'jms/metadata' => array(
'pretty_version' => '2.8.0',
'version' => '2.8.0.0',
'reference' => '7ca240dcac0c655eb15933ee55736ccd2ea0d7a6',
'type' => 'library',
'install_path' => __DIR__ . '/../jms/metadata',
'aliases' => array(),
'dev_requirement' => false,
),
'jms/serializer' => array(
'pretty_version' => '3.32.5',
'version' => '3.32.5.0',
'reference' => '7c88b1b02ff868eecc870eeddbb3b1250e4bd89c',
'type' => 'library',
'install_path' => __DIR__ . '/../jms/serializer',
'aliases' => array(),
'dev_requirement' => false,
),
'laminas/laminas-diactoros' => array(
'pretty_version' => '3.8.0',
'version' => '3.8.0.0',
'reference' => '60c182916b2749480895601649563970f3f12ec4',
'type' => 'library',
'install_path' => __DIR__ . '/../laminas/laminas-diactoros',
'aliases' => array(),
'dev_requirement' => false,
),
'laminas/laminas-loader' => array(
'pretty_version' => '2.11.1',
'version' => '2.11.1.0',
'reference' => 'c507d5eccb969f7208434e3980680a1f6c0b1d8d',
'type' => 'library',
'install_path' => __DIR__ . '/../laminas/laminas-loader',
'aliases' => array(),
'dev_requirement' => false,
),
'laminas/laminas-servicemanager' => array(
'pretty_version' => '3.24.0',
'version' => '3.24.0.0',
'reference' => 'b172a0df568bf37ebdfb3658263156eefe3c1e8c',
'type' => 'library',
'install_path' => __DIR__ . '/../laminas/laminas-servicemanager',
'aliases' => array(),
'dev_requirement' => false,
),
'laminas/laminas-stdlib' => array(
'pretty_version' => '3.21.0',
'version' => '3.21.0.0',
'reference' => 'b1c81514cfe158aadf724c42b34d3d0a8164c096',
'type' => 'library',
'install_path' => __DIR__ . '/../laminas/laminas-stdlib',
'aliases' => array(),
'dev_requirement' => false,
),
'laminas/laminas-validator' => array(
'pretty_version' => '2.65.0',
'version' => '2.65.0.0',
'reference' => 'f0767ca83e0dd91a6f8ccdd4f0887eb132c0ea49',
'type' => 'library',
'install_path' => __DIR__ . '/../laminas/laminas-validator',
'aliases' => array(),
'dev_requirement' => false,
),
'laravel/serializable-closure' => array(
'pretty_version' => 'v2.0.6',
'version' => '2.0.6.0',
'reference' => '038ce42edee619599a1debb7e81d7b3759492819',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/serializable-closure',
'aliases' => array(),
'dev_requirement' => false,
),
'league/flysystem' => array(
'pretty_version' => '3.30.1',
'version' => '3.30.1.0',
'reference' => 'c139fd65c1f796b926f4aec0df37f6caa959a8da',
'type' => 'library',
'install_path' => __DIR__ . '/../league/flysystem',
'aliases' => array(),
'dev_requirement' => false,
),
'league/flysystem-local' => array(
'pretty_version' => '3.30.0',
'version' => '3.30.0.0',
'reference' => '6691915f77c7fb69adfb87dcd550052dc184ee10',
'type' => 'library',
'install_path' => __DIR__ . '/../league/flysystem-local',
'aliases' => array(),
'dev_requirement' => false,
),
'league/mime-type-detection' => array(
'pretty_version' => '1.16.0',
'version' => '1.16.0.0',
'reference' => '2d6702ff215bf922936ccc1ad31007edc76451b9',
'type' => 'library',
'install_path' => __DIR__ . '/../league/mime-type-detection',
'aliases' => array(),
'dev_requirement' => false,
),
'maennchen/zipstream-php' => array(
'pretty_version' => '3.1.2',
'version' => '3.1.2.0',
'reference' => 'aeadcf5c412332eb426c0f9b4485f6accba2a99f',
'type' => 'library',
'install_path' => __DIR__ . '/../maennchen/zipstream-php',
'aliases' => array(),
'dev_requirement' => false,
),
'nikic/fast-route' => array(
'pretty_version' => 'v1.3.0',
'version' => '1.3.0.0',
'reference' => '181d480e08d9476e61381e04a71b34dc0432e812',
'type' => 'library',
'install_path' => __DIR__ . '/../nikic/fast-route',
'aliases' => array(),
'dev_requirement' => false,
),
'nyholm/psr7' => array(
'pretty_version' => '1.8.2',
'version' => '1.8.2.0',
'reference' => 'a71f2b11690f4b24d099d6b16690a90ae14fc6f3',
'type' => 'library',
'install_path' => __DIR__ . '/../nyholm/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'php-di/invoker' => array(
'pretty_version' => '2.3.7',
'version' => '2.3.7.0',
'reference' => '3c1ddfdef181431fbc4be83378f6d036d59e81e1',
'type' => 'library',
'install_path' => __DIR__ . '/../php-di/invoker',
'aliases' => array(),
'dev_requirement' => false,
),
'php-di/php-di' => array(
'pretty_version' => '7.0.9',
'version' => '7.0.9.0',
'reference' => 'd8480267f5cf239650debba704f3ecd15b638cde',
'type' => 'library',
'install_path' => __DIR__ . '/../php-di/php-di',
'aliases' => array(),
'dev_requirement' => false,
),
'php-http/client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'php-http/httplug' => array(
'pretty_version' => '2.4.1',
'version' => '2.4.1.0',
'reference' => '5cad731844891a4c282f3f3e1b582c46839d22f4',
'type' => 'library',
'install_path' => __DIR__ . '/../php-http/httplug',
'aliases' => array(),
'dev_requirement' => false,
),
'php-http/message' => array(
'pretty_version' => '1.16.2',
'version' => '1.16.2.0',
'reference' => '06dd5e8562f84e641bf929bfe699ee0f5ce8080a',
'type' => 'library',
'install_path' => __DIR__ . '/../php-http/message',
'aliases' => array(),
'dev_requirement' => false,
),
'php-http/message-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'php-http/promise' => array(
'pretty_version' => '1.3.1',
'version' => '1.3.1.0',
'reference' => 'fc85b1fba37c169a69a07ef0d5a8075770cc1f83',
'type' => 'library',
'install_path' => __DIR__ . '/../php-http/promise',
'aliases' => array(),
'dev_requirement' => false,
),
'phpstan/phpdoc-parser' => array(
'pretty_version' => '2.3.0',
'version' => '2.3.0.0',
'reference' => '1e0cd5370df5dd2e556a36b9c62f62e555870495',
'type' => 'library',
'install_path' => __DIR__ . '/../phpstan/phpdoc-parser',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/laminas-mail' => array(
'pretty_version' => '2.26.0-patch2',
'version' => '2.26.0.0-patch2',
'reference' => '395a522f9b62ae3da5ccce34909bd31719e52a50',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/laminas-mail',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/laminas-mime' => array(
'pretty_version' => '2.13.0-patch1',
'version' => '2.13.0.0-patch1',
'reference' => '7191ad33340e569790c561b6073287af77eb3b47',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/laminas-mime',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/mustache' => array(
'pretty_version' => '2.14.3',
'version' => '2.14.3.0',
'reference' => 'aad15e6dba4a99cda46b2e0bbcb48ccbc5a43e25',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/mustache',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/net_whois' => array(
'pretty_version' => '1.0.6',
'version' => '1.0.6.0',
'reference' => '18f93005839ab734a9d481dd2f7bbe67ed5b3eca',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/net_whois',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/plesk' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'fba2aeae16ab10459e13b5009f87454becec5ca8',
'type' => 'library',
'install_path' => __DIR__ . '/../../../../../',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/ratchetphp' => array(
'pretty_version' => 'v1.0.6',
'version' => '1.0.6.0',
'reference' => '3152ad7c236d77a3e50fe0e02ec64bfa2277b56c',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/ratchetphp',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/socket-client' => array(
'pretty_version' => '2.1.1-patch3',
'version' => '2.1.1.0-patch3',
'reference' => 'd7c0ab90fed8efb718428f60266e337cdee7b395',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/socket-client',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/uri_template' => array(
'pretty_version' => '0.3.3',
'version' => '0.3.3.0',
'reference' => '101fd3929dbb0cdc663b55c3adf2de32332d679f',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/uri_template',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/wappspector' => array(
'pretty_version' => '0.2.8',
'version' => '0.2.8.0',
'reference' => '4995ec52d0041b0f469dff2e66ab56201902b066',
'type' => 'project',
'install_path' => __DIR__ . '/../plesk/wappspector',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/zendsearch' => array(
'pretty_version' => '2.0.5',
'version' => '2.0.5.0',
'reference' => '35997da7f6ab4c31492ed97e4354b73c4a6dcd99',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/zendsearch',
'aliases' => array(),
'dev_requirement' => false,
),
'plesk/zf1' => array(
'pretty_version' => '1.12.17-patch23',
'version' => '1.12.17.0-patch23',
'reference' => 'ccb6e05c6162a9dae38376dea89557f8910fad09',
'type' => 'library',
'install_path' => __DIR__ . '/../plesk/zf1',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/cache' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/cache',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/container' => array(
'pretty_version' => '1.1.2',
'version' => '1.1.2.0',
'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/container-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '^1.0',
),
),
'psr/http-client' => array(
'pretty_version' => '1.0.3',
'version' => '1.0.3.0',
'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-factory' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '^1.0',
1 => '1.0',
),
),
'psr/http-message' => array(
'pretty_version' => '1.1',
'version' => '1.1.0.0',
'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '^1.1 || ^2.0',
1 => '1.0',
2 => '^1.0 || ^2.0',
),
),
'psr/http-server-handler' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => '84c4fb66179be4caaf8e97bd239203245302e7d4',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-server-handler',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-server-middleware' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => 'c1481f747daaa6a0782775cd6a8c26a1bf4a3829',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-server-middleware',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/log' => array(
'pretty_version' => '1.1.4',
'version' => '1.1.4.0',
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/simple-cache' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
'reference' => '8707bf3cea6f710bf6ef05491234e3ab06f6432a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/simple-cache',
'aliases' => array(),
'dev_requirement' => false,
),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'dev_requirement' => false,
),
'ratchet/rfc6455' => array(
'pretty_version' => 'v0.4.0',
'version' => '0.4.0.0',
'reference' => '859d95f85dda0912c6d5b936d036d044e3af47ef',
'type' => 'library',
'install_path' => __DIR__ . '/../ratchet/rfc6455',
'aliases' => array(),
'dev_requirement' => false,
),
'react/cache' => array(
'pretty_version' => 'v1.2.0',
'version' => '1.2.0.0',
'reference' => 'd47c472b64aa5608225f47965a484b75c7817d5b',
'type' => 'library',
'install_path' => __DIR__ . '/../react/cache',
'aliases' => array(),
'dev_requirement' => false,
),
'react/dns' => array(
'pretty_version' => 'v1.13.0',
'version' => '1.13.0.0',
'reference' => 'eb8ae001b5a455665c89c1df97f6fb682f8fb0f5',
'type' => 'library',
'install_path' => __DIR__ . '/../react/dns',
'aliases' => array(),
'dev_requirement' => false,
),
'react/event-loop' => array(
'pretty_version' => 'v1.5.0',
'version' => '1.5.0.0',
'reference' => 'bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354',
'type' => 'library',
'install_path' => __DIR__ . '/../react/event-loop',
'aliases' => array(),
'dev_requirement' => false,
),
'react/http' => array(
'pretty_version' => 'v1.11.0',
'version' => '1.11.0.0',
'reference' => '8db02de41dcca82037367f67a2d4be365b1c4db9',
'type' => 'library',
'install_path' => __DIR__ . '/../react/http',
'aliases' => array(),
'dev_requirement' => false,
),
'react/promise' => array(
'pretty_version' => 'v3.3.0',
'version' => '3.3.0.0',
'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a',
'type' => 'library',
'install_path' => __DIR__ . '/../react/promise',
'aliases' => array(),
'dev_requirement' => false,
),
'react/socket' => array(
'pretty_version' => 'v1.16.0',
'version' => '1.16.0.0',
'reference' => '23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1',
'type' => 'library',
'install_path' => __DIR__ . '/../react/socket',
'aliases' => array(),
'dev_requirement' => false,
),
'react/stream' => array(
'pretty_version' => 'v1.4.0',
'version' => '1.4.0.0',
'reference' => '1e5b0acb8fe55143b5b426817155190eb6f5b18d',
'type' => 'library',
'install_path' => __DIR__ . '/../react/stream',
'aliases' => array(),
'dev_requirement' => false,
),
'slim/psr7' => array(
'pretty_version' => '1.7.1',
'version' => '1.7.1.0',
'reference' => 'fe98653e7983010aa85c1d137c9b9ad5a1cd187d',
'type' => 'library',
'install_path' => __DIR__ . '/../slim/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'slim/slim' => array(
'pretty_version' => '4.15.0',
'version' => '4.15.0.0',
'reference' => '17eba5182975878a0ab9b27982cd2e2cfcb67ea2',
'type' => 'library',
'install_path' => __DIR__ . '/../slim/slim',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.6.0',
'version' => '3.6.0.0',
'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/http-foundation' => array(
'pretty_version' => 'v7.3.5',
'version' => '7.3.5.0',
'reference' => 'ce31218c7cac92eab280762c4375fb70a6f4f897',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/options-resolver' => array(
'pretty_version' => 'v6.4.25',
'version' => '6.4.25.0',
'reference' => 'd28e7e2db8a73e9511df892d36445f61314bbebe',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/options-resolver',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-intl-idn' => array(
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => '9614ac4d8061dc257ecc64cba1b140873dce8ad3',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => '3833d7255cc303546435cb650316bff708a1c75c',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php83' => array(
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => '17f6f9a6b1735c0f163024d959f700cfbc5155e5',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php83',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/routing' => array(
'pretty_version' => 'v6.4.26',
'version' => '6.4.26.0',
'reference' => '6fc4c445f22857d4b8b40a02b73f423ddab295de',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/routing',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/yaml' => array(
'pretty_version' => 'v5.4.45',
'version' => '5.4.45.0',
'reference' => 'a454d47278cc16a5db371fe73ae66a78a633371e',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/yaml',
'aliases' => array(),
'dev_requirement' => false,
),
'webmozart/assert' => array(
'pretty_version' => '1.12.1',
'version' => '1.12.1.0',
'reference' => '9be6926d8b485f55b9229203f962b51ed377ba68',
'type' => 'library',
'install_path' => __DIR__ . '/../webmozart/assert',
'aliases' => array(),
'dev_requirement' => false,
),
'webonyx/graphql-php' => array(
'pretty_version' => 'v15.25.2',
'version' => '15.25.2.0',
'reference' => 'da3891cb35fa694ad5a796a6c4acfa6bf987740a',
'type' => 'library',
'install_path' => __DIR__ . '/../webonyx/graphql-php',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@@ -1,26 +0,0 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@@ -1,19 +0,0 @@
Copyright (c) 2006-2013 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,31 +0,0 @@
⚠️ PHP 8 introduced
[attributes](https://www.php.net/manual/en/language.attributes.overview.php),
which are a native replacement for annotations. As such, this library is
considered feature complete, and should receive exclusively bugfixes and
security fixes.
We do not recommend using this library in new projects and encourage authors
of downstream libraries to offer support for attributes as an alternative to
Doctrine Annotations.
Have a look at [our blog](https://www.doctrine-project.org/2022/11/04/annotations-to-attributes.html)
to learn more.
# Doctrine Annotations
[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations)
[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references)
[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations)
[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations)
Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)).
## Documentation
See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/stable/index.html).
## Contributing
When making a pull request, make sure your changes follow the
[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction).

View File

@@ -1,18 +0,0 @@
# Upgrade from 1.0.x to 2.0.x
- The `NamedArgumentConstructorAnnotation` has been removed. Use the `@NamedArgumentConstructor`
annotation instead.
- `SimpleAnnotationReader` has been removed.
- `DocLexer::peek()` and `DocLexer::glimpse` now return
`Doctrine\Common\Lexer\Token` objects. When using `doctrine/lexer` 2, these
implement `ArrayAccess` as a way for you to still be able to treat them as
arrays in some ways.
- `CachedReader` and `FileCacheReader` have been removed use `PsrCachedReader` instead.
- `AnnotationRegistry` methods related to registering annotations instead of
using autoloading have been removed.
- Parameter type declarations have been added to all methods of all classes. If
you have classes inheriting from classes inside this package, you should add
parameter and return type declarations.
- Support for PHP < 7.2 has been removed
- `PhpParser::parseClass()` has been removed. Use
`PhpParser::parseUseStatements()` instead.

View File

@@ -1,72 +0,0 @@
{
"name": "doctrine/annotations",
"description": "Docblock Annotations Parser",
"license": "MIT",
"type": "library",
"keywords": [
"annotations",
"docblock",
"parser"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/annotations.html",
"require": {
"php": "^7.2 || ^8.0",
"ext-tokenizer": "*",
"doctrine/lexer": "^2 || ^3",
"psr/cache": "^1 || ^2 || ^3"
},
"require-dev": {
"doctrine/cache": "^2.0",
"doctrine/coding-standard": "^10",
"phpstan/phpstan": "^1.10.28",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"symfony/cache": "^5.4 || ^6.4 || ^7",
"vimeo/psalm": "^4.30 || ^5.14"
},
"suggest": {
"php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations"
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations",
"Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations"
},
"files": [
"tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php",
"tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php"
]
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
}
}

View File

@@ -1,189 +0,0 @@
Handling Annotations
====================
There are several different approaches to handling annotations in PHP.
Doctrine Annotations maps docblock annotations to PHP classes. Because
not all docblock annotations are used for metadata purposes a filter is
applied to ignore or skip classes that are not Doctrine annotations.
Take a look at the following code snippet:
.. code-block:: php
namespace MyProject\Entities;
use Doctrine\ORM\Mapping AS ORM;
use Symfony\Component\Validator\Constraints AS Assert;
/**
* @author Benjamin Eberlei
* @ORM\Entity
* @MyProject\Annotations\Foobarable
*/
class User
{
/**
* @ORM\Id @ORM\Column @ORM\GeneratedValue
* @dummy
* @var int
*/
private $id;
/**
* @ORM\Column(type="string")
* @Assert\NotEmpty
* @Assert\Email
* @var string
*/
private $email;
}
In this snippet you can see a variety of different docblock annotations:
- Documentation annotations such as ``@var`` and ``@author``. These
annotations are ignored and never considered for throwing an
exception due to wrongly used annotations.
- Annotations imported through use statements. The statement ``use
Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace
available as ``@ORM\ClassName``. Same goes for the import of
``@Assert``.
- The ``@dummy`` annotation. It is not a documentation annotation and
not ignored. For Doctrine Annotations it is not entirely clear how
to handle this annotation. Depending on the configuration an exception
(unknown annotation) will be thrown when parsing this annotation.
- The fully qualified annotation ``@MyProject\Annotations\Foobarable``.
This is transformed directly into the given class name.
How are these annotations loaded? From looking at the code you could
guess that the ORM Mapping, Assert Validation and the fully qualified
annotation can just be loaded using
the defined PHP autoloaders. This is not the case however: For error
handling reasons every check for class existence inside the
``AnnotationReader`` sets the second parameter $autoload
of ``class_exists($name, $autoload)`` to false. To work flawlessly the
``AnnotationReader`` requires silent autoloaders which many autoloaders are
not. Silent autoloading is NOT part of the `PSR-0 specification
<https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_
for autoloading.
This is why Doctrine Annotations uses its own autoloading mechanism
through a global registry. If you are wondering about the annotation
registry being global, there is no other way to solve the architectural
problems of autoloading annotation classes in a straightforward fashion.
Additionally if you think about PHP autoloading then you recognize it is
a global as well.
To anticipate the configuration section, making the above PHP class work
with Doctrine Annotations requires this setup:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
$reader = new AnnotationReader();
AnnotationReader::addGlobalIgnoredName('dummy');
We create the actual ``AnnotationReader`` instance.
Note that we also add ``dummy`` to the global list of ignored
annotations for which we do not throw exceptions. Setting this is
necessary in our example case, otherwise ``@dummy`` would trigger an
exception to be thrown during the parsing of the docblock of
``MyProject\Entities\User#id``.
Setup and Configuration
-----------------------
To use the annotations library is simple, you just need to create a new
``AnnotationReader`` instance:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
This creates a simple annotation reader with no caching other than in
memory (in php arrays). Since parsing docblocks can be expensive you
should cache this process by using a caching reader.
To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``.
This reader decorates the original reader and stores all annotations in a PSR-6
cache:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\PsrCachedReader;
$cache = ... // instantiate a PSR-6 Cache pool
$reader = new PsrCachedReader(
new AnnotationReader(),
$cache,
$debug = true
);
The ``debug`` flag is used here as well to invalidate the cache files
when the PHP class with annotations changed and should be used during
development.
.. warning ::
The ``AnnotationReader`` works and caches under the
assumption that all annotations of a doc-block are processed at
once. That means that annotation classes that do not exist and
aren't loaded and cannot be autoloaded (using the
AnnotationRegistry) would never be visible and not accessible if a
cache is used unless the cache is cleared and the annotations
requested again, this time with all annotations defined.
By default the annotation reader returns a list of annotations with
numeric indexes. If you want your annotations to be indexed by their
class name you can wrap the reader in an ``IndexedReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\IndexedReader;
$reader = new IndexedReader(new AnnotationReader());
.. warning::
You should never wrap the indexed reader inside a cached reader,
only the other way around. This way you can re-use the cache with
indexed or numeric keys, otherwise your code may experience failures
due to caching in a numerical or indexed format.
Ignoring missing exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default an exception is thrown from the ``AnnotationReader`` if an
annotation was found that:
- is not part of the list of ignored "documentation annotations";
- was not imported through a use statement;
- is not a fully qualified class that exists.
You can disable this behavior for specific names if your docblocks do
not follow strict requirements:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
AnnotationReader::addGlobalIgnoredName('foo');
PHP Imports
~~~~~~~~~~~
By default the annotation reader parses the use-statement of a php file
to gain access to the import rules and register them for the annotation
processing. Only if you are using PHP Imports can you validate the
correct usage of annotations and throw exceptions if you misspelled an
annotation. This mechanism is enabled by default.
To ease the upgrade path, we still allow you to disable this mechanism.
Note however that we will remove this in future versions:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setEnabledPhpImports(false);

View File

@@ -1,443 +0,0 @@
Custom Annotation Classes
=========================
If you want to define your own annotations, you just have to group them
in a namespace.
Annotation classes have to contain a class-level docblock with the text
``@Annotation``:
.. code-block:: php
namespace MyCompany\Annotations;
/** @Annotation */
class Bar
{
// some code
}
Inject annotation values
------------------------
The annotation parser checks if the annotation constructor has arguments,
if so then it will pass the value array, otherwise it will try to inject
values into public properties directly:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
*
* Some Annotation using a constructor
*/
class Bar
{
private $foo;
public function __construct(array $values)
{
$this->foo = $values['foo'];
}
}
/**
* @Annotation
*
* Some Annotation without a constructor
*/
class Foo
{
public $bar;
}
Optional: Constructors with Named Parameters
--------------------------------------------
Starting with Annotations v1.11 a new annotation instantiation strategy
is available that aims at compatibility of Annotation classes with the PHP 8
attribute feature. You need to declare a constructor with regular parameter
names that match the named arguments in the annotation syntax.
To enable this feature, you can tag your annotation class with
``@NamedArgumentConstructor`` (available from v1.12) or implement the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface
(available from v1.11 and deprecated as of v1.12).
When using the ``@NamedArgumentConstructor`` tag, the first argument of the
constructor is considered as the default one.
Usage with the ``@NamedArgumentConstructor`` tag
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(string $foo)
{
$this->foo = $foo;
}
}
/** Usable with @Bar(foo="baz") */
/** Usable with @Bar("baz") */
In combination with PHP 8's constructor property promotion feature
you can simplify this to:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
public function __construct(private string $foo) {}
}
Usage with the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation``
interface (v1.11, deprecated as of v1.12):
.. code-block:: php
namespace MyCompany\Annotations;
use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;
/** @Annotation */
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(private string $foo) {}
}
/** Usable with @Bar(foo="baz") */
Annotation Target
-----------------
``@Target`` indicates the kinds of class elements to which an annotation
type is applicable. Then you could define one or more targets:
- ``CLASS`` Allowed in class docblocks
- ``PROPERTY`` Allowed in property docblocks
- ``METHOD`` Allowed in the method docblocks
- ``FUNCTION`` Allowed in function dockblocks
- ``ALL`` Allowed in class, property, method and function docblocks
- ``ANNOTATION`` Allowed inside other annotations
If the annotations is not allowed in the current context, an
``AnnotationException`` is thrown.
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
// some code
}
/**
* @Annotation
* @Target("CLASS")
*/
class Foo
{
// some code
}
Attribute types
---------------
The annotation parser checks the given parameters using the phpdoc
annotation ``@var``, The data type could be validated using the ``@var``
annotation on the annotation properties or using the ``@Attributes`` and
``@Attribute`` annotations.
If the data type does not match you get an ``AnnotationException``
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
/** @var mixed */
public $mixed;
/** @var boolean */
public $boolean;
/** @var bool */
public $bool;
/** @var float */
public $float;
/** @var string */
public $string;
/** @var integer */
public $integer;
/** @var array */
public $array;
/** @var SomeAnnotationClass */
public $annotation;
/** @var array<integer> */
public $arrayOfIntegers;
/** @var array<SomeAnnotationClass> */
public $arrayOfAnnotations;
}
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
* @Attributes({
* @Attribute("stringProperty", type = "string"),
* @Attribute("annotProperty", type = "SomeAnnotationClass"),
* })
*/
class Foo
{
public function __construct(array $values)
{
$this->stringProperty = $values['stringProperty'];
$this->annotProperty = $values['annotProperty'];
}
// some code
}
Annotation Required
-------------------
``@Required`` indicates that the field must be specified when the
annotation is used. If it is not used you get an ``AnnotationException``
stating that this value can not be null.
Declaring a required field:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Foo
{
/** @Required */
public $requiredField;
}
Usage:
.. code-block:: php
/** @Foo(requiredField="value") */
public $direction; // Valid
/** @Foo */
public $direction; // Required field missing, throws an AnnotationException
Enumerated values
-----------------
- An annotation property marked with ``@Enum`` is a field that accepts a
fixed set of scalar values.
- You should use ``@Enum`` fields any time you need to represent fixed
values.
- The annotation parser checks the given value and throws an
``AnnotationException`` if the value does not match.
Declaring an enumerated property:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Direction
{
/**
* @Enum({"NORTH", "SOUTH", "EAST", "WEST"})
*/
public $value;
}
Annotation usage:
.. code-block:: php
/** @Direction("NORTH") */
public $direction; // Valid value
/** @Direction("NORTHEAST") */
public $direction; // Invalid value, throws an AnnotationException
Constants
---------
The use of constants and class constants is available on the annotations
parser.
The following usages are allowed:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
use MyCompany\Entity\SomeClass;
/**
* @Foo(PHP_EOL)
* @Bar(Bar::FOO)
* @Foo({SomeClass::FOO, SomeClass::BAR})
* @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE})
*/
class User
{
}
Be careful with constants and the cache !
.. note::
The cached reader will not re-evaluate each time an annotation is
loaded from cache. When a constant is changed the cache must be
cleaned.
Usage
-----
Using the library API is simple. Using the annotations described in the
previous section, you can now annotate other classes with your
annotations:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
/**
* @Foo(bar="foo")
* @Bar(foo="bar")
*/
class User
{
}
Now we can write a script to get the annotations above:
.. code-block:: php
$reflClass = new ReflectionClass('MyCompany\Entity\User');
$classAnnotations = $reader->getClassAnnotations($reflClass);
foreach ($classAnnotations AS $annot) {
if ($annot instanceof \MyCompany\Annotations\Foo) {
echo $annot->bar; // prints "foo";
} else if ($annot instanceof \MyCompany\Annotations\Bar) {
echo $annot->foo; // prints "bar";
}
}
You have a complete API for retrieving annotation class instances from a
class, property or method docblock:
Reader API
~~~~~~~~~~
Access all annotations of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotations(\ReflectionClass $class);
Access one annotation of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotation(\ReflectionClass $class, $annotationName);
Access all annotations of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotations(\ReflectionMethod $method);
Access one annotation of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName);
Access all annotations of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotations(\ReflectionProperty $property);
Access one annotation of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName);
Access all annotations of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotations(\ReflectionFunction $property);
Access one annotation of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName);

View File

@@ -1,109 +0,0 @@
Deprecation notice
==================
PHP 8 introduced `attributes
<https://www.php.net/manual/en/language.attributes.overview.php>`_,
which are a native replacement for annotations. As such, this library is
considered feature complete, and should receive exclusively bugfixes and
security fixes.
We do not recommend using this library in new projects and encourage authors
of downstream libraries to offer support for attributes as an alternative to
Doctrine Annotations.
Have a look at [our blog](https://www.doctrine-project.org/2022/11/04/annotations-to-attributes.html)
to learn more.
Introduction
============
Doctrine Annotations allows to implement custom annotation
functionality for PHP classes and functions.
.. code-block:: php
class Foo
{
/**
* @MyAnnotation(myProperty="value")
*/
private $bar;
}
Annotations aren't implemented in PHP itself which is why this component
offers a way to use the PHP doc-blocks as a place for the well known
annotation syntax using the ``@`` char.
Annotations in Doctrine are used for the ORM configuration to build the
class mapping, but it can be used in other projects for other purposes
too.
Installation
============
You can install the Annotation component with composer:
.. code-block::
$ composer require doctrine/annotations
Create an annotation class
==========================
An annotation class is a representation of the later used annotation
configuration in classes. The annotation class of the previous example
looks like this:
.. code-block:: php
/**
* @Annotation
*/
final class MyAnnotation
{
public $myProperty;
}
The annotation class is declared as an annotation by ``@Annotation``.
:ref:`Read more about custom annotations. <custom>`
Reading annotations
===================
The access to the annotations happens by reflection of the class or function
containing them. There are multiple reader-classes implementing the
``Doctrine\Common\Annotations\Reader`` interface, that can access the
annotations of a class. A common one is
``Doctrine\Common\Annotations\AnnotationReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
$reflectionClass = new ReflectionClass(Foo::class);
$property = $reflectionClass->getProperty('bar');
$reader = new AnnotationReader();
$myAnnotation = $reader->getPropertyAnnotation(
$property,
MyAnnotation::class
);
echo $myAnnotation->myProperty; // result: "value"
A reader has multiple methods to access the annotations of a class or
function.
:ref:`Read more about handling annotations. <annotations>`
IDE Support
-----------
Some IDEs already provide support for annotations:
- Eclipse via the `Symfony2 Plugin <https://github.com/pulse00/Symfony-2-Eclipse-Plugin>`_
- PhpStorm via the `PHP Annotations Plugin <https://plugins.jetbrains.com/plugin/7320-php-annotations>`_ or the `Symfony Plugin <https://plugins.jetbrains.com/plugin/7219-symfony-support>`_
.. _Read more about handling annotations.: annotations
.. _Read more about custom annotations.: custom

View File

@@ -1,6 +0,0 @@
.. toctree::
:depth: 3
index
annotations
custom

View File

@@ -1,54 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use BadMethodCallException;
use function sprintf;
/**
* Annotations class.
*/
class Annotation
{
/**
* Value property. Common among all derived classes.
*
* @var mixed
*/
public $value;
/** @param array<string, mixed> $data Key-value for properties to be defined in this class. */
final public function __construct(array $data)
{
foreach ($data as $key => $value) {
$this->$key = $value;
}
}
/**
* Error handler for unknown property accessor in Annotation class.
*
* @throws BadMethodCallException
*/
public function __get(string $name)
{
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
/**
* Error handler for unknown property mutator in Annotation class.
*
* @param mixed $value Property value.
*
* @throws BadMethodCallException
*/
public function __set(string $name, $value)
{
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
}

View File

@@ -1,21 +0,0 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the attribute type during the parsing process.
*
* @Annotation
*/
final class Attribute
{
/** @var string */
public $name;
/** @var string */
public $type;
/** @var bool */
public $required = false;
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the types of all declared attributes during the parsing process.
*
* @Annotation
*/
final class Attributes
{
/** @var array<Attribute> */
public $value;
}

View File

@@ -1,69 +0,0 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function get_class;
use function gettype;
use function in_array;
use function is_object;
use function is_scalar;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the available values during the parsing process.
*
* @Annotation
* @Attributes({
* @Attribute("value", required = true, type = "array"),
* @Attribute("literal", required = false, type = "array")
* })
*/
final class Enum
{
/** @phpstan-var list<scalar> */
public $value;
/**
* Literal target declaration.
*
* @var mixed[]
*/
public $literal;
/**
* @phpstan-param array{literal?: mixed[], value: list<scalar>} $values
*
* @throws InvalidArgumentException
*/
public function __construct(array $values)
{
if (! isset($values['literal'])) {
$values['literal'] = [];
}
foreach ($values['value'] as $var) {
if (! is_scalar($var)) {
throw new InvalidArgumentException(sprintf(
'@Enum supports only scalar values "%s" given.',
is_object($var) ? get_class($var) : gettype($var)
));
}
}
foreach ($values['literal'] as $key => $var) {
if (! in_array($key, $values['value'])) {
throw new InvalidArgumentException(sprintf(
'Undefined enumerator value "%s" for literal "%s".',
$key,
$var
));
}
}
$this->value = $values['value'];
$this->literal = $values['literal'];
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use RuntimeException;
use function is_array;
use function is_string;
use function json_encode;
use function sprintf;
/**
* Annotation that can be used to signal to the parser to ignore specific
* annotations during the parsing process.
*
* @Annotation
*/
final class IgnoreAnnotation
{
/** @phpstan-var list<string> */
public $names;
/**
* @phpstan-param array{value: string|list<string>} $values
*
* @throws RuntimeException
*/
public function __construct(array $values)
{
if (is_string($values['value'])) {
$values['value'] = [$values['value']];
}
if (! is_array($values['value'])) {
throw new RuntimeException(sprintf(
'@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.',
json_encode($values['value'])
));
}
$this->names = $values['value'];
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that indicates that the annotated class should be constructed with a named argument call.
*
* @Annotation
* @Target("CLASS")
*/
final class NamedArgumentConstructor
{
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check if that attribute is required during the parsing process.
*
* @Annotation
*/
final class Required
{
}

View File

@@ -1,101 +0,0 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function array_keys;
use function get_class;
use function gettype;
use function implode;
use function is_array;
use function is_object;
use function is_string;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the annotation target during the parsing process.
*
* @Annotation
*/
final class Target
{
public const TARGET_CLASS = 1;
public const TARGET_METHOD = 2;
public const TARGET_PROPERTY = 4;
public const TARGET_ANNOTATION = 8;
public const TARGET_FUNCTION = 16;
public const TARGET_ALL = 31;
/** @var array<string, int> */
private static $map = [
'ALL' => self::TARGET_ALL,
'CLASS' => self::TARGET_CLASS,
'METHOD' => self::TARGET_METHOD,
'PROPERTY' => self::TARGET_PROPERTY,
'FUNCTION' => self::TARGET_FUNCTION,
'ANNOTATION' => self::TARGET_ANNOTATION,
];
/** @phpstan-var list<string> */
public $value;
/**
* Targets as bitmask.
*
* @var int
*/
public $targets;
/**
* Literal target declaration.
*
* @var string
*/
public $literal;
/**
* @phpstan-param array{value?: string|list<string>} $values
*
* @throws InvalidArgumentException
*/
public function __construct(array $values)
{
if (! isset($values['value'])) {
$values['value'] = null;
}
if (is_string($values['value'])) {
$values['value'] = [$values['value']];
}
if (! is_array($values['value'])) {
throw new InvalidArgumentException(
sprintf(
'@Target expects either a string value, or an array of strings, "%s" given.',
is_object($values['value']) ? get_class($values['value']) : gettype($values['value'])
)
);
}
$bitmask = 0;
foreach ($values['value'] as $literal) {
if (! isset(self::$map[$literal])) {
throw new InvalidArgumentException(
sprintf(
'Invalid Target "%s". Available targets: [%s]',
$literal,
implode(', ', array_keys(self::$map))
)
);
}
$bitmask |= self::$map[$literal];
}
$this->targets = $bitmask;
$this->value = $values['value'];
$this->literal = implode(', ', $this->value);
}
}

View File

@@ -1,158 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use Exception;
use Throwable;
use function get_class;
use function gettype;
use function implode;
use function is_object;
use function sprintf;
/**
* Description of AnnotationException
*/
class AnnotationException extends Exception
{
/**
* Creates a new AnnotationException describing a Syntax error.
*
* @return AnnotationException
*/
public static function syntaxError(string $message)
{
return new self('[Syntax Error] ' . $message);
}
/**
* Creates a new AnnotationException describing a Semantical error.
*
* @return AnnotationException
*/
public static function semanticalError(string $message)
{
return new self('[Semantical Error] ' . $message);
}
/**
* Creates a new AnnotationException describing an error which occurred during
* the creation of the annotation.
*
* @return AnnotationException
*/
public static function creationError(string $message, ?Throwable $previous = null)
{
return new self('[Creation Error] ' . $message, 0, $previous);
}
/**
* Creates a new AnnotationException describing a type error.
*
* @return AnnotationException
*/
public static function typeError(string $message)
{
return new self('[Type Error] ' . $message);
}
/**
* Creates a new AnnotationException describing a constant semantical error.
*
* @return AnnotationException
*/
public static function semanticalErrorConstants(string $identifier, ?string $context = null)
{
return self::semanticalError(sprintf(
"Couldn't find constant %s%s.",
$identifier,
$context ? ', ' . $context : ''
));
}
/**
* Creates a new AnnotationException describing an type error of an attribute.
*
* @param mixed $actual
*
* @return AnnotationException
*/
public static function attributeTypeError(
string $attributeName,
string $annotationName,
string $context,
string $expected,
$actual
) {
return self::typeError(sprintf(
'Attribute "%s" of @%s declared on %s expects %s, but got %s.',
$attributeName,
$annotationName,
$context,
$expected,
is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual)
));
}
/**
* Creates a new AnnotationException describing an required error of an attribute.
*
* @return AnnotationException
*/
public static function requiredError(
string $attributeName,
string $annotationName,
string $context,
string $expected
) {
return self::typeError(sprintf(
'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.',
$attributeName,
$annotationName,
$context,
$expected
));
}
/**
* Creates a new AnnotationException describing a invalid enummerator.
*
* @param mixed $given
* @phpstan-param list<string> $available
*
* @return AnnotationException
*/
public static function enumeratorError(
string $attributeName,
string $annotationName,
string $context,
array $available,
$given
) {
return new self(sprintf(
'[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.',
$attributeName,
$annotationName,
$context,
implode(', ', $available),
is_object($given) ? get_class($given) : $given
));
}
/** @return AnnotationException */
public static function optimizerPlusSaveComments()
{
return new self(
'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.'
);
}
/** @return AnnotationException */
public static function optimizerPlusLoadComments()
{
return new self(
'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.'
);
}
}

View File

@@ -1,392 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
use Doctrine\Common\Annotations\Annotation\Target;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;
use function array_merge;
use function class_exists;
use function extension_loaded;
use function filter_var;
use function ini_get;
use const FILTER_VALIDATE_BOOLEAN;
/**
* A reader for docblock annotations.
*/
class AnnotationReader implements Reader
{
/**
* Global map for imports.
*
* @var array<string, class-string>
*/
private static $globalImports = [
'ignoreannotation' => Annotation\IgnoreAnnotation::class,
];
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array<string, true>
*/
private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST;
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array<string, true>
*/
private static $globalIgnoredNamespaces = [];
/**
* Add a new annotation to the globally ignored annotation names with regard to exception handling.
*/
public static function addGlobalIgnoredName(string $name)
{
self::$globalIgnoredNames[$name] = true;
}
/**
* Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
*/
public static function addGlobalIgnoredNamespace(string $namespace)
{
self::$globalIgnoredNamespaces[$namespace] = true;
}
/**
* Annotations parser.
*
* @var DocParser
*/
private $parser;
/**
* Annotations parser used to collect parsing metadata.
*
* @var DocParser
*/
private $preParser;
/**
* PHP parser used to collect imports.
*
* @var PhpParser
*/
private $phpParser;
/**
* In-memory cache mechanism to store imported annotations per class.
*
* @psalm-var array<'class'|'function', array<string, array<string, class-string>>>
*/
private $imports = [];
/**
* In-memory cache mechanism to store ignored annotations per class.
*
* @psalm-var array<'class'|'function', array<string, array<string, true>>>
*/
private $ignoredAnnotationNames = [];
/**
* Initializes a new AnnotationReader.
*
* @throws AnnotationException
*/
public function __construct(?DocParser $parser = null)
{
if (
extension_loaded('Zend Optimizer+') &&
(filter_var(ini_get('zend_optimizerplus.save_comments'), FILTER_VALIDATE_BOOLEAN) === false ||
filter_var(ini_get('opcache.save_comments'), FILTER_VALIDATE_BOOLEAN) === false)
) {
throw AnnotationException::optimizerPlusSaveComments();
}
if (
extension_loaded('Zend OPcache') &&
filter_var(ini_get('opcache.save_comments'), FILTER_VALIDATE_BOOLEAN) === false
) {
throw AnnotationException::optimizerPlusSaveComments();
}
// Make sure that the IgnoreAnnotation annotation is loaded
class_exists(IgnoreAnnotation::class);
$this->parser = $parser ?: new DocParser();
$this->preParser = new DocParser();
$this->preParser->setImports(self::$globalImports);
$this->preParser->setIgnoreNotImportedAnnotations(true);
$this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
$this->phpParser = new PhpParser();
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$this->parser->setTarget(Target::TARGET_CLASS);
$this->parser->setImports($this->getImports($class));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$annotations = $this->getClassAnnotations($class);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$context = 'property ' . $class->getName() . '::$' . $property->getName();
$this->parser->setTarget(Target::TARGET_PROPERTY);
$this->parser->setImports($this->getPropertyImports($property));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($property->getDocComment(), $context);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
$annotations = $this->getPropertyAnnotations($property);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
$this->parser->setTarget(Target::TARGET_METHOD);
$this->parser->setImports($this->getMethodImports($method));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($method->getDocComment(), $context);
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
$annotations = $this->getMethodAnnotations($method);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* Gets the annotations applied to a function.
*
* @phpstan-return list<object> An array of Annotations.
*/
public function getFunctionAnnotations(ReflectionFunction $function): array
{
$context = 'function ' . $function->getName();
$this->parser->setTarget(Target::TARGET_FUNCTION);
$this->parser->setImports($this->getImports($function));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($function->getDocComment(), $context);
}
/**
* Gets a function annotation.
*
* @return object|null The Annotation or NULL, if the requested annotation does not exist.
*/
public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName)
{
$annotations = $this->getFunctionAnnotations($function);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* Returns the ignored annotations for the given class or function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, true>
*/
private function getIgnoredAnnotationNames($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->ignoredAnnotationNames[$type][$name])) {
return $this->ignoredAnnotationNames[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->ignoredAnnotationNames[$type][$name];
}
/**
* Retrieves imports for a class or a function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, class-string>
*/
private function getImports($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->imports[$type][$name])) {
return $this->imports[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->imports[$type][$name];
}
/**
* Retrieves imports for methods.
*
* @return array<string, class-string>
*/
private function getMethodImports(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if (
! $trait->hasMethod($method->getName())
|| $trait->getFileName() !== $method->getFileName()
) {
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
}
/**
* Retrieves imports for properties.
*
* @return array<string, class-string>
*/
private function getPropertyImports(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if (! $trait->hasProperty($property->getName())) {
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
}
/**
* Collects parsing metadata for a given class or function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*/
private function collectParsingMetadata($reflection): void
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
$ignoredAnnotationNames = self::$globalIgnoredNames;
$annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name);
foreach ($annotations as $annotation) {
if (! ($annotation instanceof IgnoreAnnotation)) {
continue;
}
foreach ($annotation->names as $annot) {
$ignoredAnnotationNames[$annot] = true;
}
}
$this->imports[$type][$name] = array_merge(
self::$globalImports,
$this->phpParser->parseUseStatements($reflection),
[
'__NAMESPACE__' => $reflection->getNamespaceName(),
'self' => $name,
]
);
$this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames;
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use function array_key_exists;
use function class_exists;
final class AnnotationRegistry
{
/**
* An array of classes which cannot be found
*
* @var null[] indexed by class name
*/
private static $failedToAutoload = [];
public static function reset(): void
{
self::$failedToAutoload = [];
}
/**
* Autoloads an annotation class silently.
*/
public static function loadAnnotationClass(string $class): bool
{
if (class_exists($class, false)) {
return true;
}
if (array_key_exists($class, self::$failedToAutoload)) {
return false;
}
if (class_exists($class)) {
return true;
}
self::$failedToAutoload[$class] = null;
return false;
}
}

View File

@@ -1,131 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Lexer\AbstractLexer;
use function ctype_alpha;
use function is_numeric;
use function str_replace;
use function stripos;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
/**
* Simple lexer for docblock annotations.
*
* @template-extends AbstractLexer<DocLexer::T_*, string>
*/
final class DocLexer extends AbstractLexer
{
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_FLOAT = 4;
// All tokens that are also identifiers should be >= 100
public const T_IDENTIFIER = 100;
public const T_AT = 101;
public const T_CLOSE_CURLY_BRACES = 102;
public const T_CLOSE_PARENTHESIS = 103;
public const T_COMMA = 104;
public const T_EQUALS = 105;
public const T_FALSE = 106;
public const T_NAMESPACE_SEPARATOR = 107;
public const T_OPEN_CURLY_BRACES = 108;
public const T_OPEN_PARENTHESIS = 109;
public const T_TRUE = 110;
public const T_NULL = 111;
public const T_COLON = 112;
public const T_MINUS = 113;
/** @var array<string, self::T*> */
protected $noCase = [
'@' => self::T_AT,
',' => self::T_COMMA,
'(' => self::T_OPEN_PARENTHESIS,
')' => self::T_CLOSE_PARENTHESIS,
'{' => self::T_OPEN_CURLY_BRACES,
'}' => self::T_CLOSE_CURLY_BRACES,
'=' => self::T_EQUALS,
':' => self::T_COLON,
'-' => self::T_MINUS,
'\\' => self::T_NAMESPACE_SEPARATOR,
];
/** @var array<string, self::T*> */
protected $withCase = [
'true' => self::T_TRUE,
'false' => self::T_FALSE,
'null' => self::T_NULL,
];
/**
* Whether the next token starts immediately, or if there were
* non-captured symbols before that
*/
public function nextTokenIsAdjacent(): bool
{
return $this->token === null
|| ($this->lookahead !== null
&& ($this->lookahead->position - $this->token->position) === strlen($this->token->value));
}
/**
* {@inheritDoc}
*/
protected function getCatchablePatterns()
{
return [
'[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*',
'(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?',
'"(?:""|[^"])*+"',
];
}
/**
* {@inheritDoc}
*/
protected function getNonCatchablePatterns()
{
return ['\s+', '\*+', '(.)'];
}
/**
* {@inheritDoc}
*/
protected function getType(&$value)
{
$type = self::T_NONE;
if ($value[0] === '"') {
$value = str_replace('""', '"', substr($value, 1, strlen($value) - 2));
return self::T_STRING;
}
if (isset($this->noCase[$value])) {
return $this->noCase[$value];
}
if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) {
return self::T_IDENTIFIER;
}
$lowerValue = strtolower($value);
if (isset($this->withCase[$lowerValue])) {
return $this->withCase[$lowerValue];
}
// Checking numeric value
if (is_numeric($value)) {
return strpos($value, '.') !== false || stripos($value, 'e') !== false
? self::T_FLOAT : self::T_INTEGER;
}
return $type;
}
}

View File

@@ -1,178 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Annotations;
/**
* A list of annotations that are implicitly ignored during the parsing process.
*
* All names are case sensitive.
*/
final class ImplicitlyIgnoredAnnotationNames
{
private const Reserved = [
'Annotation' => true,
'Attribute' => true,
'Attributes' => true,
/* Can we enable this? 'Enum' => true, */
'Required' => true,
'Target' => true,
'NamedArgumentConstructor' => true,
];
private const WidelyUsedNonStandard = [
'fix' => true,
'fixme' => true,
'override' => true,
];
private const PhpDocumentor1 = [
'abstract' => true,
'access' => true,
'code' => true,
'deprec' => true,
'endcode' => true,
'exception' => true,
'final' => true,
'ingroup' => true,
'inheritdoc' => true,
'inheritDoc' => true,
'magic' => true,
'name' => true,
'private' => true,
'static' => true,
'staticvar' => true,
'staticVar' => true,
'toc' => true,
'tutorial' => true,
'throw' => true,
];
private const PhpDocumentor2 = [
'api' => true,
'author' => true,
'category' => true,
'copyright' => true,
'deprecated' => true,
'example' => true,
'filesource' => true,
'global' => true,
'ignore' => true,
/* Can we enable this? 'index' => true, */
'internal' => true,
'license' => true,
'link' => true,
'method' => true,
'package' => true,
'param' => true,
'property' => true,
'property-read' => true,
'property-write' => true,
'return' => true,
'see' => true,
'since' => true,
'source' => true,
'subpackage' => true,
'throws' => true,
'todo' => true,
'TODO' => true,
'usedby' => true,
'uses' => true,
'var' => true,
'version' => true,
];
private const PHPUnit = [
'author' => true,
'after' => true,
'afterClass' => true,
'backupGlobals' => true,
'backupStaticAttributes' => true,
'before' => true,
'beforeClass' => true,
'codeCoverageIgnore' => true,
'codeCoverageIgnoreStart' => true,
'codeCoverageIgnoreEnd' => true,
'covers' => true,
'coversDefaultClass' => true,
'coversNothing' => true,
'dataProvider' => true,
'depends' => true,
'doesNotPerformAssertions' => true,
'expectedException' => true,
'expectedExceptionCode' => true,
'expectedExceptionMessage' => true,
'expectedExceptionMessageRegExp' => true,
'group' => true,
'large' => true,
'medium' => true,
'preserveGlobalState' => true,
'requires' => true,
'runTestsInSeparateProcesses' => true,
'runInSeparateProcess' => true,
'small' => true,
'test' => true,
'testdox' => true,
'testWith' => true,
'ticket' => true,
'uses' => true,
];
private const PhpCheckStyle = ['SuppressWarnings' => true];
private const PhpStorm = ['noinspection' => true];
private const PEAR = ['package_version' => true];
private const PlainUML = [
'startuml' => true,
'enduml' => true,
];
private const Symfony = ['experimental' => true];
private const PhpCodeSniffer = [
'codingStandardsIgnoreStart' => true,
'codingStandardsIgnoreEnd' => true,
];
private const SlevomatCodingStandard = ['phpcsSuppress' => true];
private const Phan = ['suppress' => true];
private const Rector = ['noRector' => true];
private const StaticAnalysis = [
// PHPStan, Psalm
'extends' => true,
'implements' => true,
'readonly' => true,
'template' => true,
'use' => true,
// Psalm
'pure' => true,
'immutable' => true,
];
public const LIST = self::Reserved
+ self::WidelyUsedNonStandard
+ self::PhpDocumentor1
+ self::PhpDocumentor2
+ self::PHPUnit
+ self::PhpCheckStyle
+ self::PhpStorm
+ self::PEAR
+ self::PlainUML
+ self::Symfony
+ self::SlevomatCodingStandard
+ self::PhpCodeSniffer
+ self::Phan
+ self::Rector
+ self::StaticAnalysis;
private function __construct()
{
}
}

View File

@@ -1,99 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function call_user_func_array;
use function get_class;
/**
* Allows the reader to be used in-place of Doctrine's reader.
*/
class IndexedReader implements Reader
{
/** @var Reader */
private $delegate;
public function __construct(Reader $reader)
{
$this->delegate = $reader;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$annotations = [];
foreach ($this->delegate->getClassAnnotations($class) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
return $this->delegate->getClassAnnotation($class, $annotationName);
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$annotations = [];
foreach ($this->delegate->getMethodAnnotations($method) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
return $this->delegate->getMethodAnnotation($method, $annotationName);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$annotations = [];
foreach ($this->delegate->getPropertyAnnotations($property) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
return $this->delegate->getPropertyAnnotation($property, $annotationName);
}
/**
* Proxies all methods to the delegate.
*
* @param mixed[] $args
*
* @return mixed
*/
public function __call(string $method, array $args)
{
return call_user_func_array([$this->delegate, $method], $args);
}
}

View File

@@ -1,78 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionFunction;
use SplFileObject;
use function is_file;
use function method_exists;
use function preg_quote;
use function preg_replace;
/**
* Parses a file for namespaces/use/class declarations.
*/
final class PhpParser
{
/**
* Parse a class or function for use statements.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @psalm-return array<string, string> a list with use statements in the form (Alias => FQN).
*/
public function parseUseStatements($reflection): array
{
if (method_exists($reflection, 'getUseStatements')) {
return $reflection->getUseStatements();
}
$filename = $reflection->getFileName();
if ($filename === false) {
return [];
}
$content = $this->getFileContent($filename, $reflection->getStartLine());
if ($content === null) {
return [];
}
$namespace = preg_quote($reflection->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$tokenizer = new TokenParser('<?php ' . $content);
return $tokenizer->parseUseStatements($reflection->getNamespaceName());
}
/**
* Gets the content of the file right up to the given line number.
*
* @param string $filename The name of the file to load.
* @param int $lineNumber The number of lines to read from file.
*
* @return string|null The content of the file or null if the file does not exist.
*/
private function getFileContent(string $filename, $lineNumber)
{
if (! is_file($filename)) {
return null;
}
$content = '';
$lineCnt = 0;
$file = new SplFileObject($filename);
while (! $file->eof()) {
if ($lineCnt++ === $lineNumber) {
break;
}
$content .= $file->fgets();
}
return $content;
}
}

View File

@@ -1,233 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Reflector;
use function array_map;
use function array_merge;
use function assert;
use function filemtime;
use function is_file;
use function max;
use function rawurlencode;
use function time;
/**
* A cache aware annotation reader.
*/
final class PsrCachedReader implements Reader
{
/** @var Reader */
private $delegate;
/** @var CacheItemPoolInterface */
private $cache;
/** @var bool */
private $debug;
/** @var array<string, array<object>> */
private $loadedAnnotations = [];
/** @var int[] */
private $loadedFilemtimes = [];
public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false)
{
$this->delegate = $reader;
$this->cache = $cache;
$this->debug = (bool) $debug;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$cacheKey = $class->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
foreach ($this->getClassAnnotations($class) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$cacheKey = $class->getName() . '$' . $property->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$cacheKey = $class->getName() . '#' . $method->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
public function clearLoadedAnnotations(): void
{
$this->loadedAnnotations = [];
$this->loadedFilemtimes = [];
}
/** @return mixed[] */
private function fetchFromCache(
string $cacheKey,
ReflectionClass $class,
string $method,
Reflector $reflector
): array {
$cacheKey = rawurlencode($cacheKey);
$item = $this->cache->getItem($cacheKey);
if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) {
$this->cache->save($item->set($this->delegate->{$method}($reflector)));
}
return $item->get();
}
/**
* Used in debug mode to check if the cache is fresh.
*
* @return bool Returns true if the cache was fresh, or false if the class
* being read was modified since writing to the cache.
*/
private function refresh(string $cacheKey, ReflectionClass $class): bool
{
$lastModification = $this->getLastModification($class);
if ($lastModification === 0) {
return true;
}
$item = $this->cache->getItem('[C]' . $cacheKey);
if ($item->isHit() && $item->get() >= $lastModification) {
return true;
}
$this->cache->save($item->set(time()));
return false;
}
/**
* Returns the time the class was last modified, testing traits and parents
*/
private function getLastModification(ReflectionClass $class): int
{
$filename = $class->getFileName();
if (isset($this->loadedFilemtimes[$filename])) {
return $this->loadedFilemtimes[$filename];
}
$parent = $class->getParentClass();
$lastModification = max(array_merge(
[$filename !== false && is_file($filename) ? filemtime($filename) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $class->getTraits()),
array_map(function (ReflectionClass $class): int {
return $this->getLastModification($class);
}, $class->getInterfaces()),
$parent ? [$this->getLastModification($parent)] : []
));
assert($lastModification !== false);
return $this->loadedFilemtimes[$filename] = $lastModification;
}
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
{
$fileName = $reflectionTrait->getFileName();
if (isset($this->loadedFilemtimes[$fileName])) {
return $this->loadedFilemtimes[$fileName];
}
$lastModificationTime = max(array_merge(
[$fileName !== false && is_file($fileName) ? filemtime($fileName) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $reflectionTrait->getTraits())
));
assert($lastModificationTime !== false);
return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
}
}

View File

@@ -1,80 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
/**
* Interface for annotation readers.
*/
interface Reader
{
/**
* Gets the annotations applied to a class.
*
* @param ReflectionClass $class The ReflectionClass of the class from which
* the class annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getClassAnnotations(ReflectionClass $class);
/**
* Gets a class annotation.
*
* @param ReflectionClass $class The ReflectionClass of the class from which
* the class annotations should be read.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName);
/**
* Gets the annotations applied to a method.
*
* @param ReflectionMethod $method The ReflectionMethod of the method from which
* the annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getMethodAnnotations(ReflectionMethod $method);
/**
* Gets a method annotation.
*
* @param ReflectionMethod $method The ReflectionMethod to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName);
/**
* Gets the annotations applied to a property.
*
* @param ReflectionProperty $property The ReflectionProperty of the property
* from which the annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getPropertyAnnotations(ReflectionProperty $property);
/**
* Gets a property annotation.
*
* @param ReflectionProperty $property The ReflectionProperty to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName);
}

View File

@@ -1,205 +0,0 @@
<?php
namespace Doctrine\Common\Annotations;
use function array_merge;
use function count;
use function explode;
use function strtolower;
use function token_get_all;
use const PHP_VERSION_ID;
use const T_AS;
use const T_COMMENT;
use const T_DOC_COMMENT;
use const T_NAME_FULLY_QUALIFIED;
use const T_NAME_QUALIFIED;
use const T_NAMESPACE;
use const T_NS_SEPARATOR;
use const T_STRING;
use const T_USE;
use const T_WHITESPACE;
/**
* Parses a file for namespaces/use/class declarations.
*/
class TokenParser
{
/**
* The token list.
*
* @phpstan-var list<mixed[]>
*/
private $tokens;
/**
* The number of tokens.
*
* @var int
*/
private $numTokens;
/**
* The current array pointer.
*
* @var int
*/
private $pointer = 0;
public function __construct(string $contents)
{
$this->tokens = token_get_all($contents);
// The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
// saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
// doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
// docblock. If the first thing in the file is a class without a doc block this would cause calls to
// getDocBlock() on said class to return our long lost doc_comment. Argh.
// To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
// it's harmless to us.
token_get_all("<?php\n/**\n *\n */");
$this->numTokens = count($this->tokens);
}
/**
* Gets the next non whitespace and non comment token.
*
* @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
* If FALSE then only whitespace and normal comments are skipped.
*
* @return mixed[]|string|null The token if exists, null otherwise.
*/
public function next(bool $docCommentIsComment = true)
{
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
$this->pointer++;
if (
$this->tokens[$i][0] === T_WHITESPACE ||
$this->tokens[$i][0] === T_COMMENT ||
($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)
) {
continue;
}
return $this->tokens[$i];
}
return null;
}
/**
* Parses a single use statement.
*
* @return array<string, string> A list with all found class names for a use statement.
*/
public function parseUseStatement()
{
$groupRoot = '';
$class = '';
$alias = '';
$statements = [];
$explicitAlias = false;
while (($token = $this->next())) {
if (! $explicitAlias && $token[0] === T_STRING) {
$class .= $token[1];
$alias = $token[1];
} elseif ($explicitAlias && $token[0] === T_STRING) {
$alias = $token[1];
} elseif (
PHP_VERSION_ID >= 80000 &&
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
) {
$class .= $token[1];
$classSplit = explode('\\', $token[1]);
$alias = $classSplit[count($classSplit) - 1];
} elseif ($token[0] === T_NS_SEPARATOR) {
$class .= '\\';
$alias = '';
} elseif ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} elseif ($token === ',') {
$statements[strtolower($alias)] = $groupRoot . $class;
$class = '';
$alias = '';
$explicitAlias = false;
} elseif ($token === ';') {
$statements[strtolower($alias)] = $groupRoot . $class;
break;
} elseif ($token === '{') {
$groupRoot = $class;
$class = '';
} elseif ($token === '}') {
continue;
} else {
break;
}
}
return $statements;
}
/**
* Gets all use statements.
*
* @param string $namespaceName The namespace name of the reflected class.
*
* @return array<string, string> A list with all found use statements.
*/
public function parseUseStatements(string $namespaceName)
{
$statements = [];
while (($token = $this->next())) {
if ($token[0] === T_USE) {
$statements = array_merge($statements, $this->parseUseStatement());
continue;
}
if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) {
continue;
}
// Get fresh array for new namespace. This is to prevent the parser to collect the use statements
// for a previous namespace with the same name. This is the case if a namespace is defined twice
// or if a namespace with the same name is commented out.
$statements = [];
}
return $statements;
}
/**
* Gets the namespace.
*
* @return string The found namespace.
*/
public function parseNamespace()
{
$name = '';
while (
($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || (
PHP_VERSION_ID >= 80000 &&
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
))
) {
$name .= $token[1];
}
return $name;
}
/**
* Gets the class name.
*
* @return string The found class name.
*/
public function parseClass()
{
// Namespaces and class names are tokenized the same: T_STRINGs
// separated by T_NS_SEPARATOR so we can use one function to provide
// both.
return $this->parseNamespace();
}
}

View File

@@ -1,47 +0,0 @@
{
"active": true,
"name": "Instantiator",
"slug": "instantiator",
"docsSlug": "doctrine-instantiator",
"codePath": "/src",
"versions": [
{
"name": "1.5",
"branchName": "1.5.x",
"slug": "latest",
"upcoming": true
},
{
"name": "1.4",
"branchName": "1.4.x",
"slug": "1.4",
"aliases": [
"current",
"stable"
],
"maintained": true,
"current": true
},
{
"name": "1.3",
"branchName": "1.3.x",
"slug": "1.3",
"maintained": false
},
{
"name": "1.2",
"branchName": "1.2.x",
"slug": "1.2"
},
{
"name": "1.1",
"branchName": "1.1.x",
"slug": "1.1"
},
{
"name": "1.0",
"branchName": "1.0.x",
"slug": "1.0"
}
]
}

View File

@@ -1,35 +0,0 @@
# Contributing
* Follow the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard)
* The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php)
* Any contribution must provide tests for additional introduced conditions
* Any un-confirmed issue needs a failing test case before being accepted
* Pull requests must be sent from a new hotfix/feature branch, not from `master`.
## Installation
To install the project and run the tests, you need to clone it first:
```sh
$ git clone git://github.com/doctrine/instantiator.git
```
You will then need to run a composer installation:
```sh
$ cd Instantiator
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar update
```
## Testing
The PHPUnit version to be used is the one installed as a dev- dependency via composer:
```sh
$ ./vendor/bin/phpunit
```
Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement
won't be merged.

View File

@@ -1,19 +0,0 @@
Copyright (c) 2014 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,38 +0,0 @@
# Instantiator
This library provides a way of avoiding usage of constructors when instantiating PHP classes.
[![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator)
[![Code Coverage](https://codecov.io/gh/doctrine/instantiator/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/instantiator/branch/master)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator)
[![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator)
[![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator)
## Installation
The suggested installation method is via [composer](https://getcomposer.org/):
```sh
composer require doctrine/instantiator
```
## Usage
The instantiator is able to create new instances of any class without using the constructor or any API of the class
itself:
```php
$instantiator = new \Doctrine\Instantiator\Instantiator();
$instance = $instantiator->instantiate(\My\ClassName\Here::class);
```
## Contributing
Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out!
## Credits
This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which
has been donated to the doctrine organization, and which is now deprecated in favour of this package.

View File

@@ -1,48 +0,0 @@
{
"name": "doctrine/instantiator",
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"type": "library",
"license": "MIT",
"homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"keywords": [
"instantiate",
"constructor"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "https://ocramius.github.io/"
}
],
"require": {
"php": "^8.1"
},
"require-dev": {
"ext-phar": "*",
"ext-pdo": "*",
"doctrine/coding-standard": "^11",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "^1.9.4",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5.27",
"vimeo/psalm": "^5.4"
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"autoload-dev": {
"psr-0": {
"DoctrineTest\\InstantiatorPerformance\\": "tests",
"DoctrineTest\\InstantiatorTest\\": "tests",
"DoctrineTest\\InstantiatorTestAsset\\": "tests"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@@ -1,68 +0,0 @@
Introduction
============
This library provides a way of avoiding usage of constructors when instantiating PHP classes.
Installation
============
The suggested installation method is via `composer`_:
.. code-block:: console
$ composer require doctrine/instantiator
Usage
=====
The instantiator is able to create new instances of any class without
using the constructor or any API of the class itself:
.. code-block:: php
<?php
use Doctrine\Instantiator\Instantiator;
use App\Entities\User;
$instantiator = new Instantiator();
$user = $instantiator->instantiate(User::class);
Contributing
============
- Follow the `Doctrine Coding Standard`_
- The project will follow strict `object calisthenics`_
- Any contribution must provide tests for additional introduced
conditions
- Any un-confirmed issue needs a failing test case before being
accepted
- Pull requests must be sent from a new hotfix/feature branch, not from
``master``.
Testing
=======
The PHPUnit version to be used is the one installed as a dev- dependency
via composer:
.. code-block:: console
$ ./vendor/bin/phpunit
Accepted coverage for new contributions is 80%. Any contribution not
satisfying this requirement wont be merged.
Credits
=======
This library was migrated from `ocramius/instantiator`_, which has been
donated to the doctrine organization, and which is now deprecated in
favour of this package.
.. _composer: https://getcomposer.org/
.. _CONTRIBUTING.md: CONTRIBUTING.md
.. _ocramius/instantiator: https://github.com/Ocramius/Instantiator
.. _Doctrine Coding Standard: https://github.com/doctrine/coding-standard
.. _object calisthenics: http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php

View File

@@ -1,4 +0,0 @@
.. toctree::
:depth: 3
index

View File

@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
phpVersion="8.2"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Instantiator\Exception;
use Throwable;
/**
* Base exception marker interface for the instantiator component
*/
interface ExceptionInterface extends Throwable
{
}

View File

@@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Instantiator\Exception;
use InvalidArgumentException as BaseInvalidArgumentException;
use ReflectionClass;
use function interface_exists;
use function sprintf;
use function trait_exists;
/**
* Exception for invalid arguments provided to the instantiator
*/
class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface
{
public static function fromNonExistingClass(string $className): self
{
if (interface_exists($className)) {
return new self(sprintf('The provided type "%s" is an interface, and cannot be instantiated', $className));
}
if (trait_exists($className)) {
return new self(sprintf('The provided type "%s" is a trait, and cannot be instantiated', $className));
}
return new self(sprintf('The provided class "%s" does not exist', $className));
}
/**
* @phpstan-param ReflectionClass<T> $reflectionClass
*
* @template T of object
*/
public static function fromAbstractClass(ReflectionClass $reflectionClass): self
{
return new self(sprintf(
'The provided class "%s" is abstract, and cannot be instantiated',
$reflectionClass->getName(),
));
}
public static function fromEnum(string $className): self
{
return new self(sprintf(
'The provided class "%s" is an enum, and cannot be instantiated',
$className,
));
}
}

Some files were not shown because too many files have changed in this diff Show More