Merge branch 'ent-10833-15134-bug-en-chromium-111-0-5563-64-1' into 'develop'

update composer

See merge request artica/pandorafms!5674
This commit is contained in:
Daniel Rodriguez 2023-04-28 06:11:25 +00:00
commit d5450853f5
282 changed files with 9977 additions and 9232 deletions

View File

@ -9,14 +9,14 @@
],
"config": {
"platform": {
"php": "8.0.0"
"php": "8.0.2"
}
},
"require": {
"mpdf/mpdf": "^8.0.15",
"swiftmailer/swiftmailer": "^6.0",
"amphp/parallel-functions": "^1.0",
"chrome-php/chrome": "^1.7.1",
"chrome-php/chrome": "^1.8.1",
"artica/phpchartjs": "^1.0",
"tinymce/tinymce": "^6.4"
},

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
name: Continuous Integration
on:
- push
- pull_request
jobs:
tests:
strategy:
matrix:
include:
- operating-system: 'ubuntu-latest'
php-version: '7.4'
- operating-system: 'ubuntu-latest'
php-version: '8.0'
- operating-system: 'ubuntu-latest'
php-version: '8.1'
- operating-system: 'windows-latest'
php-version: '8.1'
job-description: 'on Windows'
- operating-system: 'macos-latest'
php-version: '8.1'
job-description: 'on macOS'
name: PHP ${{ matrix.php-version }} ${{ matrix.job-description }}
runs-on: ${{ matrix.operating-system }}
steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: fiber-amphp/ext-fiber@master
- name: Get Composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-${{ matrix.composer-flags }}
restore-keys: |
composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-
composer-${{ runner.os }}-${{ matrix.php-version }}-
composer-${{ runner.os }}-
composer-
- name: Install dependencies
uses: nick-invision/retry@v2
with:
timeout_minutes: 5
max_attempts: 5
retry_wait_seconds: 30
command: |
composer update --optimize-autoloader --no-interaction --no-progress ${{ matrix.composer-flags }}
composer info -D
- name: Run tests
run: vendor/bin/phpunit ${{ matrix.phpunit-flags }}
- name: Run examples
run: (for f in examples/*.php; do echo $f && if ! php $f; then echo '!! failed !!' && exit 1; fi && echo "-------"; done)
if: runner.os != 'Windows'
- name: Run style fixer
env:
PHP_CS_FIXER_IGNORE_ENV: 1
run: vendor/bin/php-cs-fixer --diff --dry-run -v fix
if: runner.os != 'Windows'
- name: Install composer-require-checker
run: php -r 'file_put_contents("composer-require-checker.phar", file_get_contents("https://github.com/maglnet/ComposerRequireChecker/releases/download/3.7.0/composer-require-checker.phar"));'
if: runner.os != 'Windows' && matrix.composer-require-checker-version != 'none'
- name: Run composer-require-checker
run: php composer-require-checker.phar check composer.json --config-file $PWD/composer-require-check.json
if: runner.os != 'Windows' && matrix.composer-require-checker-version != 'none'

View File

@ -1,7 +1,7 @@
The MIT License (MIT)
Copyright (c) 2017 amphp
Copyright (c) 2017-2022 amphp (Niklas Keller, and contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -0,0 +1,31 @@
{
"symbol-whitelist": [
"null",
"true",
"false",
"static",
"self",
"parent",
"array",
"string",
"int",
"float",
"bool",
"iterable",
"callable",
"mixed",
"void",
"object",
"parallel"
],
"php-core-extensions": [
"Core",
"date",
"pcre",
"Phar",
"Reflection",
"SPL",
"standard",
"hash"
]
}

View File

@ -23,19 +23,20 @@
}
},
"require": {
"php": ">=7",
"amphp/parallel": "^1.1",
"php": ">=7.4",
"amphp/amp": "^2.0.3",
"opis/closure": "^3.0.7"
"amphp/parallel": "^1.4",
"amphp/serialization": "^1.0",
"laravel/serializable-closure": "^1.0"
},
"require-dev": {
"amphp/phpunit-util": "^1.0",
"friendsofphp/php-cs-fixer": "^2.9",
"phpunit/phpunit": "^6.5"
"amphp/php-cs-fixer-config": "v2.x-dev",
"amphp/phpunit-util": "^2.0",
"phpunit/phpunit": "^9.5.11"
},
"config": {
"platform": {
"php": "7.0.0"
"php": "7.4"
}
},
"scripts": {

View File

@ -6,7 +6,8 @@ use Amp\Parallel\Worker\Environment;
use Amp\Parallel\Worker\Task;
/** @internal */
class SerializedCallableTask implements Task {
class SerializedCallableTask implements Task
{
/** @var string */
private $function;
@ -17,12 +18,14 @@ class SerializedCallableTask implements Task {
* @param string $function Serialized function.
* @param array $args Arguments to pass to the function. Must be serializable.
*/
public function __construct(string $function, array $args) {
public function __construct(string $function, array $args)
{
$this->function = $function;
$this->args = $args;
}
public function run(Environment $environment) {
public function run(Environment $environment)
{
$callable = \unserialize($this->function, ['allowed_classes' => true]);
if ($callable instanceof \__PHP_Incomplete_Class) {

View File

@ -3,10 +3,10 @@
namespace Amp\ParallelFunctions;
use Amp\MultiReasonException;
use Amp\Parallel\Sync\SerializationException;
use Amp\Parallel\Worker\Pool;
use Amp\Promise;
use Opis\Closure\SerializableClosure;
use Amp\Serialization\SerializationException;
use Laravel\SerializableClosure\SerializableClosure;
use function Amp\call;
use function Amp\Parallel\Worker\enqueue;
use function Amp\Promise\any;
@ -20,7 +20,8 @@ use function Amp\Promise\any;
* @return callable Callable executing in another thread / process.
* @throws SerializationException If the passed callable is not safely serializable.
*/
function parallel(callable $callable, Pool $pool = null): callable {
function parallel(callable $callable, Pool $pool = null): callable
{
if ($callable instanceof \Closure) {
$callable = new SerializableClosure($callable);
}
@ -47,11 +48,12 @@ function parallel(callable $callable, Pool $pool = null): callable {
* @return Promise Resolves to the result once the operation finished.
* @throws \Error If the passed callable is not safely serializable.
*/
function parallelMap(array $array, callable $callable, Pool $pool = null): Promise {
function parallelMap(array $array, callable $callable, Pool $pool = null): Promise
{
return call(function () use ($array, $callable, $pool) {
// Amp\Promise\any() guarantees that all operations finished prior to resolving. Amp\Promise\all() doesn't.
// Additionally, we return all errors as a MultiReasonException instead of throwing on the first error.
list($errors, $results) = yield any(\array_map(parallel($callable, $pool), $array));
[$errors, $results] = yield any(\array_map(parallel($callable, $pool), $array));
if ($errors) {
throw new MultiReasonException($errors);
@ -72,7 +74,8 @@ function parallelMap(array $array, callable $callable, Pool $pool = null): Promi
* @return Promise
* @throws \Error If the passed callable is not safely serializable.
*/
function parallelFilter(array $array, callable $callable = null, int $flag = 0, Pool $pool = null): Promise {
function parallelFilter(array $array, callable $callable = null, int $flag = 0, Pool $pool = null): Promise
{
return call(function () use ($array, $callable, $flag, $pool) {
if ($callable === null) {
if ($flag === \ARRAY_FILTER_USE_BOTH || $flag === \ARRAY_FILTER_USE_KEY) {
@ -87,11 +90,11 @@ function parallelFilter(array $array, callable $callable = null, int $flag = 0,
// Amp\Promise\any() guarantees that all operations finished prior to resolving. Amp\Promise\all() doesn't.
// Additionally, we return all errors as a MultiReasonException instead of throwing on the first error.
if ($flag === \ARRAY_FILTER_USE_BOTH) {
list($errors, $results) = yield any(\array_map(parallel($callable, $pool), $array, \array_keys($array)));
[$errors, $results] = yield any(\array_map(parallel($callable, $pool), $array, \array_keys($array)));
} elseif ($flag === \ARRAY_FILTER_USE_KEY) {
list($errors, $results) = yield any(\array_map(parallel($callable, $pool), \array_keys($array)));
[$errors, $results] = yield any(\array_map(parallel($callable, $pool), \array_keys($array)));
} else {
list($errors, $results) = yield any(\array_map(parallel($callable, $pool), $array));
[$errors, $results] = yield any(\array_map(parallel($callable, $pool), $array));
}
if ($errors) {

View File

@ -80,9 +80,13 @@ final class Process implements Context
"log_errors" => "1",
];
$otherOpts = [];
if ($binary === null) {
if (\PHP_SAPI === "cli") {
$binary = \PHP_BINARY;
} else if (\PHP_SAPI === "phpdbg") {
$binary = \PHP_BINARY;
$otherOpts []= '-qrr';
} else {
$binary = self::$binaryPath ?? self::locateBinary();
}
@ -136,7 +140,7 @@ final class Process implements Context
$command = \implode(" ", [
\escapeshellarg($binary),
$this->formatOptions($options),
$this->formatOptions($options, $otherOpts),
\escapeshellarg($scriptPath),
$this->hub->getUri(),
$script,
@ -163,9 +167,9 @@ final class Process implements Context
throw new \Error("Could not locate PHP executable binary");
}
private function formatOptions(array $options): string
private function formatOptions(array $options, array $otherOpts): string
{
$result = [];
$result = $otherOpts;
foreach ($options as $option => $value) {
$result[] = \sprintf("-d%s=%s", $option, $value);

View File

@ -4,8 +4,16 @@ namespace Amp\Parallel\Sync;
use Amp\Serialization\SerializationException as SerializerException;
// Alias must be defined in an always-loaded file as catch blocks do not trigger the autoloader.
\class_alias(SerializerException::class, SerializationException::class);
if (!\class_exists(SerializationException::class)) {
// Wrap definition in error handler to avoid apparently PHP bug with preloading, see amphp/parallel#159
set_error_handler(function (): bool { return true; });
try {
// Alias must be defined in an always-loaded file as catch blocks do not trigger the autoloader.
\class_alias(SerializerException::class, SerializationException::class);
} finally {
restore_error_handler();
}
}
/**
* @param \Throwable $exception

View File

@ -0,0 +1,85 @@
name: Continuous Integration
on:
- push
- pull_request
jobs:
tests:
strategy:
matrix:
include:
- operating-system: 'ubuntu-latest'
php-version: '7.4'
- operating-system: 'ubuntu-latest'
php-version: '8.0'
- operating-system: 'ubuntu-latest'
php-version: '8.1'
- operating-system: 'windows-latest'
php-version: '8.1'
job-description: 'on Windows'
- operating-system: 'macos-latest'
php-version: '8.1'
job-description: 'on macOS'
- operating-system: 'ubuntu-latest'
php-version: '8.2'
name: PHP ${{ matrix.php-version }} ${{ matrix.job-description }}
runs-on: ${{ matrix.operating-system }}
steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
- name: Get Composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-${{ matrix.composer-flags }}
restore-keys: |
composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-
composer-${{ runner.os }}-${{ matrix.php-version }}-
composer-${{ runner.os }}-
composer-
- name: Install dependencies
uses: nick-invision/retry@v2
with:
timeout_minutes: 5
max_attempts: 5
retry_wait_seconds: 30
command: |
composer update --optimize-autoloader --no-interaction --no-progress ${{ matrix.composer-flags }}
composer info -D
- name: Run tests
run: vendor/bin/phpunit ${{ matrix.phpunit-flags }}
- name: Run static analysis
run: vendor/bin/psalm.phar
- name: Run style fixer
env:
PHP_CS_FIXER_IGNORE_ENV: 1
run: vendor/bin/php-cs-fixer --diff --dry-run -v fix
if: runner.os != 'Windows'

View File

@ -1,3 +0,0 @@
[submodule "docs/.shared"]
path = docs/.shared
url = https://github.com/amphp/website-shared

View File

@ -0,0 +1,11 @@
<?php
$config = new Amp\CodeStyle\Config();
$config->getFinder()
->in(__DIR__ . '/examples')
->in(__DIR__ . '/src')
->in(__DIR__ . '/test');
$config->setCacheFile(__DIR__ . '/.php_cs.cache');
return $config;

View File

@ -1,39 +0,0 @@
<?php
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([
"@PSR1" => true,
"@PSR2" => true,
"braces" => [
"allow_single_line_closure" => true,
"position_after_functions_and_oop_constructs" => "same",
],
"array_syntax" => ["syntax" => "short"],
"cast_spaces" => true,
"combine_consecutive_unsets" => true,
"function_to_constant" => true,
"no_multiline_whitespace_before_semicolons" => true,
"no_unused_imports" => true,
"no_useless_else" => true,
"no_useless_return" => true,
"no_whitespace_before_comma_in_array" => true,
"no_whitespace_in_blank_line" => true,
"non_printable_character" => true,
"normalize_index_brace" => true,
"ordered_imports" => true,
"php_unit_construct" => true,
"php_unit_dedicate_assert" => true,
"php_unit_fqcn_annotation" => true,
"phpdoc_summary" => true,
"phpdoc_types" => true,
"psr4" => true,
"return_type_declaration" => ["space_before" => "none"],
"short_scalar_cast" => true,
"single_blank_line_before_namespace" => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__ . "/lib")
->in(__DIR__ . "/test")
);

View File

@ -1,7 +1,7 @@
The MIT License (MIT)
Copyright (c) 2017 amphp
Copyright (c) 2017-2022 amphp (Niklas Keller, Aaron Piotrowski, and contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,45 +0,0 @@
PHP_BIN := php
COMPOSER_BIN := composer
COVERAGE = coverage
SRCS = lib test
find_php_files = $(shell find $(1) -type f -name "*.php")
src = $(foreach d,$(SRCS),$(call find_php_files,$(d)))
.PHONY: test
test: setup phpunit code-style
.PHONY: clean
clean: clean-coverage clean-vendor
.PHONY: clean-coverage
clean-coverage:
test ! -e coverage || rm -r coverage
.PHONY: clean-vendor
clean-vendor:
test ! -e vendor || rm -r vendor
.PHONY: setup
setup: vendor/autoload.php
.PHONY: deps-update
deps-update:
$(COMPOSER_BIN) update
.PHONY: phpunit
phpunit: setup
$(PHP_BIN) vendor/bin/phpunit
.PHONY: code-style
code-style: setup
PHP_CS_FIXER_IGNORE_ENV=1 $(PHP_BIN) vendor/bin/php-cs-fixer --diff -v fix
composer.lock: composer.json
$(COMPOSER_BIN) install
touch $@
vendor/autoload.php: composer.lock
$(COMPOSER_BIN) install
touch $@

View File

@ -1,10 +1,7 @@
# parser
# amphp/parser
[![Build Status](https://img.shields.io/travis/amphp/parser/master.svg?style=flat-square)](https://travis-ci.org/amphp/parser)
[![CoverageStatus](https://img.shields.io/coveralls/amphp/parser/master.svg?style=flat-square)](https://coveralls.io/github/amphp/parser?branch=master)
![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)
`amphp/parser` is a streaming generator parser.
AMPHP is a collection of event-driven libraries for PHP designed with fibers and concurrency in mind.
`amphp/parser` allows easily building streaming generator parsers.
## Installation
@ -16,11 +13,42 @@ composer require amphp/parser
## Requirements
- PHP 7.0+
- PHP 7.4+
## Documentation
## Usage
Documentation is bundled within this repository in the [`./docs`](./docs) directory.
PHP's generators are a great way for building incremental parsers.
## Example
This simple parser parses a line delimited protocol and prints a message for each line. Instead of printing a message, you could also invoke a data callback.
```php
$parser = new Parser((function () {
while (true) {
$line = yield "\r\n";
if (trim($line) === "") {
continue;
}
print "New item: {$line}" . PHP_EOL;
}
})());
for ($i = 0; $i < 100; $i++) {
$parser->push("bar\r");
$parser->push("\nfoo");
}
```
Furthere examples can be found in other AMPHP packages which this library to build streaming parsers.
- [`ChannelParser`](https://github.com/amphp/byte-stream/blob/5c7eb399b746a582e9598935b26483b214250c34/src/Internal/ChannelParser.php#L28) in [`amphp/byte-stream`](https://github.com/amphp/byte-stream)
- [`RespParser`](https://github.com/amphp/redis/blob/649cff6d5e6b4c579dcab1a20511a437cbe3d62a/src/Connection/RespParser.php#L31) in [`amphp/redis`](https://github.com/amphp/redis)
## Yield Behavior
You can either `yield` a `string` that's used as delimiter, an `integer` that's used as length, or `null` to flush any remaining buffer in the parser (if any) or await the next call to `Parser::push()`.
## Versioning

View File

@ -23,15 +23,16 @@
}
],
"require": {
"php": ">=7"
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^6",
"friendsofphp/php-cs-fixer": "^2.3"
"phpunit/phpunit": "^9",
"amphp/php-cs-fixer-config": "^2",
"psalm/phar": "^5.4"
},
"autoload": {
"psr-4": {
"Amp\\Parser\\": "lib"
"Amp\\Parser\\": "src"
}
},
"autoload-dev": {

View File

@ -1,5 +0,0 @@
source "https://rubygems.org"
gem "github-pages"
gem "kramdown"
gem "jekyll-github-metadata"
gem "jekyll-relative-links"

View File

@ -1,202 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (4.2.8)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
colorator (1.1.0)
ethon (0.10.1)
ffi (>= 1.3.0)
execjs (2.7.0)
faraday (0.12.1)
multipart-post (>= 1.2, < 3)
ffi (1.9.18)
forwardable-extended (2.6.0)
gemoji (3.0.0)
github-pages (139)
activesupport (= 4.2.8)
github-pages-health-check (= 1.3.3)
jekyll (= 3.4.3)
jekyll-avatar (= 0.4.2)
jekyll-coffeescript (= 1.0.1)
jekyll-default-layout (= 0.1.4)
jekyll-feed (= 0.9.2)
jekyll-gist (= 1.4.0)
jekyll-github-metadata (= 2.3.1)
jekyll-mentions (= 1.2.0)
jekyll-optional-front-matter (= 0.1.2)
jekyll-paginate (= 1.1.0)
jekyll-readme-index (= 0.1.0)
jekyll-redirect-from (= 0.12.1)
jekyll-relative-links (= 0.4.0)
jekyll-sass-converter (= 1.5.0)
jekyll-seo-tag (= 2.2.3)
jekyll-sitemap (= 1.0.0)
jekyll-swiss (= 0.4.0)
jekyll-theme-architect (= 0.0.4)
jekyll-theme-cayman (= 0.0.4)
jekyll-theme-dinky (= 0.0.4)
jekyll-theme-hacker (= 0.0.4)
jekyll-theme-leap-day (= 0.0.4)
jekyll-theme-merlot (= 0.0.4)
jekyll-theme-midnight (= 0.0.4)
jekyll-theme-minimal (= 0.0.4)
jekyll-theme-modernist (= 0.0.4)
jekyll-theme-primer (= 0.2.1)
jekyll-theme-slate (= 0.0.4)
jekyll-theme-tactile (= 0.0.4)
jekyll-theme-time-machine (= 0.0.4)
jekyll-titles-from-headings (= 0.1.5)
jemoji (= 0.8.0)
kramdown (= 1.13.2)
liquid (= 3.0.6)
listen (= 3.0.6)
mercenary (~> 0.3)
minima (= 2.1.1)
rouge (= 1.11.1)
terminal-table (~> 1.4)
github-pages-health-check (1.3.3)
addressable (~> 2.3)
net-dns (~> 0.8)
octokit (~> 4.0)
public_suffix (~> 2.0)
typhoeus (~> 0.7)
html-pipeline (2.6.0)
activesupport (>= 2)
nokogiri (>= 1.4)
i18n (0.8.4)
jekyll (3.4.3)
addressable (~> 2.4)
colorator (~> 1.0)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 1.1)
kramdown (~> 1.3)
liquid (~> 3.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
rouge (~> 1.7)
safe_yaml (~> 1.0)
jekyll-avatar (0.4.2)
jekyll (~> 3.0)
jekyll-coffeescript (1.0.1)
coffee-script (~> 2.2)
jekyll-default-layout (0.1.4)
jekyll (~> 3.0)
jekyll-feed (0.9.2)
jekyll (~> 3.3)
jekyll-gist (1.4.0)
octokit (~> 4.2)
jekyll-github-metadata (2.3.1)
jekyll (~> 3.1)
octokit (~> 4.0, != 4.4.0)
jekyll-mentions (1.2.0)
activesupport (~> 4.0)
html-pipeline (~> 2.3)
jekyll (~> 3.0)
jekyll-optional-front-matter (0.1.2)
jekyll (~> 3.0)
jekyll-paginate (1.1.0)
jekyll-readme-index (0.1.0)
jekyll (~> 3.0)
jekyll-redirect-from (0.12.1)
jekyll (~> 3.3)
jekyll-relative-links (0.4.0)
jekyll (~> 3.3)
jekyll-sass-converter (1.5.0)
sass (~> 3.4)
jekyll-seo-tag (2.2.3)
jekyll (~> 3.3)
jekyll-sitemap (1.0.0)
jekyll (~> 3.3)
jekyll-swiss (0.4.0)
jekyll-theme-architect (0.0.4)
jekyll (~> 3.3)
jekyll-theme-cayman (0.0.4)
jekyll (~> 3.3)
jekyll-theme-dinky (0.0.4)
jekyll (~> 3.3)
jekyll-theme-hacker (0.0.4)
jekyll (~> 3.3)
jekyll-theme-leap-day (0.0.4)
jekyll (~> 3.3)
jekyll-theme-merlot (0.0.4)
jekyll (~> 3.3)
jekyll-theme-midnight (0.0.4)
jekyll (~> 3.3)
jekyll-theme-minimal (0.0.4)
jekyll (~> 3.3)
jekyll-theme-modernist (0.0.4)
jekyll (~> 3.3)
jekyll-theme-primer (0.2.1)
jekyll (~> 3.3)
jekyll-theme-slate (0.0.4)
jekyll (~> 3.3)
jekyll-theme-tactile (0.0.4)
jekyll (~> 3.3)
jekyll-theme-time-machine (0.0.4)
jekyll (~> 3.3)
jekyll-titles-from-headings (0.1.5)
jekyll (~> 3.3)
jekyll-watch (1.5.0)
listen (~> 3.0, < 3.1)
jemoji (0.8.0)
activesupport (~> 4.0)
gemoji (~> 3.0)
html-pipeline (~> 2.2)
jekyll (>= 3.0)
kramdown (1.13.2)
liquid (3.0.6)
listen (3.0.6)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9.7)
mercenary (0.3.6)
mini_portile2 (2.2.0)
minima (2.1.1)
jekyll (~> 3.3)
minitest (5.10.2)
multipart-post (2.0.0)
net-dns (0.8.0)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
octokit (4.7.0)
sawyer (~> 0.8.0, >= 0.5.3)
pathutil (0.14.0)
forwardable-extended (~> 2.6)
public_suffix (2.0.5)
rb-fsevent (0.9.8)
rb-inotify (0.9.8)
ffi (>= 0.5.0)
rouge (1.11.1)
safe_yaml (1.0.4)
sass (3.4.24)
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
typhoeus (0.8.0)
ethon (>= 0.8.0)
tzinfo (1.2.3)
thread_safe (~> 0.1)
unicode-display_width (1.2.1)
PLATFORMS
ruby
DEPENDENCIES
github-pages
jekyll-github-metadata
jekyll-relative-links
kramdown
BUNDLED WITH
1.15.0

View File

@ -1,23 +0,0 @@
kramdown:
input: GFM
toc_levels: 2..3
baseurl: "/parser"
layouts_dir: ".shared/layout"
exclude: ["Gemfile", "Gemfile.lock", "README.md", "vendor"]
include: [".shared"]
repository: amphp/parser
gems:
- "jekyll-github-metadata"
- "jekyll-relative-links"
defaults:
- scope:
path: ""
type: "pages"
values:
layout: "docs"
asset_path: "/parser/.shared/asset"

View File

@ -1,32 +0,0 @@
---
title: Parser
permalink: /
---
PHP's generators are a great way for building incremental parsers.
## Example
This simple parser parses a line delimited protocol and prints a message for each line. Instead of printing a message, you could also invoke a data callback.
```php
$parser = new Parser((function () {
while (true) {
$line = yield "\r\n";
if (trim($line) === "") {
continue;
}
print "New item: {$line}" . PHP_EOL;
}
})());
for ($i = 0; $i < 100; $i++) {
$parser->push("bar\r");
$parser->push("\nfoo");
}
```
## Yield Behavior
You can either `yield` a `string` that's used as delimiter, an `integer` that's used as length, or `null` for consuming everything that's available.

View File

@ -1,131 +0,0 @@
<?php
namespace Amp\Parser;
class Parser {
/** @var \Generator */
private $generator;
/** @var string */
private $buffer = '';
/** @var int|string|null */
private $delimiter;
/**
* @param \Generator $generator
*
* @throws InvalidDelimiterError If the generator yields an invalid delimiter.
* @throws \Throwable If the generator throws.
*/
public function __construct(\Generator $generator) {
$this->generator = $generator;
$this->delimiter = $this->generator->current();
if (!$this->generator->valid()) {
$this->generator = null;
return;
}
if ($this->delimiter !== null
&& (!\is_int($this->delimiter) || $this->delimiter <= 0)
&& (!\is_string($this->delimiter) || !\strlen($this->delimiter))
) {
throw new InvalidDelimiterError(
$generator,
\sprintf(
"Invalid value yielded: Expected NULL, an int greater than 0, or a non-empty string; %s given",
\is_object($this->delimiter) ? \sprintf("instance of %s", \get_class($this->delimiter)) : \gettype($this->delimiter)
)
);
}
}
/**
* Cancels the generator parser and returns any remaining data in the internal buffer. Writing data after calling
* this method will result in an error.
*
* @return string
*/
final public function cancel(): string {
$this->generator = null;
return $this->buffer;
}
/**
* @return bool True if the parser can still receive more data to parse, false if it has ended and calling push
* will throw an exception.
*/
final public function isValid(): bool {
return $this->generator !== null;
}
/**
* Adds data to the internal buffer and tries to continue parsing.
*
* @param string $data Data to append to the internal buffer.
*
* @throws InvalidDelimiterError If the generator yields an invalid delimiter.
* @throws \Error If parsing has already been cancelled.
* @throws \Throwable If the generator throws.
*/
final public function push(string $data) {
if ($this->generator === null) {
throw new \Error("The parser is no longer writable");
}
$this->buffer .= $data;
$end = false;
try {
while ($this->buffer !== "") {
if (\is_int($this->delimiter)) {
if (\strlen($this->buffer) < $this->delimiter) {
break; // Too few bytes in buffer.
}
$send = \substr($this->buffer, 0, $this->delimiter);
$this->buffer = \substr($this->buffer, $this->delimiter);
} elseif (\is_string($this->delimiter)) {
if (($position = \strpos($this->buffer, $this->delimiter)) === false) {
break;
}
$send = \substr($this->buffer, 0, $position);
$this->buffer = \substr($this->buffer, $position + \strlen($this->delimiter));
} else {
$send = $this->buffer;
$this->buffer = "";
}
$this->delimiter = $this->generator->send($send);
if (!$this->generator->valid()) {
$end = true;
break;
}
if ($this->delimiter !== null
&& (!\is_int($this->delimiter) || $this->delimiter <= 0)
&& (!\is_string($this->delimiter) || !\strlen($this->delimiter))
) {
throw new InvalidDelimiterError(
$this->generator,
\sprintf(
"Invalid value yielded: Expected NULL, an int greater than 0, or a non-empty string; %s given",
\is_object($this->delimiter) ? \sprintf("instance of %s", \get_class($this->delimiter)) : \gettype($this->delimiter)
)
);
}
}
} catch (\Throwable $exception) {
$end = true;
throw $exception;
} finally {
if ($end) {
$this->generator = null;
}
}
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
phpVersion="8.1"
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="examples" />
<directory name="src" />
</projectFiles>
<issueHandlers>
<ForbiddenCode>
<errorLevel type="suppress">
<directory name="examples"/>
</errorLevel>
</ForbiddenCode>
<DocblockTypeContradiction>
<errorLevel type="suppress">
<directory name="src" />
</errorLevel>
</DocblockTypeContradiction>
<MissingClosureReturnType>
<errorLevel type="suppress">
<directory name="src" />
</errorLevel>
</MissingClosureReturnType>
</issueHandlers>
</psalm>

View File

@ -1,14 +1,11 @@
<?php
<?php declare(strict_types=1);
namespace Amp\Parser;
class InvalidDelimiterError extends \Error {
/**
* @param \Generator $generator
* @param string $prefix
* @param \Throwable|null $previous
*/
public function __construct(\Generator $generator, string $prefix, \Throwable $previous = null) {
class InvalidDelimiterError extends \Error
{
public function __construct(\Generator $generator, string $prefix, \Throwable $previous = null)
{
$yielded = $generator->current();
$prefix .= \sprintf(
"; %s yielded at key %s",

View File

@ -0,0 +1,153 @@
<?php declare(strict_types=1);
namespace Amp\Parser;
class Parser
{
/** @var \Generator<array-key, int|string|null, string, void>|null */
private ?\Generator $generator;
/** @var list<string> */
private array $buffers = [];
private int $bufferLength = 0;
/** @var int|string|null */
private $delimiter;
/**
* @param \Generator<array-key, int|string|null, string, void> $generator
*
* @throws InvalidDelimiterError If the generator yields an invalid delimiter.
* @throws \Throwable If the generator throws.
*/
public function __construct(\Generator $generator)
{
$this->generator = $generator;
$this->delimiter = $this->filterDelimiter($this->generator->current());
if (!$this->generator->valid()) {
$this->generator = null;
}
}
/**
* Cancels the generator parser and returns any remaining data in the internal buffer. Writing data after calling
* this method will result in an error.
*/
final public function cancel(): string
{
$buffer = \implode($this->buffers);
$this->buffers = [];
$this->generator = null;
return $buffer;
}
/**
* @return bool True if the parser can still receive more data to parse, false if it has ended and calling push
* will throw an exception.
*/
final public function isValid(): bool
{
return $this->generator !== null;
}
/**
* Adds data to the internal buffer and tries to continue parsing.
*
* @param string $data Data to append to the internal buffer.
*
* @throws InvalidDelimiterError If the generator yields an invalid delimiter.
* @throws \Error If parsing has already been cancelled.
* @throws \Throwable If the generator throws.
*/
final public function push(string $data): void
{
if ($this->generator === null) {
throw new \Error("The parser is no longer writable");
}
$length = \strlen($data);
if ($length === 0) {
return;
}
$this->bufferLength += $length;
try {
do {
if (\is_int($this->delimiter) && $this->bufferLength < $this->delimiter) {
return;
}
if (!empty($this->buffers)) {
$this->buffers[] = $data;
$data = \implode($this->buffers);
$this->buffers = [];
}
if (\is_int($this->delimiter)) {
$cutAt = $retainFrom = $this->delimiter;
} elseif (\is_string($this->delimiter)) {
if (($cutAt = \strpos($data, $this->delimiter)) === false) {
return;
}
$retainFrom = $cutAt + \strlen($this->delimiter);
} else {
$cutAt = $retainFrom = $this->bufferLength;
}
if ($this->bufferLength > $cutAt) {
$send = \substr($data, 0, $cutAt);
$data = \substr($data, $retainFrom);
} else {
$send = $data;
$data = '';
}
$this->bufferLength -= $retainFrom;
$this->delimiter = $this->filterDelimiter($this->generator->send($send));
if (!$this->generator->valid()) {
$this->generator = null;
return;
}
} while ($this->bufferLength);
} catch (\Throwable $exception) {
$this->generator = null;
throw $exception;
} finally {
if (\strlen($data)) {
$this->buffers[] = $data;
}
}
}
/**
* @param mixed $delimiter Value yielded from Generator.
* @return int|string|null
*/
private function filterDelimiter($delimiter)
{
\assert($this->generator instanceof \Generator, "Invalid parser state");
if ($delimiter !== null
&& (!\is_int($delimiter) || $delimiter <= 0)
&& (!\is_string($delimiter) || !\strlen($delimiter))
) {
throw new InvalidDelimiterError(
$this->generator,
\sprintf(
"Invalid value yielded: Expected NULL, an int greater than 0, or a non-empty string; %s given",
\is_object($delimiter) ? \sprintf("instance of %s", \get_class($delimiter)) : \gettype($delimiter),
)
);
}
return $delimiter;
}
}

View File

@ -3,6 +3,7 @@
namespace Amp\Process\Internal\Posix;
use Amp\Deferred;
use Amp\Loop;
use Amp\Process\Internal\ProcessHandle;
/** @internal */
@ -32,4 +33,28 @@ final class Handle extends ProcessHandle
/** @var int */
public $originalParentPid;
/** @var int */
public $shellPid;
public function wait()
{
if ($this->shellPid === 0) {
return;
}
$pid = $this->shellPid;
$this->shellPid = 0;
Loop::unreference(Loop::repeat(100, static function (string $watcherId) use ($pid) {
if (!\extension_loaded('pcntl') || \pcntl_waitpid($pid, $status, \WNOHANG) !== 0) {
Loop::cancel($watcherId);
}
}));
}
public function __destruct()
{
$this->wait();
}
}

View File

@ -39,6 +39,8 @@ final class Runner implements ProcessRunner
} else {
$handle->joinDeferred->resolve((int) \rtrim(@\stream_get_contents($stream)));
}
$handle->wait();
}
public static function onProcessStartExtraDataPipeReadable($watcher, $stream, $data)
@ -102,6 +104,8 @@ final class Runner implements ProcessRunner
throw new ProcessException("Could not get process status");
}
$handle->shellPid = \proc_get_status($handle->proc)['pid'];
$stdinDeferred = new Deferred;
$handle->stdin = new ProcessOutputStream($stdinDeferred->promise());

View File

@ -1,8 +1,8 @@
The MIT License (MIT)
Copyright (c) 2017-2020 Soufiane Ghzal <sghzal@gmail.com>
Copyright (c) 2020-2022 Graham Campbell <hello@gjcampbell.co.uk>
Copyright (c) 2020-2022 Enrico Dias <enricodias@gmail.com>
Copyright (c) 2020-2023 Graham Campbell <hello@gjcampbell.co.uk>
Copyright (c) 2020-2023 Enrico Dias <enricodias@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -16,8 +16,8 @@
}
],
"require": {
"php": "^7.3 || ^8.0",
"chrome-php/wrench": "^1.3",
"php": "^7.4.15 || ^8.0.2",
"chrome-php/wrench": "^1.5",
"evenement/evenement": "^3.0.1",
"monolog/monolog": "^1.27.1 || ^2.8 || ^3.2",
"psr/log": "^1.1 || ^2.0 || ^3.0",
@ -26,8 +26,8 @@
"symfony/process": "^4.4 || ^5.0 || ^6.0"
},
"require-dev":{
"bamarni/composer-bin-plugin": "^1.8.1",
"phpunit/phpunit": "^9.5.23",
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^9.6.3 || ^10.0.12",
"symfony/var-dumper": "^4.4 || ^5.0 || ^6.0"
},
"autoload":{

View File

@ -120,12 +120,18 @@ class Browser
*/
final public function sendCloseMessage(): void
{
if (!$this->connection->isConnected()) {
$this->connection->getLogger()->debug('process: chrome already stopped, ignoring');
return;
}
$r = $this->connection->sendMessageSync(new Message('Browser.close'));
if (!$r->isSuccessful()) {
// log
$this->connection->getLogger()->debug('process: ✗ could not close gracefully');
throw new \Exception('cannot close, Browser.close not supported');
}
$this->connection->disconnect();
}
/**
@ -181,6 +187,23 @@ class Browser
return \array_values($this->targets);
}
/**
* Find a target matching the type and title.
*
* @param string $type
* @param string $title
*/
public function findTarget(string $type, string $title): ?Target
{
foreach ($this->targets as $target) {
if ($target->getTargetInfo('type') === $type && $target->getTargetInfo('title') === $title) {
return $target;
}
}
return null;
}
/**
* @param string $targetId
*

View File

@ -294,6 +294,9 @@ class BrowserProcess implements LoggerAwareInterface
// auto debug port
'--remote-debugging-port=0',
// allow remote access
'--remote-allow-origins=*',
// disable undesired features
'--disable-background-networking',
'--disable-background-timer-throttling',
@ -344,6 +347,11 @@ class BrowserProcess implements LoggerAwareInterface
$args[] = '--window-size='.\implode(',', $options['windowSize']);
}
if (\array_key_exists('userCrashDumpsDir', $options)) {
$args[] = '--enable-crash-reporter';
$args[] = '--crash-dumps-dir='.$options['userCrashDumpsDir'];
}
// sandbox mode - useful if you want to use chrome headless inside docker
if (\array_key_exists('noSandbox', $options) && $options['noSandbox']) {
$args[] = '--no-sandbox';
@ -432,6 +440,9 @@ class BrowserProcess implements LoggerAwareInterface
$this->logger->debug('process: ✓ accepted output');
return $matches[1];
} elseif (\preg_match('/Cannot start http server for devtools\./', $output, $matches)) {
$process->stop();
throw new \RuntimeException('Devtools could not start');
} else {
// log
$this->logger->debug('process: ignoring output:'.\trim($output));
@ -446,6 +457,7 @@ class BrowserProcess implements LoggerAwareInterface
return Utils::tryWithTimeout($timeout, $generator($process));
} catch (OperationTimedOut $e) {
$process->stop();
throw new \RuntimeException('Cannot start browser', 0, $e);
}
}

View File

@ -44,6 +44,7 @@ class BrowserFactory
* - startupTimeout: Maximum time in seconds to wait for chrome to start (default: 30 sec)
* - userAgent: User agent to use for the whole browser
* - userDataDir: Chrome user data dir (default: a new empty dir is generated temporarily)
* - userCrashDumpsDir: The directory crashpad should store dumps in (crash reporter will be enabled automatically)
* - windowSize: Size of the window. ex: `[1920, 1080]` (default: none)
*/
protected $options = [];

View File

@ -267,7 +267,7 @@ class Connection extends EventEmitter implements LoggerAwareInterface
public function sendMessageSync(Message $message, int $timeout = null): Response
{
$responseReader = $this->sendMessage($message);
$response = $responseReader->waitForResponse($timeout ?? $this->sendSyncDefaultTimeout);
$response = $responseReader->waitForResponse($timeout);
return $response;
}

View File

@ -109,8 +109,7 @@ class ResponseReader
return $this->getResponse();
}
// default 2000ms
$timeout = $timeout ?? 2000;
$timeout = $timeout ?? $this->connection->getSendSyncDefaultTimeout();
return Utils::tryWithTimeout($timeout * 1000, $this->waitForResponseGenerator());
}

View File

@ -86,7 +86,7 @@ class Session extends EventEmitter
{
$responseReader = $this->sendMessage($message);
$response = $responseReader->waitForResponse($timeout ?? $this->getConnection()->getSendSyncDefaultTimeout());
$response = $responseReader->waitForResponse($timeout);
if (!$response) {
throw new NoResponseAvailable('No response was sent in the given timeout');

View File

@ -12,14 +12,16 @@ class Dom extends Node
public function __construct(Page $page)
{
$message = new Message('DOM.getDocument');
$stream = $page->getSession()->sendMessage($message);
$response = $stream->waitForResponse(1000);
$response = $page->getSession()->sendMessageSync($message);
$rootNodeId = $response->getResultData('root')['nodeId'];
parent::__construct($page, $rootNodeId);
}
/**
* @return Node[]
*/
public function search(string $selector): array
{
$message = new Message('DOM.performSearch', [

View File

@ -857,8 +857,10 @@ class Page
*
* @throws CommunicationException
*/
public function setHtml(string $html, int $timeout = 3000): void
public function setHtml(string $html, int $timeout = 3000, string $eventName = self::LOAD): void
{
$time = \hrtime(true) / 1000 / 1000;
$this->getSession()->sendMessageSync(
new Message(
'Page.setDocumentContent',
@ -866,10 +868,13 @@ class Page
'frameId' => $this->getFrameManager()->getMainFrame()->getFrameId(),
'html' => $html,
]
)
),
$timeout
);
$this->waitForReload(self::LOAD, $timeout, '');
$timeout -= (int) \floor((\hrtime(true) / 1000 / 1000) - $time);
$this->waitForReload($eventName, \max(0, $timeout), '');
}
/**

View File

@ -11,14 +11,14 @@
}
],
"require": {
"php": "^7.3 || ^8.0",
"php": "^7.4.15 || ^8.0.2",
"ext-sockets": "*",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"symfony/polyfill-php80": "^1.26"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1",
"phpunit/phpunit": "^9.5.23"
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^9.6.3 || ^10.0.12"
},
"autoload": {
"psr-4": {

View File

@ -187,14 +187,7 @@ abstract class Protocol
*/
public function generateKey(): string
{
if (\extension_loaded('openssl')) {
$key = \openssl_random_pseudo_bytes(16);
} else {
// SHA1 is 128 bit (= 16 bytes)
$key = \sha1(\spl_object_hash($this).\mt_rand(0, \PHP_INT_MAX).\uniqid('', true), true);
}
return \base64_encode($key);
return \base64_encode(\random_bytes(16));
}
/**
@ -382,7 +375,7 @@ abstract class Protocol
*
* @return string[]
*/
protected function getSuccessResponseHeaders($key)
protected function getSuccessResponseHeaders(string $key): array
{
return [
self::HEADER_UPGRADE => self::UPGRADE_VALUE,
@ -398,13 +391,13 @@ abstract class Protocol
*
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-4.2.2
*
* @param string $encoded_key
* @param string $key
*
* @return string
*/
protected function getAcceptValue($encoded_key)
protected function getAcceptValue(string $key): string
{
return \base64_encode(\sha1($encoded_key.self::MAGIC_GUID, true));
return \base64_encode(\sha1($key.self::MAGIC_GUID, true));
}
/**
@ -437,7 +430,7 @@ abstract class Protocol
*
* @return string
*/
public function getResponseError($e, array $headers = [])
public function getResponseError($e, array $headers = []): string
{
$code = false;
@ -463,24 +456,37 @@ abstract class Protocol
return false;
}
$headers = $this->getHeaders($response);
$statusCode = $this->getStatusCode($response);
if (!isset($headers[self::HEADER_ACCEPT])) {
throw new HandshakeException('No accept header receieved on handshake response');
if (self::HTTP_SWITCHING_PROTOCOLS !== $statusCode) {
$errorMessage = \explode("\n", \trim($this->getBody($response)), 2)[0];
throw new HandshakeException(\trim(\sprintf('Expected handshake response status code %d, but received %d. %s', self::HTTP_SWITCHING_PROTOCOLS, $statusCode, $errorMessage)));
}
$accept = $headers[self::HEADER_ACCEPT];
$acceptHeaderValue = $this->getHeaders($response)[self::HEADER_ACCEPT] ?? '';
if (!$accept) {
throw new HandshakeException('Invalid accept header');
if ('' === $acceptHeaderValue) {
throw new HandshakeException('No accept header received on handshake response');
}
$expected = $this->getAcceptValue($key);
return $this->getEncodedHash($key) === $acceptHeaderValue;
}
\preg_match('#Sec-WebSocket-Accept:\s(.*)$#imU', $response, $matches);
$keyAccept = \trim($matches[1]);
/**
* Gets the status code from a full response.
*
* If there is no status line, we return 0.
*
* @return int
*/
protected function getStatusCode(string $response): int
{
[$statusLine] = \explode("\r\n", $response, 2);
return $keyAccept === $this->getEncodedHash($key);
[$protocol, $statusCode] = \explode(' ', $response, 2);
return (int) $statusCode;
}
/**
@ -488,7 +494,7 @@ abstract class Protocol
*
* @return array<string, array>
*/
protected function getHeaders(string $response)
protected function getHeaders(string $response): array
{
$parts = \explode("\r\n\r\n", $response, 2);
@ -500,16 +506,16 @@ abstract class Protocol
$return = [];
foreach (\explode("\r\n", $headers) as $header) {
$parts = \explode(': ', $header, 2);
$parts = \explode(':', $header, 2);
if (2 == \count($parts)) {
[$name, $value] = $parts;
if (!isset($return[$name])) {
$return[$name] = $value;
$return[$name] = \trim($value);
} else {
if (\is_array($return[$name])) {
$return[$name][] = $value;
$return[$name][] = \trim($value);
} else {
$return[$name] = [$return[$name], $value];
$return[$name] = [$return[$name], \trim($value)];
}
}
}
@ -518,6 +524,16 @@ abstract class Protocol
return \array_change_key_case($return);
}
/**
* Gets the body from a full response.
*
* @return string
*/
protected function getBody(string $response): string
{
return \explode("\r\n\r\n", $response, 2)[1] ?? '';
}
/**
* Gets an encoded hash for a key.
*
@ -525,7 +541,7 @@ abstract class Protocol
*
* @return string
*/
public function getEncodedHash($key)
public function getEncodedHash(string $key): string
{
return \base64_encode(\pack('H*', \sha1($key.self::MAGIC_GUID)));
}
@ -629,7 +645,7 @@ abstract class Protocol
*
* @throws InvalidArgumentException
*/
protected function getRequestHeaders(string $response)
protected function getRequestHeaders(string $response): array
{
$eol = \stripos($response, "\r\n");

View File

@ -1,7 +1,7 @@
{
"require": {
"php": "^7.4",
"phpstan/phpstan": "1.8.2"
"phpstan/phpstan": "1.10.3"
},
"config": {
"preferred-install": "dist"

View File

@ -42,6 +42,9 @@ namespace Composer\Autoload;
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var ?string */
private $vendorDir;
@ -106,6 +109,7 @@ class ClassLoader
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
@ -425,7 +429,8 @@ class ClassLoader
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
@ -555,18 +560,26 @@ class ClassLoader
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{
include $file;
/**
* @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

@ -9,21 +9,20 @@ return array(
'e8aa6e4b5a1db2f56ae794f1505391a8' => $vendorDir . '/amphp/amp/lib/functions.php',
'76cd0796156622033397994f25b0d8fc' => $vendorDir . '/amphp/amp/lib/Internal/functions.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'6cd5651c4fef5ed6b63e8d8b8ffbf3cc' => $vendorDir . '/amphp/byte-stream/lib/functions.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'3da389f428d8ee50333e4391c3f45046' => $vendorDir . '/amphp/serialization/src/functions.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'8dc56fe697ca93c4b40d876df1c94584' => $vendorDir . '/amphp/process/lib/functions.php',
'3da389f428d8ee50333e4391c3f45046' => $vendorDir . '/amphp/serialization/src/functions.php',
'bcb7d4fc55f4b1a7e10f5806723e9892' => $vendorDir . '/amphp/sync/src/functions.php',
'e187e371b30897d6dc51cac6a8c94ff6' => $vendorDir . '/amphp/sync/src/ConcurrentIterator/functions.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'430de19db8b7ee88fdbe5c545d82d33d' => $vendorDir . '/amphp/parallel/lib/Context/functions.php',
'888e1afeed2e8d13ef5a662692091e6e' => $vendorDir . '/amphp/parallel/lib/Sync/functions.php',
'384cf4f2eb4d2f896db72315a76066ad' => $vendorDir . '/amphp/parallel/lib/Worker/functions.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php',
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'861372841bb4b8ba9fdd215894666f40' => $vendorDir . '/amphp/parallel-functions/src/functions.php',

View File

@ -22,24 +22,27 @@ return array(
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
'PandoraFMS\\Enterprise\\' => array($baseDir . '/enterprise/include/lib'),
'PandoraFMS\\' => array($baseDir . '/include/lib'),
'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'),
'Mpdf\\' => array($vendorDir . '/mpdf/mpdf/src'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'Models\\' => array($baseDir . '/include/rest-api/models'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Laminas\\Json\\' => array($vendorDir . '/laminas/laminas-json/src'),
'Http\\Message\\' => array($vendorDir . '/php-http/message-factory/src'),
'HeadlessChromium\\' => array($vendorDir . '/chrome-php/chrome/src'),
'Halfpastfour\\Collection\\' => array($vendorDir . '/halfpastfouram/collection/src'),
'Enterprise\\Models\\' => array($baseDir . '/enterprise/include/rest-api/models'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
'Artica\\PHPChartJS\\' => array($vendorDir . '/artica/phpchartjs/src'),
'Amp\\Sync\\' => array($vendorDir . '/amphp/sync/src'),
'Amp\\Serialization\\' => array($vendorDir . '/amphp/serialization/src'),
'Amp\\Process\\' => array($vendorDir . '/amphp/process/lib'),
'Amp\\Parser\\' => array($vendorDir . '/amphp/parser/lib'),
'Amp\\Parser\\' => array($vendorDir . '/amphp/parser/src'),
'Amp\\Parallel\\' => array($vendorDir . '/amphp/parallel/lib'),
'Amp\\ParallelFunctions\\' => array($vendorDir . '/amphp/parallel-functions/src'),
'Amp\\ByteStream\\' => array($vendorDir . '/amphp/byte-stream/lib'),

View File

@ -33,25 +33,18 @@ class ComposerAutoloaderInit94a17e624d873685991e8ae888e00eb9
$loader->register(true);
$includeFiles = \Composer\Autoload\ComposerStaticInit94a17e624d873685991e8ae888e00eb9::$files;
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire94a17e624d873685991e8ae888e00eb9($fileIdentifier, $file);
$filesToLoad = \Composer\Autoload\ComposerStaticInit94a17e624d873685991e8ae888e00eb9::$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;
}
}
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequire94a17e624d873685991e8ae888e00eb9($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}

View File

@ -10,21 +10,20 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
'e8aa6e4b5a1db2f56ae794f1505391a8' => __DIR__ . '/..' . '/amphp/amp/lib/functions.php',
'76cd0796156622033397994f25b0d8fc' => __DIR__ . '/..' . '/amphp/amp/lib/Internal/functions.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'6cd5651c4fef5ed6b63e8d8b8ffbf3cc' => __DIR__ . '/..' . '/amphp/byte-stream/lib/functions.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'3da389f428d8ee50333e4391c3f45046' => __DIR__ . '/..' . '/amphp/serialization/src/functions.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'8dc56fe697ca93c4b40d876df1c94584' => __DIR__ . '/..' . '/amphp/process/lib/functions.php',
'3da389f428d8ee50333e4391c3f45046' => __DIR__ . '/..' . '/amphp/serialization/src/functions.php',
'bcb7d4fc55f4b1a7e10f5806723e9892' => __DIR__ . '/..' . '/amphp/sync/src/functions.php',
'e187e371b30897d6dc51cac6a8c94ff6' => __DIR__ . '/..' . '/amphp/sync/src/ConcurrentIterator/functions.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'430de19db8b7ee88fdbe5c545d82d33d' => __DIR__ . '/..' . '/amphp/parallel/lib/Context/functions.php',
'888e1afeed2e8d13ef5a662692091e6e' => __DIR__ . '/..' . '/amphp/parallel/lib/Sync/functions.php',
'384cf4f2eb4d2f896db72315a76066ad' => __DIR__ . '/..' . '/amphp/parallel/lib/Worker/functions.php',
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'538ca81a9a966a6716601ecf48f4eaef' => __DIR__ . '/..' . '/opis/closure/functions.php',
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'861372841bb4b8ba9fdd215894666f40' => __DIR__ . '/..' . '/amphp/parallel-functions/src/functions.php',
@ -66,13 +65,10 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
'P' =>
array (
'Psr\\Log\\' => 8,
'Psr\\Http\\Message\\' => 17,
'PandoraFMS\\Enterprise\\' => 22,
'PandoraFMS\\' => 11,
),
'O' =>
array (
'Opis\\Closure\\' => 13,
),
'M' =>
array (
'Mpdf\\' => 5,
@ -81,10 +77,12 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
),
'L' =>
array (
'Laravel\\SerializableClosure\\' => 28,
'Laminas\\Json\\' => 13,
),
'H' =>
array (
'Http\\Message\\' => 13,
'HeadlessChromium\\' => 17,
'Halfpastfour\\Collection\\' => 24,
),
@ -95,6 +93,7 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
),
'D' =>
array (
'Doctrine\\Deprecations\\' => 22,
'Doctrine\\Common\\Lexer\\' => 22,
'DeepCopy\\' => 9,
),
@ -178,6 +177,10 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
array (
0 => __DIR__ . '/..' . '/psr/log/src',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-message/src',
),
'PandoraFMS\\Enterprise\\' =>
array (
0 => __DIR__ . '/../..' . '/enterprise/include/lib',
@ -186,10 +189,6 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
array (
0 => __DIR__ . '/../..' . '/include/lib',
),
'Opis\\Closure\\' =>
array (
0 => __DIR__ . '/..' . '/opis/closure/src',
),
'Mpdf\\' =>
array (
0 => __DIR__ . '/..' . '/mpdf/mpdf/src',
@ -202,10 +201,18 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
array (
0 => __DIR__ . '/../..' . '/include/rest-api/models',
),
'Laravel\\SerializableClosure\\' =>
array (
0 => __DIR__ . '/..' . '/laravel/serializable-closure/src',
),
'Laminas\\Json\\' =>
array (
0 => __DIR__ . '/..' . '/laminas/laminas-json/src',
),
'Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/php-http/message-factory/src',
),
'HeadlessChromium\\' =>
array (
0 => __DIR__ . '/..' . '/chrome-php/chrome/src',
@ -222,9 +229,13 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
array (
0 => __DIR__ . '/..' . '/egulias/email-validator/src',
),
'Doctrine\\Deprecations\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations',
),
'Doctrine\\Common\\Lexer\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
0 => __DIR__ . '/..' . '/doctrine/lexer/src',
),
'DeepCopy\\' =>
array (
@ -248,7 +259,7 @@ class ComposerStaticInit94a17e624d873685991e8ae888e00eb9
),
'Amp\\Parser\\' =>
array (
0 => __DIR__ . '/..' . '/amphp/parser/lib',
0 => __DIR__ . '/..' . '/amphp/parser/src',
),
'Amp\\Parallel\\' =>
array (

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
'name' => 'pandorafms/console',
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => '8d950c3672332ae0f9823f784fe4b5b23704e18f',
'reference' => 'f68cd81d35010b07e3e022ca1141a3fb639e6834',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -29,36 +29,36 @@
'dev_requirement' => false,
),
'amphp/parallel' => array(
'pretty_version' => 'v1.4.1',
'version' => '1.4.1.0',
'reference' => 'fbc128383c1ffb3823866f71b88d8c4722a25ce9',
'pretty_version' => 'v1.4.3',
'version' => '1.4.3.0',
'reference' => '3aac213ba7858566fd83d38ccb85b91b2d652cb0',
'type' => 'library',
'install_path' => __DIR__ . '/../amphp/parallel',
'aliases' => array(),
'dev_requirement' => false,
),
'amphp/parallel-functions' => array(
'pretty_version' => 'v1.0.0',
'version' => '1.0.0.0',
'reference' => 'af9795d51abfafc3676cbe7e17965479491abaad',
'pretty_version' => 'v1.1.0',
'version' => '1.1.0.0',
'reference' => '04e92fcacfc921a56dfe12c23b3265e62593a7cb',
'type' => 'library',
'install_path' => __DIR__ . '/../amphp/parallel-functions',
'aliases' => array(),
'dev_requirement' => false,
),
'amphp/parser' => array(
'pretty_version' => 'v1.0.0',
'version' => '1.0.0.0',
'reference' => 'f83e68f03d5b8e8e0365b8792985a7f341c57ae1',
'pretty_version' => 'v1.1.0',
'version' => '1.1.0.0',
'reference' => 'ff1de4144726c5dad5fab97f66692ebe8de3e151',
'type' => 'library',
'install_path' => __DIR__ . '/../amphp/parser',
'aliases' => array(),
'dev_requirement' => false,
),
'amphp/process' => array(
'pretty_version' => 'v1.1.3',
'version' => '1.1.3.0',
'reference' => 'f09e3ed3b0a953ccbfff1140f12be4a884f0aa83',
'pretty_version' => 'v1.1.4',
'version' => '1.1.4.0',
'reference' => '76e9495fd6818b43a20167cb11d8a67f7744ee0f',
'type' => 'library',
'install_path' => __DIR__ . '/../amphp/process',
'aliases' => array(),
@ -85,43 +85,52 @@
'artica/phpchartjs' => array(
'pretty_version' => 'v1.0.2',
'version' => '1.0.2.0',
'reference' => '4957e7cd699e50cee8e0ba7304e1423aafb2cad2',
'reference' => '681980c084ad505f9dc811d3d1f02ffc9442ccee',
'type' => 'package',
'install_path' => __DIR__ . '/../artica/phpchartjs',
'aliases' => array(),
'dev_requirement' => false,
),
'chrome-php/chrome' => array(
'pretty_version' => 'v1.7.1',
'version' => '1.7.1.0',
'reference' => '5783c749b2ee385d1c481b0906f1b8acef0296e4',
'pretty_version' => 'v1.8.1',
'version' => '1.8.1.0',
'reference' => '9146c37c14d99cff3e4d9cd2f5ba843d149b3cbc',
'type' => 'library',
'install_path' => __DIR__ . '/../chrome-php/chrome',
'aliases' => array(),
'dev_requirement' => false,
),
'chrome-php/wrench' => array(
'pretty_version' => 'v1.3.0',
'version' => '1.3.0.0',
'reference' => '68b8282d5d0d54a519c3212ee3e4c35bef40b7d9',
'pretty_version' => 'v1.5.0',
'version' => '1.5.0.0',
'reference' => '725246324339e5fd5d798361b561e81004324f96',
'type' => 'library',
'install_path' => __DIR__ . '/../chrome-php/wrench',
'aliases' => array(),
'dev_requirement' => false,
),
'doctrine/deprecations' => array(
'pretty_version' => 'v1.0.0',
'version' => '1.0.0.0',
'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/deprecations',
'aliases' => array(),
'dev_requirement' => false,
),
'doctrine/lexer' => array(
'pretty_version' => '1.2.2',
'version' => '1.2.2.0',
'reference' => '9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c',
'pretty_version' => '2.1.0',
'version' => '2.1.0.0',
'reference' => '39ab8fcf5a51ce4b85ca97c7a7d033eb12831124',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/lexer',
'aliases' => array(),
'dev_requirement' => false,
),
'egulias/email-validator' => array(
'pretty_version' => '3.1.2',
'version' => '3.1.2.0',
'reference' => 'ee0db30118f661fb166bcffbf5d82032df484697',
'pretty_version' => '3.2.5',
'version' => '3.2.5.0',
'reference' => 'b531a2311709443320c786feb4519cfaf94af796',
'type' => 'library',
'install_path' => __DIR__ . '/../egulias/email-validator',
'aliases' => array(),
@ -154,46 +163,46 @@
'aliases' => array(),
'dev_requirement' => false,
),
'laravel/serializable-closure' => array(
'pretty_version' => 'v1.3.0',
'version' => '1.3.0.0',
'reference' => 'f23fe9d4e95255dacee1bf3525e0810d1a1b0f37',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/serializable-closure',
'aliases' => array(),
'dev_requirement' => false,
),
'monolog/monolog' => array(
'pretty_version' => '2.8.0',
'version' => '2.8.0.0',
'reference' => '720488632c590286b88b80e62aa3d3d551ad4a50',
'pretty_version' => '2.9.1',
'version' => '2.9.1.0',
'reference' => 'f259e2b15fb95494c83f52d3caad003bbf5ffaa1',
'type' => 'library',
'install_path' => __DIR__ . '/../monolog/monolog',
'aliases' => array(),
'dev_requirement' => false,
),
'mpdf/mpdf' => array(
'pretty_version' => 'v8.0.15',
'version' => '8.0.15.0',
'reference' => 'd8a5294a6cc2e814c4157aecc8d7ac25014b18ed',
'pretty_version' => 'v8.1.5',
'version' => '8.1.5.0',
'reference' => 'c264ce27af0d794ecd04e201b7e37a06b8a9d720',
'type' => 'library',
'install_path' => __DIR__ . '/../mpdf/mpdf',
'aliases' => array(),
'dev_requirement' => false,
),
'myclabs/deep-copy' => array(
'pretty_version' => '1.10.2',
'version' => '1.10.2.0',
'reference' => '776f831124e9c62e1a2c601ecc52e776d8bb7220',
'pretty_version' => '1.11.1',
'version' => '1.11.1.0',
'reference' => '7284c22080590fb39f2ffa3e9057f10a4ddd0e0c',
'type' => 'library',
'install_path' => __DIR__ . '/../myclabs/deep-copy',
'aliases' => array(),
'dev_requirement' => false,
),
'opis/closure' => array(
'pretty_version' => '3.6.3',
'version' => '3.6.3.0',
'reference' => '3d81e4309d2a927abbe66df935f4bb60082805ad',
'type' => 'library',
'install_path' => __DIR__ . '/../opis/closure',
'aliases' => array(),
'dev_requirement' => false,
),
'pandorafms/console' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => '8d950c3672332ae0f9823f784fe4b5b23704e18f',
'reference' => 'f68cd81d35010b07e3e022ca1141a3fb639e6834',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -208,6 +217,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
'php-http/message-factory' => array(
'pretty_version' => 'v1.0.2',
'version' => '1.0.2.0',
'reference' => 'a478cb11f66a6ac48d8954216cfed9aa06a501a1',
'type' => 'library',
'install_path' => __DIR__ . '/../php-http/message-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'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/log' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
@ -224,9 +251,9 @@
),
),
'setasign/fpdi' => array(
'pretty_version' => 'v2.3.6',
'version' => '2.3.6.0',
'reference' => '6231e315f73e4f62d72b73f3d6d78ff0eed93c31',
'pretty_version' => 'v2.3.7',
'version' => '2.3.7.0',
'reference' => 'bccc892d5fa1f48c43f8ba7db5ed4ba6f30c8c05',
'type' => 'library',
'install_path' => __DIR__ . '/../setasign/fpdi',
'aliases' => array(),
@ -242,9 +269,9 @@
'dev_requirement' => false,
),
'symfony/filesystem' => array(
'pretty_version' => 'v5.4.13',
'version' => '5.4.13.0',
'reference' => 'ac09569844a9109a5966b9438fc29113ce77cf51',
'pretty_version' => 'v6.0.19',
'version' => '6.0.19.0',
'reference' => '3d49eec03fda1f0fc19b7349fbbe55ebc1004214',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
@ -260,27 +287,27 @@
'dev_requirement' => false,
),
'symfony/polyfill-iconv' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => 'f1aed619e28cb077fc83fac8c4c0383578356e40',
'pretty_version' => 'v1.27.0',
'version' => '1.27.0.0',
'reference' => '927013f3aac555983a5059aada98e1907d842695',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-iconv',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-intl-idn' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '749045c69efb97c70d25d7463abba812e91f3a44',
'pretty_version' => 'v1.27.0',
'version' => '1.27.0.0',
'reference' => '639084e360537a19f9ee352433b84ce831f3d2da',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '8590a5f561694770bdcd3f9b5c69dde6945028e8',
'pretty_version' => 'v1.27.0',
'version' => '1.27.0.0',
'reference' => '19bd1e4fcd5b91116f14d8533c57831ed00571b6',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(),
@ -296,9 +323,9 @@
'dev_requirement' => false,
),
'symfony/polyfill-php72' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '9a142215a36a3888e30d0a9eeea9766764e96976',
'pretty_version' => 'v1.27.0',
'version' => '1.27.0.0',
'reference' => '869329b1e9894268a8a61dabb69153029b7a8c97',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php72',
'aliases' => array(),
@ -314,9 +341,9 @@
'dev_requirement' => false,
),
'symfony/process' => array(
'pretty_version' => 'v5.4.11',
'version' => '5.4.11.0',
'reference' => '6e75fe6874cbc7e4773d049616ab450eff537bf1',
'pretty_version' => 'v6.0.19',
'version' => '6.0.19.0',
'reference' => '2114fd60f26a296cc403a7939ab91478475a33d4',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/process',
'aliases' => array(),

View File

@ -4,8 +4,8 @@
$issues = array();
if (!(PHP_VERSION_ID >= 80000)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.';
if (!(PHP_VERSION_ID >= 80002)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.2". You are running ' . PHP_VERSION . '.';
}
if ($issues) {

View File

@ -1,20 +1,19 @@
The MIT License (MIT)
Copyright (c) 2018-2019 Zindex Software
Copyright (c) 2020-2021 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:
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.
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

@ -0,0 +1,154 @@
# Doctrine Deprecations
A small (side-effect free by default) layer on top of
`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging.
- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under
- options to avoid having to rely on error handlers global state by using PSR-3 logging
- deduplicate deprecation messages to avoid excessive triggering and reduce overhead
We recommend to collect Deprecations using a PSR logger instead of relying on
the global error handler.
## Usage from consumer perspective:
Enable Doctrine deprecations to be sent to a PSR3 logger:
```php
\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
```
Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)`
messages.
```php
\Doctrine\Deprecations\Deprecation::enableWithTriggerError();
```
If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call:
```php
\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
```
Tracking is enabled with all three modes and provides access to all triggered
deprecations and their individual count:
```php
$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations();
foreach ($deprecations as $identifier => $count) {
echo $identifier . " was triggered " . $count . " times\n";
}
```
### Suppressing Specific Deprecations
Disable triggering about specific deprecations:
```php
\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier");
```
Disable all deprecations from a package
```php
\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm");
```
### Other Operations
When used within PHPUnit or other tools that could collect multiple instances of the same deprecations
the deduplication can be disabled:
```php
\Doctrine\Deprecations\Deprecation::withoutDeduplication();
```
Disable deprecation tracking again:
```php
\Doctrine\Deprecations\Deprecation::disable();
```
## Usage from a library/producer perspective:
When you want to unconditionally trigger a deprecation even when called
from the library itself then the `trigger` method is the way to go:
```php
\Doctrine\Deprecations\Deprecation::trigger(
"doctrine/orm",
"https://link/to/deprecations-description",
"message"
);
```
If variable arguments are provided at the end, they are used with `sprintf` on
the message.
```php
\Doctrine\Deprecations\Deprecation::trigger(
"doctrine/orm",
"https://github.com/doctrine/orm/issue/1234",
"message %s %d",
"foo",
1234
);
```
When you want to trigger a deprecation only when it is called by a function
outside of the current package, but not trigger when the package itself is the cause,
then use:
```php
\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside(
"doctrine/orm",
"https://link/to/deprecations-description",
"message"
);
```
Based on the issue link each deprecation message is only triggered once per
request.
A limited stacktrace is included in the deprecation message to find the
offending location.
Note: A producer/library should never call `Deprecation::enableWith` methods
and leave the decision how to handle deprecations to application and
frameworks.
## Usage in PHPUnit tests
There is a `VerifyDeprecations` trait that you can use to make assertions on
the occurrence of deprecations within a test.
```php
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
class MyTest extends TestCase
{
use VerifyDeprecations;
public function testSomethingDeprecation()
{
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
triggerTheCodeWithDeprecation();
}
public function testSomethingDeprecationFixed()
{
$this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
triggerTheCodeWithoutDeprecation();
}
}
```
## What is a deprecation identifier?
An identifier for deprecations is just a link to any resource, most often a
Github Issue or Pull Request explaining the deprecation and potentially its
alternative.

View File

@ -0,0 +1,32 @@
{
"name": "doctrine/deprecations",
"type": "library",
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"license": "MIT",
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3",
"doctrine/coding-standard": "^9"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"autoload": {
"psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"}
},
"autoload-dev": {
"psr-4": {
"DeprecationTests\\": "test_fixtures/src",
"Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@ -0,0 +1,266 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations;
use Psr\Log\LoggerInterface;
use function array_key_exists;
use function array_reduce;
use function debug_backtrace;
use function sprintf;
use function strpos;
use function strrpos;
use function substr;
use function trigger_error;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
use const DIRECTORY_SEPARATOR;
use const E_USER_DEPRECATED;
/**
* Manages Deprecation logging in different ways.
*
* By default triggered exceptions are not logged.
*
* To enable different deprecation logging mechanisms you can call the
* following methods:
*
* - Minimal collection of deprecations via getTriggeredDeprecations()
* \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
*
* - Uses @trigger_error with E_USER_DEPRECATED
* \Doctrine\Deprecations\Deprecation::enableWithTriggerError();
*
* - Sends deprecation messages via a PSR-3 logger
* \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
*
* Packages that trigger deprecations should use the `trigger()` or
* `triggerIfCalledFromOutside()` methods.
*/
class Deprecation
{
private const TYPE_NONE = 0;
private const TYPE_TRACK_DEPRECATIONS = 1;
private const TYPE_TRIGGER_ERROR = 2;
private const TYPE_PSR_LOGGER = 4;
/** @var int */
private static $type = self::TYPE_NONE;
/** @var LoggerInterface|null */
private static $logger;
/** @var array<string,bool> */
private static $ignoredPackages = [];
/** @var array<string,int> */
private static $ignoredLinks = [];
/** @var bool */
private static $deduplication = true;
/**
* Trigger a deprecation for the given package and identfier.
*
* The link should point to a Github issue or Wiki entry detailing the
* deprecation. It is additionally used to de-duplicate the trigger of the
* same deprecation during a request.
*
* @param mixed $args
*/
public static function trigger(string $package, string $link, string $message, ...$args): void
{
if (self::$type === self::TYPE_NONE) {
return;
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
}
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
return;
}
if (isset(self::$ignoredPackages[$package])) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$message = sprintf($message, ...$args);
self::delegateTriggerToBackend($message, $backtrace, $link, $package);
}
/**
* Trigger a deprecation for the given package and identifier when called from outside.
*
* "Outside" means we assume that $package is currently installed as a
* dependency and the caller is not a file in that package. When $package
* is installed as a root package then deprecations triggered from the
* tests folder are also considered "outside".
*
* This deprecation method assumes that you are using Composer to install
* the dependency and are using the default /vendor/ folder and not a
* Composer plugin to change the install location. The assumption is also
* that $package is the exact composer packge name.
*
* Compared to {@link trigger()} this method causes some overhead when
* deprecation tracking is enabled even during deduplication, because it
* needs to call {@link debug_backtrace()}
*
* @param mixed $args
*/
public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
{
if (self::$type === self::TYPE_NONE) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
// first check that the caller is not from a tests folder, in which case we always let deprecations pass
if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
$path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR;
if (strpos($backtrace[0]['file'], $path) === false) {
return;
}
if (strpos($backtrace[1]['file'], $path) !== false) {
return;
}
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
}
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
return;
}
if (isset(self::$ignoredPackages[$package])) {
return;
}
$message = sprintf($message, ...$args);
self::delegateTriggerToBackend($message, $backtrace, $link, $package);
}
/**
* @param array<mixed> $backtrace
*/
private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
{
if ((self::$type & self::TYPE_PSR_LOGGER) > 0) {
$context = [
'file' => $backtrace[0]['file'],
'line' => $backtrace[0]['line'],
'package' => $package,
'link' => $link,
];
self::$logger->notice($message, $context);
}
if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) {
return;
}
$message .= sprintf(
' (%s:%d called by %s:%d, %s, package %s)',
self::basename($backtrace[0]['file']),
$backtrace[0]['line'],
self::basename($backtrace[1]['file']),
$backtrace[1]['line'],
$link,
$package
);
@trigger_error($message, E_USER_DEPRECATED);
}
/**
* A non-local-aware version of PHPs basename function.
*/
private static function basename(string $filename): string
{
$pos = strrpos($filename, DIRECTORY_SEPARATOR);
if ($pos === false) {
return $filename;
}
return substr($filename, $pos + 1);
}
public static function enableTrackingDeprecations(): void
{
self::$type |= self::TYPE_TRACK_DEPRECATIONS;
}
public static function enableWithTriggerError(): void
{
self::$type |= self::TYPE_TRIGGER_ERROR;
}
public static function enableWithPsrLogger(LoggerInterface $logger): void
{
self::$type |= self::TYPE_PSR_LOGGER;
self::$logger = $logger;
}
public static function withoutDeduplication(): void
{
self::$deduplication = false;
}
public static function disable(): void
{
self::$type = self::TYPE_NONE;
self::$logger = null;
self::$deduplication = true;
foreach (self::$ignoredLinks as $link => $count) {
self::$ignoredLinks[$link] = 0;
}
}
public static function ignorePackage(string $packageName): void
{
self::$ignoredPackages[$packageName] = true;
}
public static function ignoreDeprecations(string ...$links): void
{
foreach ($links as $link) {
self::$ignoredLinks[$link] = 0;
}
}
public static function getUniqueTriggeredDeprecationsCount(): int
{
return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) {
return $carry + $count;
}, 0);
}
/**
* Returns each triggered deprecation link identifier and the amount of occurrences.
*
* @return array<string,int>
*/
public static function getTriggeredDeprecations(): array
{
return self::$ignoredLinks;
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations\PHPUnit;
use Doctrine\Deprecations\Deprecation;
use function sprintf;
trait VerifyDeprecations
{
/** @var array<string,int> */
private $doctrineDeprecationsExpectations = [];
/** @var array<string,int> */
private $doctrineNoDeprecationsExpectations = [];
public function expectDeprecationWithIdentifier(string $identifier): void
{
$this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
}
public function expectNoDeprecationWithIdentifier(string $identifier): void
{
$this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
}
/**
* @before
*/
public function enableDeprecationTracking(): void
{
Deprecation::enableTrackingDeprecations();
}
/**
* @after
*/
public function verifyDeprecationsAreTriggered(): void
{
foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) {
$actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
$this->assertTrue(
$actualCount > $expectation,
sprintf(
"Expected deprecation with identifier '%s' was not triggered by code executed in test.",
$identifier
)
);
}
foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) {
$actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
$this->assertTrue(
$actualCount === $expectation,
sprintf(
"Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.",
$identifier
)
);
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<ruleset>
<arg name="basepath" value="."/>
<arg name="extensions" value="php"/>
<arg name="parallel" value="80"/>
<arg name="cache" value=".phpcs-cache"/>
<arg name="colors"/>
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps"/>
<config name="php_version" value="70100"/>
<!-- Directories to be checked -->
<file>lib</file>
<file>tests</file>
<!-- Include full Doctrine Coding Standard -->
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint" />
</rule>
</ruleset>

View File

@ -0,0 +1,14 @@
Note about upgrading: Doctrine uses static and runtime mechanisms to raise
awareness about deprecated code.
- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or
Static Analysis tools (like Psalm, phpstan)
- Use of our low-overhead runtime deprecation API, details:
https://github.com/doctrine/deprecations/
# Upgrade to 2.0.0
`AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return
instances of `Doctrine\Common\Lexer\Token`, which is an array-like class
Using it as an array is deprecated in favor of using properties of that class.
Using `count()` on it is deprecated with no replacement.

View File

@ -1,7 +1,8 @@
{
"name": "doctrine/lexer",
"type": "library",
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
"license": "MIT",
"type": "library",
"keywords": [
"php",
"parser",
@ -9,27 +10,41 @@
"annotations",
"docblock"
],
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"require": {
"php": "^7.1 || ^8.0"
"php": "^7.1 || ^8.0",
"doctrine/deprecations": "^1.0"
},
"require-dev": {
"doctrine/coding-standard": "^9.0",
"phpstan/phpstan": "1.3",
"doctrine/coding-standard": "^9 || ^10",
"phpstan/phpstan": "^1.3",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^4.11"
"psalm/plugin-phpunit": "^0.18.3",
"vimeo/psalm": "^4.11 || ^5.0"
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" }
"psr-4": {
"Doctrine\\Common\\Lexer\\": "src"
}
},
"autoload-dev": {
"psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" }
"psr-4": {
"Doctrine\\Tests\\Common\\Lexer\\": "tests"
}
},
"config": {
"allow-plugins": {

View File

@ -1,15 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="5"
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="lib/Doctrine/Common/Lexer" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@ -5,9 +5,10 @@ declare(strict_types=1);
namespace Doctrine\Common\Lexer;
use ReflectionClass;
use UnitEnum;
use function get_class;
use function implode;
use function in_array;
use function preg_split;
use function sprintf;
use function substr;
@ -18,6 +19,9 @@ use const PREG_SPLIT_OFFSET_CAPTURE;
/**
* Base class for writing simple lexers, i.e. for creating small DSLs.
*
* @template T of UnitEnum|string|int
* @template V of string|int
*/
abstract class AbstractLexer
{
@ -31,14 +35,7 @@ abstract class AbstractLexer
/**
* Array of scanned tokens.
*
* Each token is an associative array containing three items:
* - 'value' : the string value of the token in the input string
* - 'type' : the type of the token (identifier, numeric, string, input
* parameter, none)
* - 'position' : the position of the token in the input string
*
* @var mixed[][]
* @psalm-var list<array{value: string, type: string|int|null, position: int}>
* @var list<Token<T, V>>
*/
private $tokens = [];
@ -60,7 +57,7 @@ abstract class AbstractLexer
* The next token in the input.
*
* @var mixed[]|null
* @psalm-var array{value: string, type: string|int|null, position: int}|null
* @psalm-var Token<T, V>|null
*/
public $lookahead;
@ -68,7 +65,7 @@ abstract class AbstractLexer
* The last matched/seen token.
*
* @var mixed[]|null
* @psalm-var array{value: string, type: string|int|null, position: int}|null
* @psalm-var Token<T, V>|null
*/
public $token;
@ -148,31 +145,37 @@ abstract class AbstractLexer
/**
* Checks whether a given token matches the current lookahead.
*
* @param int|string $token
* @param T $type
*
* @return bool
*
* @psalm-assert-if-true !=null $this->lookahead
*/
public function isNextToken($token)
public function isNextToken($type)
{
return $this->lookahead !== null && $this->lookahead['type'] === $token;
return $this->lookahead !== null && $this->lookahead->isA($type);
}
/**
* Checks whether any of the given tokens matches the current lookahead.
*
* @param string[] $tokens
* @param list<T> $types
*
* @return bool
*
* @psalm-assert-if-true !=null $this->lookahead
*/
public function isNextTokenAny(array $tokens)
public function isNextTokenAny(array $types)
{
return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true);
return $this->lookahead !== null && $this->lookahead->isA(...$types);
}
/**
* Moves to the next token in the input string.
*
* @return bool
*
* @psalm-assert-if-true !null $this->lookahead
*/
public function moveNext()
{
@ -187,13 +190,13 @@ abstract class AbstractLexer
/**
* Tells the lexer to skip input tokens until it sees a token with the given value.
*
* @param string $type The token type to skip until.
* @param T $type The token type to skip until.
*
* @return void
*/
public function skipUntil($type)
{
while ($this->lookahead !== null && $this->lookahead['type'] !== $type) {
while ($this->lookahead !== null && ! $this->lookahead->isA($type)) {
$this->moveNext();
}
}
@ -201,7 +204,7 @@ abstract class AbstractLexer
/**
* Checks if given value is identical to the given token.
*
* @param mixed $value
* @param string $value
* @param int|string $token
*
* @return bool
@ -215,7 +218,7 @@ abstract class AbstractLexer
* Moves the lookahead token forward.
*
* @return mixed[]|null The next token or NULL if there are no more tokens ahead.
* @psalm-return array{value: string, type: string|int|null, position: int}|null
* @psalm-return Token<T, V>|null
*/
public function peek()
{
@ -230,7 +233,7 @@ abstract class AbstractLexer
* Peeks at the next token, returns it and immediately resets the peek.
*
* @return mixed[]|null The next token or NULL if there are no more tokens ahead.
* @psalm-return array{value: string, type: string|int|null, position: int}|null
* @psalm-return Token<T, V>|null
*/
public function glimpse()
{
@ -268,26 +271,32 @@ abstract class AbstractLexer
foreach ($matches as $match) {
// Must remain before 'value' assignment since it can change content
$type = $this->getType($match[0]);
$firstMatch = $match[0];
$type = $this->getType($firstMatch);
$this->tokens[] = [
'value' => $match[0],
'type' => $type,
'position' => $match[1],
];
$this->tokens[] = new Token(
$firstMatch,
$type,
$match[1]
);
}
}
/**
* Gets the literal for a given token.
*
* @param int|string $token
* @param T $token
*
* @return int|string
*/
public function getLiteral($token)
{
if ($token instanceof UnitEnum) {
return get_class($token) . '::' . $token->name;
}
$className = static::class;
$reflClass = new ReflectionClass($className);
$constants = $reflClass->getConstants();
@ -329,7 +338,9 @@ abstract class AbstractLexer
*
* @param string $value
*
* @return int|string|null
* @return T|null
*
* @param-out V $value
*/
abstract protected function getType(&$value);
}

View File

@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Lexer;
use ArrayAccess;
use Doctrine\Deprecations\Deprecation;
use ReturnTypeWillChange;
use UnitEnum;
use function in_array;
/**
* @template T of UnitEnum|string|int
* @template V of string|int
* @implements ArrayAccess<string,mixed>
*/
final class Token implements ArrayAccess
{
/**
* The string value of the token in the input string
*
* @readonly
* @var V
*/
public $value;
/**
* The type of the token (identifier, numeric, string, input parameter, none)
*
* @readonly
* @var T|null
*/
public $type;
/**
* The position of the token in the input string
*
* @readonly
* @var int
*/
public $position;
/**
* @param V $value
* @param T|null $type
*/
public function __construct($value, $type, int $position)
{
$this->value = $value;
$this->type = $type;
$this->position = $position;
}
/** @param T ...$types */
public function isA(...$types): bool
{
return in_array($this->type, $types, true);
}
/**
* @deprecated Use the value, type or position property instead
* {@inheritDoc}
*/
public function offsetExists($offset): bool
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead',
self::class
);
return in_array($offset, ['value', 'type', 'position'], true);
}
/**
* @deprecated Use the value, type or position property instead
* {@inheritDoc}
*
* @param O $offset
*
* @return mixed
* @psalm-return (
* O is 'value'
* ? V
* : (
* O is 'type'
* ? T|null
* : (
* O is 'position'
* ? int
* : mixed
* )
* )
* )
*
* @template O of array-key
*/
#[ReturnTypeWillChange]
public function offsetGet($offset)
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead',
self::class
);
return $this->$offset;
}
/**
* @deprecated no replacement planned
* {@inheritDoc}
*/
public function offsetSet($offset, $value): void
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Setting %s properties via ArrayAccess is deprecated',
self::class
);
$this->$offset = $value;
}
/**
* @deprecated no replacement planned
* {@inheritDoc}
*/
public function offsetUnset($offset): void
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Setting %s properties via ArrayAccess is deprecated',
self::class
);
$this->$offset = null;
}
}

View File

@ -5,7 +5,7 @@
* Access to local part and domain part from EmailParser
* Validations outside of the scope of the RFC will be considered "extra" validations, thus opening the door for adding new; will live in their own folder "extra" (as requested in #248, #195, #183).
## Breacking changes
## Breaking changes
* PHP version upgraded to match Symfony's (as of 12/2020).
* DNSCheckValidation now fails for missing MX records. While the RFC argues that the existence of only A records to be valid, starting in v3 they will be considered invalid.

View File

@ -1,6 +1,6 @@
# Contributing
When contributing to this repository make sure to follow the Pull request process below.
When contributing to this repository make sure to follow the Pull request process below.
Reduce to the minimum 3rd party dependencies.
Please note we have a [code of conduct](#Code of Conduct), please follow it in all your interactions with the project.
@ -14,7 +14,7 @@ When doing a PR to v2 remember that you also have to do the PR port to v3, or te
3. Describe the changes you are proposing
1. If adding an extra validation state the benefits of adding it and the problem is solving
2. Document in the readme, by adding it to the list
4. Provide appropiate tests for the code you are submitting: aim to keep the existing coverage percentage.
4. Provide appropriate tests for the code you are submitting: aim to keep the existing coverage percentage.
5. Add your Twitter handle (if you have) so we can thank you there.
## License
@ -139,11 +139,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org

View File

@ -1,4 +1,4 @@
Copyright (c) 2013-2021 Eduardo Gulias Davis
Copyright (c) 2013-2022 Eduardo Gulias Davis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -14,11 +14,10 @@
},
"require": {
"php": ">=7.2",
"doctrine/lexer": "^1.2",
"doctrine/lexer": "^1.2|^2",
"symfony/polyfill-intl-idn": "^1.15"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^8.5.8|^9.3.3",
"vimeo/psalm": "^4"
},

File diff suppressed because it is too large Load Diff

View File

@ -3,65 +3,69 @@
namespace Egulias\EmailValidator;
use Doctrine\Common\Lexer\AbstractLexer;
use Doctrine\Common\Lexer\Token;
/**
* @extends AbstractLexer<int, string>
*/
class EmailLexer extends AbstractLexer
{
//ASCII values
const S_EMPTY = null;
const C_NUL = 0;
const S_HTAB = 9;
const S_LF = 10;
const S_CR = 13;
const S_SP = 32;
const EXCLAMATION = 33;
const S_DQUOTE = 34;
const NUMBER_SIGN = 35;
const DOLLAR = 36;
const PERCENTAGE = 37;
const AMPERSAND = 38;
const S_SQUOTE = 39;
const S_OPENPARENTHESIS = 40;
const S_CLOSEPARENTHESIS = 41;
const ASTERISK = 42;
const S_PLUS = 43;
const S_COMMA = 44;
const S_HYPHEN = 45;
const S_DOT = 46;
const S_SLASH = 47;
const S_COLON = 58;
const S_SEMICOLON = 59;
const S_LOWERTHAN = 60;
const S_EQUAL = 61;
const S_GREATERTHAN = 62;
const QUESTIONMARK = 63;
const S_AT = 64;
const S_OPENBRACKET = 91;
const S_BACKSLASH = 92;
const S_CLOSEBRACKET = 93;
const CARET = 94;
const S_UNDERSCORE = 95;
const S_BACKTICK = 96;
const S_OPENCURLYBRACES = 123;
const S_PIPE = 124;
const S_CLOSECURLYBRACES = 125;
const S_TILDE = 126;
const C_DEL = 127;
const INVERT_QUESTIONMARK= 168;
const INVERT_EXCLAMATION = 173;
const GENERIC = 300;
const S_IPV6TAG = 301;
const INVALID = 302;
const CRLF = 1310;
const S_DOUBLECOLON = 5858;
const ASCII_INVALID_FROM = 127;
const ASCII_INVALID_TO = 199;
public const S_EMPTY = null;
public const C_NUL = 0;
public const S_HTAB = 9;
public const S_LF = 10;
public const S_CR = 13;
public const S_SP = 32;
public const EXCLAMATION = 33;
public const S_DQUOTE = 34;
public const NUMBER_SIGN = 35;
public const DOLLAR = 36;
public const PERCENTAGE = 37;
public const AMPERSAND = 38;
public const S_SQUOTE = 39;
public const S_OPENPARENTHESIS = 40;
public const S_CLOSEPARENTHESIS = 41;
public const ASTERISK = 42;
public const S_PLUS = 43;
public const S_COMMA = 44;
public const S_HYPHEN = 45;
public const S_DOT = 46;
public const S_SLASH = 47;
public const S_COLON = 58;
public const S_SEMICOLON = 59;
public const S_LOWERTHAN = 60;
public const S_EQUAL = 61;
public const S_GREATERTHAN = 62;
public const QUESTIONMARK = 63;
public const S_AT = 64;
public const S_OPENBRACKET = 91;
public const S_BACKSLASH = 92;
public const S_CLOSEBRACKET = 93;
public const CARET = 94;
public const S_UNDERSCORE = 95;
public const S_BACKTICK = 96;
public const S_OPENCURLYBRACES = 123;
public const S_PIPE = 124;
public const S_CLOSECURLYBRACES = 125;
public const S_TILDE = 126;
public const C_DEL = 127;
public const INVERT_QUESTIONMARK= 168;
public const INVERT_EXCLAMATION = 173;
public const GENERIC = 300;
public const S_IPV6TAG = 301;
public const INVALID = 302;
public const CRLF = 1310;
public const S_DOUBLECOLON = 5858;
public const ASCII_INVALID_FROM = 127;
public const ASCII_INVALID_TO = 199;
/**
* US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
*
* @var array
*/
protected $charValue = array(
protected $charValue = [
'{' => self::S_OPENCURLYBRACES,
'}' => self::S_CLOSECURLYBRACES,
'(' => self::S_OPENPARENTHESIS,
@ -105,11 +109,29 @@ class EmailLexer extends AbstractLexer
'?' => self::QUESTIONMARK,
'#' => self::NUMBER_SIGN,
'¡' => self::INVERT_EXCLAMATION,
);
];
/**
* @var bool
*/
public const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu";
public const VALID_UTF8_REGEX = '/\p{Cc}+/u';
public const CATCHABLE_PATTERNS = [
'[a-zA-Z]+[46]?', //ASCII and domain literal
'[^\x00-\x7F]', //UTF-8
'[0-9]+',
'\r\n',
'::',
'\s+?',
'.',
];
public const NON_CATCHABLE_PATTERNS = [
'[\xA0-\xff]+',
];
public const MODIFIERS = 'iu';
/** @var bool */
protected $hasInvalidTokens = false;
/**
@ -122,38 +144,34 @@ class EmailLexer extends AbstractLexer
/**
* The last matched/seen token.
*
* @var array
* @var array|Token
*
* @psalm-suppress NonInvariantDocblockPropertyType
* @psalm-var array{value:string, type:null|int, position:int}
* @psalm-suppress NonInvariantDocblockPropertyType
* @psalm-var array{value:string, type:null|int, position:int}|Token<int, string>
*/
public $token;
/**
* The next token in the input.
*
* @var array|null
* @var array|Token|null
*
* @psalm-suppress NonInvariantDocblockPropertyType
* @psalm-var array{position: int, type: int|null|string, value: int|string}|Token<int, string>|null
*/
public $lookahead;
/**
* @psalm-var array{value:'', type:null, position:0}
*/
/** @psalm-var array{value:'', type:null, position:0} */
private static $nullToken = [
'value' => '',
'type' => null,
'position' => 0,
];
/**
* @var string
*/
/** @var string */
private $accumulator = '';
/**
* @var bool
*/
/** @var bool */
private $hasToRecord = false;
public function __construct()
@ -162,24 +180,13 @@ class EmailLexer extends AbstractLexer
$this->lookahead = null;
}
/**
* @return void
*/
public function reset()
public function reset() : void
{
$this->hasInvalidTokens = false;
parent::reset();
$this->previous = $this->token = self::$nullToken;
}
/**
* @return bool
*/
public function hasInvalidTokens()
{
return $this->hasInvalidTokens;
}
/**
* @param int $type
* @throws \UnexpectedValueException
@ -187,7 +194,7 @@ class EmailLexer extends AbstractLexer
*
* @psalm-suppress InvalidScalarArgument
*/
public function find($type)
public function find($type) : bool
{
$search = clone $this;
$search->skipUntil($type);
@ -198,30 +205,26 @@ class EmailLexer extends AbstractLexer
return true;
}
/**
* getPrevious
*
* @return array
*/
public function getPrevious()
{
return $this->previous;
}
/**
* moveNext
*
* @return boolean
*/
public function moveNext()
public function moveNext() : bool
{
if ($this->hasToRecord && $this->previous === self::$nullToken) {
$this->accumulator .= $this->token['value'];
}
$this->previous = $this->token;
$this->previous = $this->token instanceof Token
? ['value' => $this->token->value, 'type' => $this->token->type, 'position' => $this->token->position]
: $this->token;
if($this->lookahead === null) {
$this->lookahead = self::$nullToken;
}
$hasNext = parent::moveNext();
$this->token = $this->token ?: self::$nullToken;
if ($this->hasToRecord) {
$this->accumulator .= $this->token['value'];
@ -230,36 +233,6 @@ class EmailLexer extends AbstractLexer
return $hasNext;
}
/**
* Lexical catchable patterns.
*
* @return string[]
*/
protected function getCatchablePatterns()
{
return array(
'[a-zA-Z]+[46]?', //ASCII and domain literal
'[^\x00-\x7F]', //UTF-8
'[0-9]+',
'\r\n',
'::',
'\s+?',
'.',
);
}
/**
* Lexical non-catchable patterns.
*
* @return string[]
*/
protected function getNonCatchablePatterns()
{
return [
'[\xA0-\xff]+',
];
}
/**
* Retrieve token type. Also processes the token value if necessary.
*
@ -272,7 +245,7 @@ class EmailLexer extends AbstractLexer
$encoded = $value;
if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
$encoded = utf8_encode($value);
$encoded = mb_convert_encoding($value, 'UTF-8', 'Windows-1252');
}
if ($this->isValid($encoded)) {
@ -292,51 +265,64 @@ class EmailLexer extends AbstractLexer
return self::GENERIC;
}
protected function isInvalidChar(string $value) : bool
{
if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) {
return false;
}
return true;
}
protected function isValid(string $value) : bool
{
if (isset($this->charValue[$value])) {
return true;
}
return false;
return isset($this->charValue[$value]);
}
/**
* @param string $value
* @return bool
*/
protected function isNullType($value)
protected function isNullType(string $value) : bool
{
if ($value === "\0") {
return true;
}
return $value === "\0";
}
return false;
protected function isInvalidChar(string $value) : bool
{
return !preg_match(self::INVALID_CHARS_REGEX, $value);
}
protected function isUTF8Invalid(string $value) : bool
{
if (preg_match('/\p{Cc}+/u', $value)) {
return true;
}
return preg_match(self::VALID_UTF8_REGEX, $value) !== false;
}
return false;
public function hasInvalidTokens() : bool
{
return $this->hasInvalidTokens;
}
/**
* @return string
* getPrevious
*
* @return array
*/
protected function getModifiers()
public function getPrevious() : array
{
return 'iu';
return $this->previous;
}
/**
* Lexical catchable patterns.
*
* @return string[]
*/
protected function getCatchablePatterns() : array
{
return self::CATCHABLE_PATTERNS;
}
/**
* Lexical non-catchable patterns.
*
* @return string[]
*/
protected function getNonCatchablePatterns() : array
{
return self::NON_CATCHABLE_PATTERNS;
}
protected function getModifiers() : string
{
return self::MODIFIERS;
}
public function getAccumulatedValues() : string

View File

@ -2,7 +2,6 @@
namespace Egulias\EmailValidator;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Parser\LocalPart;
use Egulias\EmailValidator\Parser\DomainPart;
@ -13,7 +12,7 @@ use Egulias\EmailValidator\Result\Reason\NoLocalPart;
class EmailParser extends Parser
{
const EMAIL_MAX_LENGTH = 254;
public const EMAIL_MAX_LENGTH = 254;
/**
* @var string

View File

@ -2,8 +2,6 @@
namespace Egulias\EmailValidator;
use Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Parser\IDLeftPart;
use Egulias\EmailValidator\Parser\IDRightPart;
@ -15,7 +13,7 @@ use Egulias\EmailValidator\Result\Reason\NoLocalPart;
class MessageIDParser extends Parser
{
const EMAILID_MAX_LENGTH = 254;
public const EMAILID_MAX_LENGTH = 254;
/**
* @var string
@ -90,4 +88,4 @@ class MessageIDParser extends Parser
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
}
}
}
}

View File

@ -59,7 +59,8 @@ class Comment extends PartParser
if($this->openedParenthesis >= 1) {
return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']);
} else if ($this->openedParenthesis < 0) {
}
if ($this->openedParenthesis < 0) {
return new InvalidEmail(new UnOpenedComment(), $this->lexer->token['value']);
}
@ -100,4 +101,4 @@ class Comment extends PartParser
return true;
}
}
}
}

View File

@ -15,4 +15,4 @@ interface CommentStrategy
public function endOfLoopValidations(EmailLexer $lexer) : Result;
public function getWarnings() : array;
}
}

View File

@ -34,4 +34,4 @@ class DomainComment implements CommentStrategy
{
return [];
}
}
}

View File

@ -34,4 +34,4 @@ class LocalComment implements CommentStrategy
{
return $this->warnings;
}
}
}

View File

@ -22,6 +22,15 @@ use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral;
class DomainLiteral extends PartParser
{
public const IPV4_REGEX = '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
public const OBSOLETE_WARNINGS = [
EmailLexer::INVALID,
EmailLexer::C_DEL,
EmailLexer::S_LF,
EmailLexer::S_BACKSLASH
];
public function parse() : Result
{
$this->addTagWarnings();
@ -41,7 +50,7 @@ class DomainLiteral extends PartParser
}
if ($this->lexer->isNextTokenAny(
array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
array(EmailLexer::S_HTAB, EmailLexer::S_SP, EmailLexer::CRLF)
)) {
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
$this->parseFWS();
@ -138,11 +147,8 @@ class DomainLiteral extends PartParser
public function convertIPv4ToIPv6(string $addressLiteralIPv4) : string
{
$matchesIP = array();
$IPv4Match = preg_match(
'/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
$addressLiteralIPv4,
$matchesIP);
$matchesIP = [];
$IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteralIPv4, $matchesIP);
// Extract IPv4 part from the end of the address-literal (if there is one)
if ($IPv4Match > 0) {
@ -164,11 +170,8 @@ class DomainLiteral extends PartParser
*/
protected function checkIPV4Tag($addressLiteral) : bool
{
$matchesIP = array();
$IPv4Match = preg_match(
'/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
$addressLiteral,
$matchesIP);
$matchesIP = [];
$IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteral, $matchesIP);
// Extract IPv4 part from the end of the address-literal (if there is one)
@ -186,11 +189,7 @@ class DomainLiteral extends PartParser
private function addObsoleteWarnings() : void
{
if ($this->lexer->token['type'] === EmailLexer::INVALID ||
$this->lexer->token['type'] === EmailLexer::C_DEL ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::S_BACKSLASH
) {
if(in_array($this->lexer->token['type'], self::OBSOLETE_WARNINGS)) {
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
}
}
@ -209,4 +208,4 @@ class DomainLiteral extends PartParser
}
}
}
}

View File

@ -2,6 +2,7 @@
namespace Egulias\EmailValidator\Parser;
use Doctrine\Common\Lexer\Token;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Warning\TLD;
use Egulias\EmailValidator\Result\Result;
@ -24,8 +25,8 @@ use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
class DomainPart extends PartParser
{
const DOMAIN_MAX_LENGTH = 253;
const LABEL_MAX_LENGTH = 63;
public const DOMAIN_MAX_LENGTH = 253;
public const LABEL_MAX_LENGTH = 63;
/**
* @var string
@ -212,7 +213,10 @@ class DomainPart extends PartParser
return new ValidEmail();
}
private function checkNotAllowedChars(array $token) : Result
/**
* @psalm-param array|Token<int, string> $token
*/
private function checkNotAllowedChars($token) : Result
{
$notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
if (isset($notAllowed[$token['type']])) {
@ -292,7 +296,7 @@ class DomainPart extends PartParser
private function isLabelTooLong(string $label) : bool
{
if (preg_match('/[^\x00-\x7F]/', $label)) {
idn_to_ascii(utf8_decode($label), IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
}
return strlen($label) > self::LABEL_MAX_LENGTH;
@ -309,4 +313,4 @@ class DomainPart extends PartParser
{
return $this->domainPart;
}
}
}

View File

@ -2,7 +2,6 @@
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Parser\Parser;
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Warning\CFWSWithFWS;
@ -19,18 +18,19 @@ class DoubleQuote extends PartParser
$validQuotedString = $this->checkDQUOTE();
if($validQuotedString->isInvalid()) return $validQuotedString;
$special = array(
$special = [
EmailLexer::S_CR => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_LF => true
);
];
$invalid = array(
$invalid = [
EmailLexer::C_NUL => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_CR => true,
EmailLexer::S_LF => true
);
];
$setSpecialsWarning = true;
$this->lexer->moveNext();
@ -84,4 +84,4 @@ class DoubleQuote extends PartParser
return new ValidEmail();
}
}
}

View File

@ -15,6 +15,14 @@ use Egulias\EmailValidator\Result\ValidEmail;
class FoldingWhiteSpace extends PartParser
{
public const FWS_TYPES = [
EmailLexer::S_SP,
EmailLexer::S_HTAB,
EmailLexer::S_CR,
EmailLexer::S_LF,
EmailLexer::CRLF
];
public function parse() : Result
{
if (!$this->isFWS()) {
@ -73,10 +81,6 @@ class FoldingWhiteSpace extends PartParser
return false;
}
return $this->lexer->token['type'] === EmailLexer::S_SP ||
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
$this->lexer->token['type'] === EmailLexer::S_CR ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::CRLF;
return in_array($this->lexer->token['type'], self::FWS_TYPES);
}
}
}

View File

@ -3,7 +3,6 @@
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Parser\LocalPart;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\CommentsInIDRight;
@ -13,4 +12,4 @@ class IDLeftPart extends LocalPart
{
return new InvalidEmail(new CommentsInIDRight(), $this->lexer->token['value']);
}
}
}

View File

@ -12,18 +12,18 @@ class IDRightPart extends DomainPart
{
protected function validateTokens(bool $hasComments) : Result
{
$invalidDomainTokens = array(
$invalidDomainTokens = [
EmailLexer::S_DQUOTE => true,
EmailLexer::S_SQUOTE => true,
EmailLexer::S_BACKTICK => true,
EmailLexer::S_SEMICOLON => true,
EmailLexer::S_GREATERTHAN => true,
EmailLexer::S_LOWERTHAN => true,
);
];
if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);
}
return new ValidEmail();
}
}
}

View File

@ -15,6 +15,17 @@ use Egulias\EmailValidator\Parser\CommentStrategy\LocalComment;
class LocalPart extends PartParser
{
public const INVALID_TOKENS = [
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON => EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
EmailLexer::INVALID => EmailLexer::INVALID
];
/**
* @var string
*/
@ -88,17 +99,7 @@ class LocalPart extends PartParser
protected function validateTokens(bool $hasComments) : Result
{
$invalidTokens = array(
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON => EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
EmailLexer::INVALID => EmailLexer::INVALID
);
if (isset($invalidTokens[$this->lexer->token['type']])) {
if (isset(self::INVALID_TOKENS[$this->lexer->token['type']])) {
return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']);
}
return new ValidEmail();
@ -161,4 +162,4 @@ class LocalPart extends PartParser
return new ValidEmail();
}
}
}

View File

@ -60,4 +60,4 @@ abstract class PartParser
&&
$this->lexer->token['type'] !== EmailLexer::GENERIC;
}
}
}

View File

@ -6,7 +6,7 @@ use Egulias\EmailValidator\Result\Reason\Reason;
class InvalidEmail implements Result
{
private $token;
private $token;
/**
* @var Reason
*/
@ -43,4 +43,4 @@ class InvalidEmail implements Result
return $this->reason;
}
}
}

View File

@ -13,4 +13,4 @@ class AtextAfterCFWS implements Reason
{
return 'ATEXT found after CFWS';
}
}
}

View File

@ -4,8 +4,8 @@ namespace Egulias\EmailValidator\Result\Reason;
class CRLFAtTheEnd implements Reason
{
const CODE = 149;
const REASON = "CRLF at the end";
public const CODE = 149;
public const REASON = "CRLF at the end";
public function code() : int
{

View File

@ -13,4 +13,4 @@ class CharNotAllowed implements Reason
{
return "Character not allowed";
}
}
}

View File

@ -13,4 +13,4 @@ class CommaInDomain implements Reason
{
return "Comma ',' is not allowed in domain part";
}
}
}

View File

@ -13,4 +13,4 @@ class CommentsInIDRight implements Reason
{
return 'Comments are not allowed in IDRight for message-id';
}
}
}

View File

@ -10,4 +10,4 @@ abstract class DetailedReason implements Reason
{
$this->detailedDescription = $details;
}
}
}

View File

@ -13,4 +13,4 @@ class DomainAcceptsNoMail implements Reason
{
return 'Domain accepts no mail (Null MX, RFC7505)';
}
}
}

View File

@ -23,4 +23,4 @@ class ExceptionFound implements Reason
{
return $this->exception->getMessage();
}
}
}

View File

@ -13,4 +13,4 @@ class ExpectingDomainLiteralClose implements Reason
{
return "Closing bracket ']' for domain literal not found";
}
}
}

View File

@ -13,4 +13,4 @@ class LocalOrReservedDomain implements Reason
{
return 'Local, mDNS or reserved domain (RFC2606, RFC6762)';
}
}
}

View File

@ -13,4 +13,4 @@ class NoDNSRecord implements Reason
{
return 'No MX or A DSN record was found for this email';
}
}
}

View File

@ -13,4 +13,4 @@ interface Reason
* Short description of the result, human readable.
*/
public function description() : string;
}
}

View File

@ -11,6 +11,6 @@ class UnOpenedComment implements Reason
public function description(): string
{
return 'Missing openning comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2';
return 'Missing opening comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2';
}
}
}

View File

@ -23,4 +23,4 @@ class UnusualElements implements Reason
{
return 'Unusual element found, wourld render invalid in majority of cases. Element found: ' . $this->element;
}
}
}

View File

@ -24,4 +24,4 @@ interface Result
* Code for user land to act upon.
*/
public function code() : int;
}
}

View File

@ -1,7 +1,6 @@
<?php
namespace Egulias\EmailValidator\Result;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\SpoofEmail as ReasonSpoofEmail;
class SpoofEmail extends InvalidEmail
@ -11,4 +10,4 @@ class SpoofEmail extends InvalidEmail
$this->reason = new ReasonSpoofEmail();
parent::__construct($this->reason, '');
}
}
}

View File

@ -24,4 +24,4 @@ class ValidEmail implements Result
return 0;
}
}
}

View File

@ -17,6 +17,29 @@ class DNSCheckValidation implements EmailValidation
*/
protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA;
/**
* Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
* mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
*/
public const RESERVED_DNS_TOP_LEVEL_NAMES = [
// Reserved Top Level DNS Names
'test',
'example',
'invalid',
'localhost',
// mDNS
'local',
// Private DNS Namespaces
'intranet',
'internal',
'private',
'corp',
'home',
'lan',
];
/**
* @var array
*/
@ -32,12 +55,22 @@ class DNSCheckValidation implements EmailValidation
*/
private $mxRecords = [];
/**
* @var DNSGetRecordWrapper
*/
private $dnsGetRecord;
public function __construct()
public function __construct(?DNSGetRecordWrapper $dnsGetRecord = null)
{
if (!function_exists('idn_to_ascii')) {
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
}
if ($dnsGetRecord == null) {
$dnsGetRecord = new DNSGetRecordWrapper();
}
$this->dnsGetRecord = $dnsGetRecord;
}
public function isValid(string $email, EmailLexer $emailLexer) : bool
@ -53,29 +86,8 @@ class DNSCheckValidation implements EmailValidation
// Get the domain parts
$hostParts = explode('.', $host);
// Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
// mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
$reservedTopLevelDnsNames = [
// Reserved Top Level DNS Names
'test',
'example',
'invalid',
'localhost',
// mDNS
'local',
// Private DNS Namespaces
'intranet',
'internal',
'private',
'corp',
'home',
'lan',
];
$isLocalDomain = count($hostParts) <= 1;
$isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], $reservedTopLevelDnsNames, true);
$isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], self::RESERVED_DNS_TOP_LEVEL_NAMES, true);
// Exclude reserved top level DNS names
if ($isLocalDomain || $isReservedTopLevel) {
@ -120,27 +132,17 @@ class DNSCheckValidation implements EmailValidation
*/
private function validateDnsRecords($host) : bool
{
// A workaround to fix https://bugs.php.net/bug.php?id=73149
/** @psalm-suppress InvalidArgument */
set_error_handler(
static function (int $errorLevel, string $errorMessage): ?bool {
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
}
);
$dnsRecordsResult = $this->dnsGetRecord->getRecords($host, static::DNS_RECORD_TYPES_TO_CHECK);
try {
// Get all MX, A and AAAA DNS records for host
$dnsRecords = dns_get_record($host, static::DNS_RECORD_TYPES_TO_CHECK);
} catch (\RuntimeException $exception) {
if ($dnsRecordsResult->withError()) {
$this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
return false;
} finally {
restore_error_handler();
}
$dnsRecords = $dnsRecordsResult->getRecords();
// No MX, A or AAAA DNS records
if ($dnsRecords === [] || $dnsRecords === false) {
if ($dnsRecords === []) {
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
return false;
}
@ -167,6 +169,11 @@ class DNSCheckValidation implements EmailValidation
*/
private function validateMxRecord($dnsRecord) : bool
{
if (!isset($dnsRecord['type'])) {
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
return false;
}
if ($dnsRecord['type'] !== 'MX') {
return true;
}
@ -181,4 +188,4 @@ class DNSCheckValidation implements EmailValidation
return true;
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Egulias\EmailValidator\Validation;
class DNSGetRecordWrapper
{
/**
* @param string $host
* @param int $type
*/
public function getRecords(string $host, int $type) : DNSRecords
{
// A workaround to fix https://bugs.php.net/bug.php?id=73149
/** @psalm-suppress InvalidArgument */
set_error_handler(
static function (int $errorLevel, string $errorMessage): ?bool {
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
}
);
try {
// Get all MX, A and AAAA DNS records for host
return new DNSRecords(dns_get_record($host, $type));
} catch (\RuntimeException $exception) {
return new DNSRecords([], true);
} finally {
restore_error_handler();
}
}
}

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