<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Util\Annotation;
use const JSON_ERROR_NONE;
use const PREG_OFFSET_CAPTURE;
use function array_filter;
use function array_key_exists;
use function array_map;
use function array_merge;
use function array_pop;
use function array_slice;
use function array_values;
use function count;
use function explode;
use function file;
use function implode;
use function is_array;
use function is_int;
use function json_decode;
use function json_last_error;
use function json_last_error_msg;
use function preg_match;
use function preg_match_all;
use function preg_replace;
use function preg_split;
use function realpath;
use function rtrim;
use function sprintf;
use function str_replace;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
use function trim;
use PharIo\Version\VersionConstraintParser;
use PHPUnit\Framework\InvalidDataProviderException;
use PHPUnit\Framework\SkippedTestError;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Exception;
use PHPUnit\Util\InvalidDataSetException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use Reflector;
use Traversable;
/**
* This is an abstraction around a PHPUnit-specific docBlock,
* allowing us to ask meaningful questions about a specific
* reflection symbol.
*
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class DocBlock
{
/**
* @todo This constant should be private (it's public because of TestTest::testGetProvidedDataRegEx)
*/
public const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/';
private const REGEX_REQUIRES_VERSION = '/@requires\s+(?P<name>PHP(?:Unit)?)\s+(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m';
private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\s+(?P<name>PHP(?:Unit)?)\s+(?P<constraint>[\d\t \-.|~^]+)[ \t]*\r?$/m';
private const REGEX_REQUIRES_OS = '/@requires\s+(?P<name>OS(?:FAMILY)?)\s+(?P<value>.+?)[ \t]*\r?$/m';
private const REGEX_REQUIRES_SETTING = '/@requires\s+(?P<name>setting)\s+(?P<setting>([^ ]+?))\s*(?P<value>[\w\.-]+[\w\.]?)?[ \t]*\r?$/m';
private const REGEX_REQUIRES = '/@requires\s+(?P<name>function|extension)\s+(?P<value>([^\s<>=!]+))\s*(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+[\d\.]?)?[ \t]*\r?$/m';
private const REGEX_TEST_WITH = '/@testWith\s+/';
/** @var string */
private $docComment;
/** @var bool */
private $isMethod;
/** @var array<string, array<int, string>> pre-parsed annotations indexed by name and occurrence index */
private $symbolAnnotations;
/**
* @var null|array<string, mixed>
*
* @psalm-var null|(array{
* __OFFSET: array<string, int>&array{__FILE: string},
* setting?: array<string, string>,
* extension_versions?: array<string, array{version: string, operator: string}>
* }&array<
* string,
* string|array{version: string, operator: string}|array{constraint: string}|array<int|string, string>
* >)
*/
private $parsedRequirements;
/** @var int */
private $startLine;
/** @var int */
private $endLine;
/** @var string */
private $fileName;
/** @var string */
private $name;
/**
* @var string
*
* @psalm-var class-string
*/
private $className;
public static function ofClass(ReflectionClass $class): self
{
$className = $class->getName();
return new self(
(string) $class->getDocComment(),
false,
self::extractAnnotationsFromReflector($class),
$class->getStartLine(),
$class->getEndLine(),
$class->getFileName(),
$className,
$className,
);
}
/**
* @psalm-param class-string $classNameInHierarchy
*/
public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy): self
{
return new self(
(string) $method->getDocComment(),
true,
self::extractAnnotationsFromReflector($method),
$method->getStartLine(),
$method->getEndLine(),
$method->getFileName(),
$method->getName(),
$classNameInHierarchy,
);
}
/**
* Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized.
*
* @param array<string, array<int, string>> $symbolAnnotations
*
* @psalm-param class-string $className
*/
private function __construct(string $docComment, bool $isMethod, array $symbolAnnotations, int $startLine, int $endLine, string $fileName, string $name, string $className)
{
$this->docComment = $docComment;
$this->isMethod = $isMethod;
$this->symbolAnnotations = $symbolAnnotations;
$this->startLine = $startLine;
$this->endLine = $endLine;
$this->fileName = $fileName;
$this->name = $name;
$this->className = $className;
}
/**
* @psalm-return array{
* __OFFSET: array<string, int>&array{__FILE: string},
* setting?: array<string, string>,
* extension_versions?: array<string, array{version: string, operator: string}>
* }&array<
* string,
* string|array{version: string, operator: string}|array{constraint: string}|array<int|string, string>
* >
*
* @throws Warning if the requirements version constraint is not well-formed
*/
public function requirements(): array
{
if ($this->parsedRequirements !== null) {
return $this->parsedRequirements;
}
$offset = $this->startLine;
$requires = [];
$recordedSettings = [];
$extensionVersions = [];
$recordedOffsets = [
'__FILE' => realpath($this->fileName),
];
// Trim docblock markers, split it into lines and rewind offset to start of docblock
$lines = preg_replace(['#^/\*{2}#', '#\*/$#'], '', preg_split('/\r\n|\r|\n/', $this->docComment));
$offset -= count($lines);
foreach ($lines as $line) {
if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) {
$requires[$matches['name']] = $matches['value'];
$recordedOffsets[$matches['name']] = $offset;
}
if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) {
$requires[$matches['name']] = [
'version' => $matches['version'],
'operator' => $matches['operator'],
];
$recordedOffsets[$matches['name']] = $offset;
}
if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) {
if (!empty($requires[$matches['name']])) {
$offset++;
continue;
}
try {
$versionConstraintParser = new VersionConstraintParser;
$requires[$matches['name'] . '_constraint'] = [
'constraint' => $versionConstraintParser->parse(trim($matches['constraint'])),
];
$recordedOffsets[$matches['name'] . '_constraint'] = $offset;
} catch (\PharIo\Version\Exception $e) {
throw new Warning($e->getMessage(), $e->getCode(), $e);
}
}
if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) {
$recordedSettings[$matches['setting']] = $matches['value'];
$recordedOffsets['__SETTING_' . $matches['setting']] = $offset;
}
if (preg_match(self::REGEX_REQUIRES, $line, $matches)) {
$name = $matches['name'] . 's';
if (!isset($requires[$name])) {
$requires[$name] = [];
}
$requires[$name][] = $matches['value'];
$recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset;
if ($name === 'extensions' && !empty($matches['version'])) {
$extensionVersions[$matches['value']] = [
'version' => $matches['version'],
'operator' => $matches['operator'],
];
}
}
$offset++;
}
return $this->parsedRequirements = array_merge(
$requires,
['__OFFSET' => $recordedOffsets],
array_filter([
'setting' => $recordedSettings,
'extension_versions' => $extensionVersions,
]),
);
}
/**
* Returns the provided data for a method.
*
* @throws Exception
*/
public function getProvidedData(): ?array
{
/** @noinspection SuspiciousBinaryOperationInspection */
$data = $this->getDataFromDataProviderAnnotation($this->docComment) ?? $this->getDataFromTestWithAnnotation($this->docComment);
if ($data === null) {
return null;
}
if ($data === []) {
throw new SkippedTestError;
}
foreach ($data as $key => $value) {
if (!is_array($value)) {
throw new InvalidDataSetException(
sprintf(
'Data set %s is invalid.',
is_int($key) ? '#' . $key : '"' . $key . '"',
),
);
}
}
return $data;
}
/**
* @psalm-return array<string, array{line: int, value: string}>
*/
public function getInlineAnnotations(): array
{
$code = file($this->fileName);
$lineNumber = $this->startLine;
$startLine = $this->startLine - 1;
$endLine = $this->endLine - 1;
$codeLines = array_slice($code, $startLine, $endLine - $startLine + 1);
$annotations = [];
foreach ($codeLines as $line) {
if (preg_match('#/\*\*?\s*@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?\*/$#m', $line, $matches)) {
$annotations[strtolower($matches['name'])] = [
'line' => $lineNumber,
'value' => $matches['value'],
];
}
$lineNumber++;
}
return $annotations;
}
public function symbolAnnotations(): array
{
return $this->symbolAnnotations;
}
public function isHookToBeExecutedBeforeClass(): bool
{
return $this->isMethod &&
false !== strpos($this->docComment, '@beforeClass');
}
public function isHookToBeExecutedAfterClass(): bool
{
return $this->isMethod &&
false !== strpos($this->docComment, '@afterClass');
}
public function isToBeExecutedBeforeTest(): bool
{
return 1 === preg_match('/@before\b/', $this->docComment);
}
public function isToBeExecutedAfterTest(): bool
{
return 1 === preg_match('/@after\b/', $this->docComment);
}
public function isToBeExecutedAsPreCondition(): bool
{
return 1 === preg_match('/@preCondition\b/', $this->docComment);
}
public function isToBeExecutedAsPostCondition(): bool
{
return 1 === preg_match('/@postCondition\b/', $this->docComment);
}
private function getDataFromDataProviderAnnotation(string $docComment): ?array
{
$methodName = null;
$className = $this->className;
if ($this->isMethod) {
$methodName = $this->name;
}
if (!preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) {
return null;
}
$result = [];
foreach ($matches[1] as $match) {
$dataProviderMethodNameNamespace = explode('\\', $match);
$leaf = explode('::', array_pop($dataProviderMethodNameNamespace));
$dataProviderMethodName = array_pop($leaf);
if (empty($dataProviderMethodNameNamespace)) {
$dataProviderMethodNameNamespace = '';
} else {
$dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\';
}
if (empty($leaf)) {
$dataProviderClassName = $className;
} else {
/** @psalm-var class-string $dataProviderClassName */
$dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf);
}
try {
$dataProviderClass = new ReflectionClass($dataProviderClassName);
$dataProviderMethod = $dataProviderClass->getMethod(
$dataProviderMethodName,
);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
throw new Exception(
$e->getMessage(),
$e->getCode(),
$e,
);
// @codeCoverageIgnoreEnd
}
if ($dataProviderMethod->isStatic()) {
$object = null;
} else {
$object = $dataProviderClass->newInstance();
}
if ($dataProviderMethod->getNumberOfParameters() === 0) {
$data = $dataProviderMethod->invoke($object);
} else {
$data = $dataProviderMethod->invoke($object, $methodName);
}
if ($data instanceof Traversable) {
$origData = $data;
$data = [];
foreach ($origData as $key => $value) {
if (is_int($key)) {
$data[] = $value;
} elseif (array_key_exists($key, $data)) {
throw new InvalidDataProviderException(
sprintf(
'The key "%s" has already been defined in the data provider "%s".',
$key,
$match,
),
);
} else {
$data[$key] = $value;
}
}
}
if (is_array($data)) {
$result = array_merge($result, $data);
}
}
return $result;
}
/**
* @throws Exception
*/
private function getDataFromTestWithAnnotation(string $docComment): ?array
{
$docComment = $this->cleanUpMultiLineAnnotation($docComment);
if (!preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) {
return null;
}
$offset = strlen($matches[0][0]) + $matches[0][1];
$annotationContent = substr($docComment, $offset);
$data = [];
foreach (explode("\n", $annotationContent) as $candidateRow) {
$candidateRow = trim($candidateRow);
if ($candidateRow[0] !== '[') {
break;
}
$dataSet = json_decode($candidateRow, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception(
'The data set for the @testWith annotation cannot be parsed: ' . json_last_error_msg(),
);
}
$data[] = $dataSet;
}
if (!$data) {
throw new Exception('The data set for the @testWith annotation cannot be parsed.');
}
return $data;
}
private function cleanUpMultiLineAnnotation(string $docComment): string
{
// removing initial ' * ' for docComment
$docComment = str_replace("\r\n", "\n", $docComment);
$docComment = preg_replace('/\n\s*\*\s?/', "\n", $docComment);
$docComment = (string) substr($docComment, 0, -1);
return rtrim($docComment, "\n");
}
/** @return array<string, array<int, string>> */
private static function parseDocBlock(string $docBlock): array
{
// Strip away the docblock header and footer to ease parsing of one line annotations
$docBlock = (string) substr($docBlock, 3, -2);
$annotations = [];
if (preg_match_all('/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docBlock, $matches)) {
$numMatches = count($matches[0]);
for ($i = 0; $i < $numMatches; $i++) {
$annotations[$matches['name'][$i]][] = (string) $matches['value'][$i];
}
}
return $annotations;
}
/** @param ReflectionClass|ReflectionFunctionAbstract $reflector */
private static function extractAnnotationsFromReflector(Reflector $reflector): array
{
$annotations = [];
if ($reflector instanceof ReflectionClass) {
$annotations = array_merge(
$annotations,
...array_map(
static function (ReflectionClass $trait): array
{
return self::parseDocBlock((string) $trait->getDocComment());
},
array_values($reflector->getTraits()),
),
);
}
return array_merge(
$annotations,
self::parseDocBlock((string) $reflector->getDocComment()),
);
}
}
At NFC Pay, your privacy is of utmost importance to us. This Privacy Policy outlines how we collect, use, share, and protect your personal information when you use our services, including our website and mobile applications.
1. Information We Collect
2. How We Use Your Information
We use the information we collect for the following purposes:
3. Sharing Your Information
We may share your personal information in the following circumstances:
4. Security of Your Information
We take the security of your personal information seriously and implement a variety of security measures, including encryption, secure servers, and access controls, to protect your data from unauthorized access, disclosure, alteration, or destruction. However, no method of transmission over the internet or electronic storage is completely secure, and we cannot guarantee its absolute security.
5. Your Privacy Rights
Depending on your location, you may have certain rights regarding your personal information, such as:
6. Third-Party Links
Our services may contain links to third-party websites or services. We are not responsible for the privacy practices or the content of these third-party sites. We encourage you to review the privacy policies of those third parties.
7. Children’s Privacy
Our services are not intended for individuals under the age of 13. We do not knowingly collect personal information from children under 13. If we become aware that we have collected personal information from a child under 13, we will take steps to delete that information.
8. Changes to This Privacy Policy
We may update this Privacy Policy from time to time to reflect changes in our practices or for other operational, legal, or regulatory reasons. We will notify you of any significant changes by posting the new Privacy Policy on our website and updating the effective date.
9. Contact Us
If you have any questions or concerns about this Privacy Policy or our data practices, please contact us at:
Email: support@nfcpay.com