add missing integration test vendor files

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
This commit is contained in:
Arthur Schiwon 2022-03-02 16:49:21 +01:00
parent 3435d5093a
commit 98c817613f
No known key found for this signature in database
GPG key ID: 7424F1874854DF23
399 changed files with 43427 additions and 1 deletions

2
.gitignore vendored
View file

@ -7,5 +7,5 @@
3rdparty/vendor/onelogin/php-saml/endpoints/
build
vendor
/vendor/
.php_cs.cache

View file

@ -0,0 +1,4 @@
default:
gherkin:
filters:
tags: ~@php8

View file

@ -0,0 +1,43 @@
<?xml version="1.0"?>
<psalm
errorLevel="8"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<!-- Psalm does not understand classes/interfaces defined inside if (false) {} -->
<!-- https://github.com/vimeo/psalm/issues/5750 -->
<UnrecognizedStatement>
<errorLevel type="suppress">
<file name="src/Behat/Behat/HelperContainer/ContainerInterface.php" />
<file name="src/Behat/Behat/HelperContainer/Exception/ContainerException.php" />
<file name="src/Behat/Behat/HelperContainer/Exception/NotFoundException.php" />
</errorLevel>
</UnrecognizedStatement>
<!-- Suppress errors due to PSR exceptions not implementing Throwable -->
<!-- https://github.com/vimeo/psalm/issues/1856 -->
<InvalidThrow>
<errorLevel type="suppress">
<referencedClass name="Psr\Container\ContainerExceptionInterface" />
</errorLevel>
</InvalidThrow>
<!-- Suppress errors related to legacy PHPUnit classes -->
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="PHPUnit_Framework_Exception" />
<referencedClass name="PHPUnit_Framework_TestFailure" />
</errorLevel>
</UndefinedClass>
</issueHandlers>
</psalm>

View file

@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Context\Annotation;
/**
* Helper class for DocBlock parsing
*/
class DocBlockHelper
{
/**
* Extracts a description from the provided docblock,
* with support for multiline descriptions.
*/
public function extractDescription(string $docBlock): string
{
// Remove indentation
$description = preg_replace('/^[\s\t]*/m', '', $docBlock);
// Remove block comment syntax
$description = preg_replace('/^\/\*\*\s*|^\s*\*\s|^\s*\*\/$/m', '', $description);
// Remove annotations
$description = preg_replace('/^@.*$/m', '', $description);
// Ignore docs after a "--" separator
if (preg_match('/^--.*$/m', $description)) {
$descriptionParts = preg_split('/^--.*$/m', $description);
$description = array_shift($descriptionParts);
}
// Trim leading and trailing newlines
return trim($description, "\r\n");
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Context\Argument;
use Behat\Testwork\Environment\Environment;
/**
* Creates argument resolvers for provided environment.
*
* @see ContextEnvironmentHandler
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface ArgumentResolverFactory
{
/**
* Builds argument resolvers for provided suite.
*
* @param Environment $environment
*
* @return ArgumentResolver[]
*/
public function createArgumentResolvers(Environment $environment);
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Context\Argument;
use Behat\Testwork\Environment\Environment;
/**
* Composite factory. Delegates to other (registered) factories to do the job.
*
* @see ContextEnvironmentHandler
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class CompositeArgumentResolverFactory implements ArgumentResolverFactory
{
/**
* @var ArgumentResolverFactory[]
*/
private $factories = array();
/**
* Registers factory.
*
* @param ArgumentResolverFactory $factory
*/
public function registerFactory(ArgumentResolverFactory $factory)
{
$this->factories[] = $factory;
}
/**
* {@inheritdoc}
*/
public function createArgumentResolvers(Environment $environment)
{
return array_reduce(
$this->factories,
function (array $resolvers, ArgumentResolverFactory $factory) use ($environment) {
return array_merge($resolvers, $factory->createArgumentResolvers($environment));
},
array()
);
}
}

View file

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Context\Argument;
use Behat\Testwork\Environment\Environment;
/**
* Adapts SuiteScopedResolverFactory to new ArgumentResolverFactory interface.
*
* @see ContextEnvironmentHandler
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*
* @deprecated since 3.4. Use `ArgumentResolverFactory` instead
*/
final class SuiteScopedResolverFactoryAdapter implements ArgumentResolverFactory
{
/**
* @var SuiteScopedResolverFactory
*/
private $factory;
/**
* Initialises adapter.
*
* @param SuiteScopedResolverFactory $factory
*/
public function __construct(SuiteScopedResolverFactory $factory)
{
$this->factory = $factory;
}
/**
* {@inheritdoc}
*/
public function createArgumentResolvers(Environment $environment)
{
return $this->factory->generateArgumentResolvers($environment->getSuite());
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Context\Attribute;
use ReflectionMethod;
/**
* Reads Attributes of a provided context method into a Callee.
*
* @see AttributeContextReader
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface AttributeReader
{
/**
* Reads all callees associated with a provided method.
*
* @param string $contextClass
* @param ReflectionMethod $method
*
* @return array
*/
public function readCallees(string $contextClass, ReflectionMethod $method);
}

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Context\Reader;
use Behat\Behat\Context\Attribute\AttributeReader;
use Behat\Behat\Context\Environment\ContextEnvironment;
use ReflectionClass;
use ReflectionMethod;
/**
* Reads context callees by Attributes using registered Attribute readers.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class AttributeContextReader implements ContextReader
{
/**
* @var AttributeReader[]
*/
private $readers = array();
/**
* Registers attribute reader.
*
* @param AttributeReader $reader
*/
public function registerAttributeReader(AttributeReader $reader)
{
$this->readers[] = $reader;
}
/**
* {@inheritdoc}
*/
public function readContextCallees(ContextEnvironment $environment, $contextClass)
{
if (\PHP_MAJOR_VERSION < 8) {
return [];
}
$reflection = new ReflectionClass($contextClass);
$callees = array();
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
foreach ($this->readMethodCallees($reflection->getName(), $method) as $callee) {
$callees[] = $callee;
}
}
return $callees;
}
private function readMethodCallees(string $contextClass, ReflectionMethod $method)
{
$callees = [];
foreach ($this->readers as $reader) {
$callees = array_merge($callees, $reader->readCallees($contextClass, $method));
}
return $callees;
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Definition\Context\Attribute;
use Behat\Behat\Context\Annotation\DocBlockHelper;
use Behat\Behat\Context\Attribute\AttributeReader;
use Behat\Step\Definition;
use Behat\Step\Given;
use Behat\Step\Then;
use Behat\Step\When;
use ReflectionMethod;
/**
* Reads definition Attributes from the context class.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class DefinitionAttributeReader implements AttributeReader
{
/**
* @var string[]
*/
private static $classes = array(
Given::class => 'Behat\Behat\Definition\Call\Given',
When::class => 'Behat\Behat\Definition\Call\When',
Then::class => 'Behat\Behat\Definition\Call\Then',
);
/**
* @var DocBlockHelper
*/
private $docBlockHelper;
/**
* Initializes reader.
*
* @param DocBlockHelper $docBlockHelper
*/
public function __construct(DocBlockHelper $docBlockHelper)
{
$this->docBlockHelper = $docBlockHelper;
}
/**
* @{inheritdoc}
*/
public function readCallees(string $contextClass, ReflectionMethod $method)
{
if (\PHP_MAJOR_VERSION < 8) {
return [];
}
$attributes = $method->getAttributes(Definition::class, \ReflectionAttribute::IS_INSTANCEOF);
$callees = [];
foreach ($attributes as $attribute) {
$class = self::$classes[$attribute->getName()];
$callable = array($contextClass, $method->getName());
$description = null;
if ($docBlock = $method->getDocComment()) {
$description = $this->docBlockHelper->extractDescription($docBlock);
}
$callees[] = new $class($attribute->newInstance()->pattern, $callable, $description);
}
return $callees;
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace Behat\Behat\Definition\Translator;
class Translator extends \Symfony\Component\Translation\Translator implements TranslatorInterface
{
}

View file

@ -0,0 +1,10 @@
<?php
namespace Behat\Behat\Definition\Translator;
/**
* @method string getLocale()
*/
interface TranslatorInterface extends \Symfony\Contracts\Translation\TranslatorInterface
{
}

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer\Argument;
use Behat\Behat\Context\Argument\ArgumentResolver;
use Behat\Behat\HelperContainer\ArgumentAutowirer;
use Psr\Container\ContainerInterface;
use ReflectionClass;
/**
* Resolves arguments that weren't resolved before by autowiring.
*
* @see ContextFactory
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class AutowiringResolver implements ArgumentResolver
{
/**
* @var ArgumentAutowirer
*/
private $autowirer;
/**
* Initialises resolver.
*
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->autowirer = new ArgumentAutowirer($container);
}
/**
* {@inheritdoc}
*/
public function resolveArguments(ReflectionClass $classReflection, array $arguments)
{
if ($constructor = $classReflection->getConstructor()) {
return $this->autowirer->autowireArguments($constructor, $arguments);
}
return $arguments;
}
}

View file

@ -0,0 +1,114 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use ReflectionClass;
use ReflectionFunctionAbstract;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionException;
/**
* Automatically wires arguments of a given function from inside the container by using type-hints.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class ArgumentAutowirer
{
/**
* @var PsrContainerInterface
*/
private $container;
/**
* Initialises wirer.
*
* @param PsrContainerInterface $container
*/
public function __construct(PsrContainerInterface $container)
{
$this->container = $container;
}
/**
* Autowires given arguments using provided container.
*
* @param ReflectionFunctionAbstract $reflection
* @param array $arguments
*
* @return array
*
* @throws ContainerExceptionInterface if unset argument typehint can not be resolved from container
*/
public function autowireArguments(ReflectionFunctionAbstract $reflection, array $arguments)
{
$newArguments = $arguments;
foreach ($reflection->getParameters() as $index => $parameter) {
if ($this->isArgumentWireable($newArguments, $index, $parameter)) {
$newArguments[$index] = $this->container->get($this->getClassFromParameter($parameter));
}
}
return $newArguments;
}
/**
* Checks if given argument is wireable.
*
* Argument is wireable if it was not previously set and it has a class type-hint.
*
* @param array $arguments
* @param integer $index
* @param ReflectionParameter $parameter
*
* @return bool
*/
private function isArgumentWireable(array $arguments, $index, ReflectionParameter $parameter)
{
if (isset($arguments[$index]) || array_key_exists($index, $arguments)) {
return false;
}
if (isset($arguments[$parameter->getName()]) || array_key_exists($parameter->getName(), $arguments)) {
return false;
}
return (bool) $this->getClassFromParameter($parameter);
}
private function getClassFromParameter(ReflectionParameter $parameter) : ?string
{
if (!($type = $parameter->getType()) || !($type instanceof ReflectionNamedType)) {
return null;
}
try {
$typeString = $type->getName();
if ($typeString == 'self') {
return $parameter->getDeclaringClass()->getName();
}
elseif ($typeString == 'parent') {
return $parameter->getDeclaringClass()->getParentClass()->getName();
}
// will throw if not valid class
new ReflectionClass($typeString);
return $typeString;
} catch (ReflectionException $e) {
return null;
}
}
}

View file

@ -0,0 +1,204 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer\Call\Filter;
use Behat\Behat\Definition\Definition;
use Behat\Behat\HelperContainer\Environment\ServiceContainerEnvironment;
use Behat\Behat\Definition\Call\DefinitionCall;
use Behat\Behat\HelperContainer\ArgumentAutowirer;
use Behat\Behat\HelperContainer\Exception\UnsupportedCallException;
use Behat\Behat\Transformation\Call\TransformationCall;
use Behat\Behat\Transformation\Transformation;
use Behat\Testwork\Call\Call;
use Behat\Testwork\Call\Filter\CallFilter;
use Behat\Testwork\Environment\Call\EnvironmentCall;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
/**
* Dynamically resolves call arguments using the service container.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class ServicesResolver implements CallFilter
{
/**
* {@inheritdoc}
*/
public function supportsCall(Call $call)
{
return ($call instanceof DefinitionCall || $call instanceof TransformationCall)
&& $call->getEnvironment() instanceof ServiceContainerEnvironment;
}
/**
* Filters a call and returns a new one.
*
* @param Call $call
*
* @return Call
*
* @throws UnsupportedCallException
* @throws ContainerExceptionInterface
*/
public function filterCall(Call $call)
{
if ($container = $this->getContainer($call)) {
$autowirer = new ArgumentAutowirer($container);
$newArguments = $autowirer->autowireArguments($call->getCallee()->getReflection(), $call->getArguments());
return $this->repackageCallIfNewArguments($call, $newArguments);
}
return $call;
}
/**
* Gets container from the call.
*
* @param Call $call
*
* @return null|ContainerInterface
*
* @throws UnsupportedCallException if given call is not EnvironmentCall or environment is not ServiceContainerEnvironment
*/
private function getContainer(Call $call)
{
if (!$call instanceof EnvironmentCall) {
throw new UnsupportedCallException(sprintf(
'ServicesResolver can not filter `%s` call.',
get_class($call)
), $call);
}
$environment = $call->getEnvironment();
if (!$environment instanceof ServiceContainerEnvironment) {
throw new UnsupportedCallException(sprintf(
'ServicesResolver can not filter `%s` call.',
get_class($call)
), $call);
}
return $environment->getServiceContainer();
}
/**
* Repackages old calls with new arguments, but only if two differ.
*
* @param Call $call
* @param array $arguments
*
* @return Call
*
* @throws UnsupportedCallException if given call is not DefinitionCall or TransformationCall
*/
private function repackageCallIfNewArguments(Call $call, array $arguments)
{
if ($arguments === $call->getArguments()) {
return $call;
}
return $this->repackageCallWithNewArguments($call, $arguments);
}
/**
* Repackages old calls with new arguments.
*
* @param Call $call
* @param array $newArguments
*
* @return DefinitionCall|TransformationCall
*
* @throws UnsupportedCallException
*/
private function repackageCallWithNewArguments(Call $call, array $newArguments)
{
if ($call instanceof DefinitionCall) {
return $this->repackageDefinitionCall($call, $newArguments);
}
if ($call instanceof TransformationCall) {
return $this->repackageTransformationCall($call, $newArguments);
}
throw new UnsupportedCallException(
sprintf(
'ServicesResolver can not filter `%s` call.',
get_class($call)
), $call
);
}
/**
* Repackages definition call with new arguments.
*
* @param DefinitionCall $call
* @param array $newArguments
*
* @return DefinitionCall
*
* @throws UnsupportedCallException
*/
private function repackageDefinitionCall(DefinitionCall $call, array $newArguments)
{
$definition = $call->getCallee();
if (!$definition instanceof Definition) {
throw new UnsupportedCallException(
sprintf(
'Something is wrong in callee associated with `%s` call.',
get_class($call)
), $call
);
}
return new DefinitionCall(
$call->getEnvironment(),
$call->getFeature(),
$call->getStep(),
$definition,
$newArguments,
$call->getErrorReportingLevel()
);
}
/**
* Repackages transformation call with new arguments.
*
* @param TransformationCall $call
* @param array $newArguments
*
* @return TransformationCall
*
* @throws UnsupportedCallException
*/
private function repackageTransformationCall(TransformationCall $call, array $newArguments)
{
$transformation = $call->getCallee();
if (!$transformation instanceof Transformation) {
throw new UnsupportedCallException(
sprintf(
'Something is wrong in callee associated with `%s` call.',
get_class($call)
), $call
);
}
return new TransformationCall(
$call->getEnvironment(),
$call->getDefinition(),
$transformation,
$newArguments
);
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer;
class_alias(
interface_exists('Interop\\Container\\ContainerInterface')
? 'Interop\\Container\\ContainerInterface'
: 'Psr\\Container\\ContainerInterface',
'Behat\\Behat\\HelperContainer\\ContainerInterface'
);
if (false) {
/**
* @internal
*/
interface ContainerInterface
{
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer\Environment;
use Behat\Testwork\Environment\Environment;
use Psr\Container\ContainerInterface;
/**
* Represents test environment based on a service locator pattern.
*
* @see ContextEnvironmentHandler
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface ServiceContainerEnvironment extends Environment
{
/**
* Sets/unsets service container for the environment.
*
* @param ContainerInterface|null $container
*/
public function setServiceContainer(ContainerInterface $container = null);
/**
* Returns environment service container if set.
*
* @return null|ContainerInterface
*/
public function getServiceContainer();
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer\Exception;
class_alias(
interface_exists('Interop\\Container\\Exception\\ContainerException')
? 'Interop\\Container\\Exception\\ContainerException'
: 'Psr\\Container\\ContainerExceptionInterface',
'Behat\\Behat\\HelperContainer\\Exception\\ContainerException'
);
if (false) {
/**
* @internal
*/
interface ContainerException
{
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer\Exception;
class_alias(
interface_exists('Interop\\Container\\Exception\\NotFoundException')
? 'Interop\\Container\\Exception\\NotFoundException'
: 'Psr\\Container\\NotFoundExceptionInterface',
'Behat\\Behat\\HelperContainer\\Exception\\NotFoundException'
);
if (false) {
/**
* @internal
*/
interface NotFoundException
{
}
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\HelperContainer\Exception;
use Behat\Testwork\Call\Call;
use InvalidArgumentException;
/**
* Represents an exception caused by an attempt to filter an unsupported call.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class UnsupportedCallException extends InvalidArgumentException implements HelperContainerException
{
/**
* @var Call
*/
private $call;
/**
* Initializes exception.
*
* @param string $message
* @param Call $call
*/
public function __construct($message, Call $call)
{
parent::__construct($message);
$this->call = $call;
}
/**
* Returns a call that caused exception.
*
* @return Call
*/
public function getCall()
{
return $this->call;
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Behat\Hook\Context\Attribute;
use Behat\Behat\Context\Annotation\DocBlockHelper;
use Behat\Behat\Context\Attribute\AttributeReader;
use Behat\Hook\AfterFeature;
use Behat\Hook\AfterScenario;
use Behat\Hook\AfterStep;
use Behat\Hook\BeforeFeature;
use Behat\Hook\BeforeScenario;
use Behat\Hook\BeforeStep;
use Behat\Hook\Hook;
use ReflectionMethod;
final class HookAttributeReader implements AttributeReader
{
/**
* @var string[]
*/
private const KNOWN_ATTRIBUTES = array(
AfterFeature::class => 'Behat\Behat\Hook\Call\AfterFeature',
AfterScenario::class => 'Behat\Behat\Hook\Call\AfterScenario',
AfterStep::class => 'Behat\Behat\Hook\Call\AfterStep',
BeforeFeature::class => 'Behat\Behat\Hook\Call\BeforeFeature',
BeforeScenario::class => 'Behat\Behat\Hook\Call\BeforeScenario',
BeforeStep::class => 'Behat\Behat\Hook\Call\BeforeStep',
);
/**
* @var DocBlockHelper
*/
private $docBlockHelper;
/**
* Initializes reader.
*
* @param DocBlockHelper $docBlockHelper
*/
public function __construct(DocBlockHelper $docBlockHelper)
{
$this->docBlockHelper = $docBlockHelper;
}
/**
* @{inheritdoc}
*/
public function readCallees(string $contextClass, ReflectionMethod $method)
{
if (\PHP_MAJOR_VERSION < 8) {
return [];
}
$attributes = $method->getAttributes(Hook::class, \ReflectionAttribute::IS_INSTANCEOF);
$callees = [];
foreach ($attributes as $attribute) {
$class = self::KNOWN_ATTRIBUTES[$attribute->getName()];
$callable = array($contextClass, $method->getName());
$description = null;
if ($docBlock = $method->getDocComment()) {
$description = $this->docBlockHelper->extractDescription($docBlock);
}
$callees[] = new $class($attribute->newInstance()->filterString, $callable, $description);
}
return $callees;
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace Behat\Behat\Output\Node\EventListener\JUnit;
use Behat\Behat\EventDispatcher\Event\AfterFeatureTested;
use Behat\Behat\EventDispatcher\Event\AfterScenarioTested;
use Behat\Behat\EventDispatcher\Event\BeforeFeatureTested;
use Behat\Behat\EventDispatcher\Event\BeforeScenarioTested;
use Behat\Gherkin\Node\FeatureNode;
use Behat\Gherkin\Node\KeywordNodeInterface;
use Behat\Gherkin\Node\ScenarioLikeInterface;
use Behat\Testwork\Counter\Timer;
use Behat\Testwork\Event\Event;
use Behat\Testwork\Output\Formatter;
use Behat\Testwork\Output\Node\EventListener\EventListener;
final class JUnitDurationListener implements EventListener
{
private $scenarioTimerStore = array();
private $featureTimerStore = array();
private $resultStore = array();
private $featureResultStore = array();
/** @inheritdoc */
public function listenEvent(Formatter $formatter, Event $event, $eventName)
{
$this->captureBeforeScenarioEvent($event);
$this->captureBeforeFeatureTested($event);
$this->captureAfterScenarioEvent($event);
$this->captureAfterFeatureEvent($event);
}
public function getDuration(ScenarioLikeInterface $scenario)
{
$key = $this->getHash($scenario);
return array_key_exists($key, $this->resultStore) ? $this->resultStore[$key] : '';
}
public function getFeatureDuration(FeatureNode $feature)
{
$key = $this->getHash($feature);
return array_key_exists($key, $this->featureResultStore) ? $this->featureResultStore[$key] : '';
}
private function captureBeforeFeatureTested(Event $event)
{
if (!$event instanceof BeforeFeatureTested) {
return;
}
$this->featureTimerStore[$this->getHash($event->getFeature())] = $this->startTimer();
}
private function captureBeforeScenarioEvent(Event $event)
{
if (!$event instanceof BeforeScenarioTested) {
return;
}
$this->scenarioTimerStore[$this->getHash($event->getScenario())] = $this->startTimer();
}
private function captureAfterScenarioEvent(Event $event)
{
if (!$event instanceof AfterScenarioTested) {
return;
}
$key = $this->getHash($event->getScenario());
$timer = $this->scenarioTimerStore[$key];
if ($timer instanceof Timer) {
$timer->stop();
$this->resultStore[$key] = round($timer->getTime());
}
}
private function captureAfterFeatureEvent(Event $event)
{
if (!$event instanceof AfterFeatureTested) {
return;
}
$key = $this->getHash($event->getFeature());
$timer = $this->featureTimerStore[$key];
if ($timer instanceof Timer) {
$timer->stop();
$this->featureResultStore[$key] = round($timer->getTime());
}
}
private function getHash(KeywordNodeInterface $node)
{
return spl_object_hash($node);
}
/** @return Timer */
private function startTimer()
{
$timer = new Timer();
$timer->start();
return $timer;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Hook;
/**
* Represents an Attribute for AfterFeature hook
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AfterFeature implements Hook
{
/**
* @var string
*/
public $filterString;
public function __construct($filterString = null)
{
$this->filterString = $filterString;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Hook;
/**
* Represents an Attribute for AfterScenario hook
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AfterScenario implements Hook
{
/**
* @var string
*/
public $filterString;
public function __construct($filterString = null)
{
$this->filterString = $filterString;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Hook;
/**
* Represents an Attribute for AfterStep hook
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AfterStep implements Hook
{
/**
* @var string
*/
public $filterString;
public function __construct($filterString = null)
{
$this->filterString = $filterString;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Hook;
/**
* Represents an Attribute for BeforeFeature hook
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class BeforeFeature implements Hook
{
/**
* @var string
*/
public $filterString;
public function __construct($filterString = null)
{
$this->filterString = $filterString;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Hook;
/**
* Represents an Attribute for BeforeScenario hook
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class BeforeScenario implements Hook
{
/**
* @var string
*/
public $filterString;
public function __construct($filterString = null)
{
$this->filterString = $filterString;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Hook;
/**
* Represents an Attribute for BeforeStep hook
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class BeforeStep implements Hook
{
/**
* @var string
*/
public $filterString;
public function __construct($filterString = null)
{
$this->filterString = $filterString;
}
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Hook;
/**
* Marker interface for all Attributes regarding
* Hooks
*
* @internal Only meant as marker, not as an extension point
*/
interface Hook
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Step;
/**
* Marker interface for all Attributes regarding
* Call definitions
*
* @internal Only meant as marker, not as an extension point
*/
interface Definition
{
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Step;
/**
* Represents an Attribute for Given steps
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class Given implements Definition
{
/**
* @var string
*/
public $pattern;
public function __construct($pattern = null)
{
$this->pattern = $pattern;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Step;
/**
* Represents an Attribute for Then steps
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class Then implements Definition
{
/**
* @var string
*/
public $pattern;
public function __construct($pattern = null)
{
$this->pattern = $pattern;
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Step;
/**
* Represents an Attribute for When steps
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class When implements Definition
{
/**
* @var string
*/
public $pattern;
public function __construct($pattern = null)
{
$this->pattern = $pattern;
}
}

View file

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Testwork\Argument;
use Behat\Testwork\Argument\Exception\UnknownParameterValueException;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use ReflectionParameter;
/**
* Validates function arguments.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
final class Validator
{
/**
* Validates that all arguments are in place, throws exception otherwise.
*
* @param ReflectionFunctionAbstract $function
* @param mixed[] $arguments
*
* @throws UnknownParameterValueException
*/
public function validateArguments(ReflectionFunctionAbstract $function, array $arguments)
{
foreach ($function->getParameters() as $num => $parameter) {
$this->validateArgument($parameter, $num, $arguments);
}
}
/**
* Validates given argument.
*
* @param ReflectionParameter $parameter
* @param integer $parameterIndex
* @param array $givenArguments
*/
private function validateArgument(ReflectionParameter $parameter, $parameterIndex, array $givenArguments)
{
if ($parameter->isDefaultValueAvailable()) {
return;
}
if (array_key_exists($parameterIndex, $givenArguments)) {
return;
}
if (array_key_exists($parameter->getName(), $givenArguments)) {
return;
}
throw new UnknownParameterValueException(sprintf(
'Can not find a matching value for an argument `$%s` of the method `%s`.',
$parameter->getName(),
$this->getFunctionPath($parameter->getDeclaringFunction())
));
}
/**
* Returns function path for a provided reflection.
*
* @param ReflectionFunctionAbstract $function
*
* @return string
*/
private function getFunctionPath(ReflectionFunctionAbstract $function)
{
if ($function instanceof ReflectionMethod) {
return sprintf(
'%s::%s()',
$function->getDeclaringClass()->getName(),
$function->getName()
);
}
return sprintf('%s()', $function->getName());
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Behat\Testwork\Event;
use Behat\Testwork\EventDispatcher\TestworkEventDispatcher;
class Event extends \Symfony\Contracts\EventDispatcher\Event
{
}

View file

@ -0,0 +1,68 @@
<?php
namespace Behat\Testwork\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* Extends Symfony2 (>=5.0) event dispatcher with catch-all listeners.
*
* This is magically aliased to TestworkEventDispatcher by the code in TestworkEventDispatcher.php
* if the new symfony interface is detected.
*
* @deprecated Do not reference this class directly, use TestworkEventDispatcher
*/
final class TestworkEventDispatcherSymfony5 extends EventDispatcher
{
public const BEFORE_ALL_EVENTS = '*~';
public const AFTER_ALL_EVENTS = '~*';
public const DISPATCHER_VERSION = 2;
/**
* {@inheritdoc}
*/
public function dispatch($event, string $eventName = null): object
{
trigger_error(
'Class "\Behat\Testwork\EventDispatcher\TestworkEventDispatcherSymfony5" is deprecated ' .
'and should not be relied upon anymore. Use "Behat\Testwork\EventDispatcher\TestworkEventDispatcher" ' .
'instead',
E_USER_DEPRECATED
);
if (null === $event) {
$event = new \Symfony\Contracts\EventDispatcher\Event();
}
if (method_exists($event, 'setName')) {
$event->setName($eventName);
}
$this->callListeners($this->getListeners($eventName), $eventName, $event);
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
trigger_error(
'Class "\Behat\Testwork\EventDispatcher\TestworkEventDispatcherSymfony5" is deprecated ' .
'and should not be relied upon anymore. Use "Behat\Testwork\EventDispatcher\TestworkEventDispatcher" ' .
'instead',
E_USER_DEPRECATED
);
if (null == $eventName || self::BEFORE_ALL_EVENTS === $eventName) {
return parent::getListeners($eventName);
}
return array_merge(
parent::getListeners(self::BEFORE_ALL_EVENTS),
parent::getListeners($eventName),
parent::getListeners(self::AFTER_ALL_EVENTS)
);
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Behat\Testwork\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* Extends Symfony2 ( < 5.0) event dispatcher with catch-all listeners.
*
* This is magically aliased to TestworkEventDispatcher by the code in TestworkEventDispatcher.php
* if the old symfony interface is detected.
*
* @deprecated Do not reference this class directly, use TestworkEventDispatcher
*/
final class TestworkEventDispatcherSymfonyLegacy extends EventDispatcher
{
public const BEFORE_ALL_EVENTS = '*~';
public const AFTER_ALL_EVENTS = '~*';
public const DISPATCHER_VERSION = 1;
/**
* {@inheritdoc}
*
*/
public function dispatch($eventName, \Symfony\Component\EventDispatcher\Event $event = null)
{
trigger_error(
'Class "\Behat\Testwork\EventDispatcher\TestworkEventDispatcherSymfonyLegacy" is deprecated ' .
'and should not be relied upon anymore. Use "Behat\Testwork\EventDispatcher\TestworkEventDispatcher" ' .
'instead',
E_USER_DEPRECATED
);
if (null === $event) {
/** @psalm-suppress UndefinedClass */
$event = new \Symfony\Component\EventDispatcher\Event();
}
if (method_exists($event, 'setName')) {
$event->setName($eventName);
}
/** @scrutinizer ignore-call */
$this->doDispatch($this->getListeners($eventName), $eventName, $event);
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
trigger_error(
'Class "\Behat\Testwork\EventDispatcher\TestworkEventDispatcherSymfonyLegacy" is deprecated ' .
'and should not be relied upon anymore. Use "Behat\Testwork\EventDispatcher\TestworkEventDispatcher" ' .
'instead',
E_USER_DEPRECATED
);
if (null == $eventName || self::BEFORE_ALL_EVENTS === $eventName) {
return parent::getListeners($eventName);
}
return array_merge(
parent::getListeners(self::BEFORE_ALL_EVENTS),
parent::getListeners($eventName),
parent::getListeners(self::AFTER_ALL_EVENTS)
);
}
}

View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Behat Testwork.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Behat\Testwork\Output\Exception;
use RuntimeException;
/**
* Represents an exception thrown when a required extension is missing.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class MissingExtensionException extends RuntimeException implements PrinterException
{
}

View file

@ -0,0 +1,47 @@
name: Build
on:
push:
branches: [master]
pull_request:
release:
types: [created]
jobs:
tests:
runs-on: ubuntu-latest
name: Build and test
strategy:
fail-fast: false
matrix:
php: [7.2, 7.3, 7.4, 8.0, 8.1]
composer-flags: [ "" ]
symfony-version: [ "" ]
include:
- php: 7.2
symfony-version: '3.*'
- php: 7.3
symfony-version: '4.*'
- php: 7.4
symfony-version: '5.*'
- php: 8.0
symfony-version: '5.*'
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php }}"
coverage: none
- name: Update Symfony version
if: matrix.symfony-version != ''
run: composer require --no-update "symfony/symfony:${{ matrix.symfony-version }}"
- name: Install dependencies
run: composer update ${{ matrix.composer-flags }}
- name: Run tests (phpunit)
run: ./vendor/bin/phpunit

View file

@ -0,0 +1,49 @@
name: Update Cucumber
on:
schedule:
- cron: '0 7 * * *'
jobs:
cucumber-update:
runs-on: ubuntu-latest
name: Upstream cucumber update
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
coverage: none
- uses: actions/checkout@v2
- name: Install dependencies
run: composer update
- name: Update cucumber tag
id: cucumber
run: bin/update_cucumber
- name: Re-install dependencies
run: composer update
if: steps.cucumber.outputs.cucumber_version
- name: Update translations
run: bin/update_i18n
if: steps.cucumber.outputs.cucumber_version
- name: Find changelog
id: changelog
run: bin/cucumber_changelog ${{ steps.cucumber.outputs.cucumber_version }}
if: steps.cucumber.outputs.cucumber_version
- name: Open a PR
uses: peter-evans/create-pull-request@v3
if: steps.cucumber.outputs.cucumber_version
with:
commit-message: Automatic Cucumber tag update to ${{ steps.cucumber.outputs.cucumber_version }}
branch: cucumber-update-${{ steps.cucumber.outputs.cucumber_version }}
delete-branch: true
title: Cucumber update ${{ steps.cucumber.outputs.cucumber_version }}
body: ${{ steps.changelog.outputs.changelog }}
base: 'master'

View file

@ -0,0 +1,193 @@
<?php
namespace Behat\Gherkin\Loader;
use Behat\Gherkin\Node\BackgroundNode;
use Behat\Gherkin\Node\ExampleTableNode;
use Behat\Gherkin\Node\FeatureNode;
use Behat\Gherkin\Node\OutlineNode;
use Behat\Gherkin\Node\ScenarioInterface;
use Behat\Gherkin\Node\ScenarioNode;
use Behat\Gherkin\Node\StepNode;
/**
* Loads a feature from cucumber's protobuf JSON format
*/
class CucumberNDJsonAstLoader implements LoaderInterface
{
public function supports($resource)
{
return is_string($resource);
}
public function load($resource)
{
return array_values(array_filter(array_map(
static function ($line) use ($resource) {
return self::getFeature(json_decode($line, true), $resource);
},
file($resource)
)));
}
/**
* @return FeatureNode|null
*/
private static function getFeature(array $json, $filePath)
{
if (!isset($json['gherkinDocument']['feature'])) {
return null;
}
$featureJson = $json['gherkinDocument']['feature'];
$feature = new FeatureNode(
isset($featureJson['name']) ? $featureJson['name'] : null,
$featureJson['description'] ? trim($featureJson['description']) : null,
self::getTags($featureJson),
self::getBackground($featureJson),
self::getScenarios($featureJson),
$featureJson['keyword'],
$featureJson['language'],
preg_replace('/(?<=\\.feature).*$/', '', $filePath),
$featureJson['location']['line']
);
return $feature;
}
/**
* @return string[]
*/
private static function getTags(array $json)
{
return array_map(
static function(array $tag) { return preg_replace('/^@/', '', $tag['name']); },
isset($json['tags']) ? $json['tags'] : []
);
}
/**
* @return ScenarioInterface[]
*/
private static function getScenarios(array $json)
{
return array_values(
array_map(
static function ($child) {
if ($child['scenario']['examples']) {
return new OutlineNode(
isset($child['scenario']['name']) ? $child['scenario']['name'] : null,
self::getTags($child['scenario']),
self::getSteps(isset($child['scenario']['steps']) ? $child['scenario']['steps'] : []),
self::getTables($child['scenario']['examples']),
$child['scenario']['keyword'],
$child['scenario']['location']['line']
);
}
else {
return new ScenarioNode(
$child['scenario']['name'],
self::getTags($child['scenario']),
self::getSteps(isset($child['scenario']['steps']) ? $child['scenario']['steps'] : []),
$child['scenario']['keyword'],
$child['scenario']['location']['line']
);
}
},
array_filter(
isset($json['children']) ? $json['children'] : [],
static function ($child) {
return isset($child['scenario']);
}
)
)
);
}
/**
* @return BackgroundNode|null
*/
private static function getBackground(array $json)
{
$backgrounds = array_values(
array_map(
static function ($child) {
return new BackgroundNode(
$child['background']['name'],
self::getSteps(isset($child['background']['steps']) ? $child['background']['steps'] : []),
$child['background']['keyword'],
$child['background']['location']['line']
);
},
array_filter(
isset($json['children']) ? $json['children'] : [],
static function ($child) {
return isset($child['background']);
}
)
)
);
return count($backgrounds) == 1 ? $backgrounds[0] : null;
}
/**
* @return StepNode[]
*/
private static function getSteps(array $json)
{
return array_map(
static function(array $json) {
return new StepNode(
trim($json['keyword']),
$json['text'],
[],
$json['location']['line'],
trim($json['keyword'])
);
},
$json
);
}
/**
* @return ExampleTableNode[]
*/
private static function getTables(array $json)
{
return array_map(
static function($tableJson) {
$table = [];
$table[$tableJson['tableHeader']['location']['line']] = array_map(
static function($cell) {
return $cell['value'];
},
$tableJson['tableHeader']['cells']
);
foreach ($tableJson['tableBody'] as $bodyRow) {
$table[$bodyRow['location']['line']] = array_map(
static function($cell) {
return $cell['value'];
},
$bodyRow['cells']
);
}
return new ExampleTableNode(
$table,
$tableJson['keyword'],
self::getTags($tableJson)
);
},
$json
);
}
}

View file

@ -0,0 +1,18 @@
Contributing to Behat Transliterator
====================================
Updating data
-------------
Setup dependencies with [Composer](https://getcomposer.org):
```bash
composer install
```
Run, char tables in Behat Transliterator will be synced from Perl library
using version defined in `\Behat\Transliterator\SyncTool::LIB_VERSION`
```bash
bin/update-data
```

View file

@ -0,0 +1,213 @@
<?php
namespace Behat\Transliterator;
use RollingCurl\Request;
use RollingCurl\RollingCurl;
use Yaoi\Command;
use Yaoi\Command\Option;
use Yaoi\Http\Client;
use Yaoi\String\Lexer\Parsed;
use Yaoi\String\Lexer\Parser;
use Yaoi\String\Lexer\Renderer;
use Yaoi\String\Lexer\Token;
use Yaoi\String\StringValue;
use Yaoi\String\Parser as StringParser;
/**
* Tool for converting char tables for Behat/Transliterator from Perl to PHP
* @internal
*/
class SyncTool extends Command
{
const LIB_VERSION = '1.27';
private $tokenizer;
private $renderer;
private $phpTable;
private $itemIndex;
private $nonQuestionBoxFound;
private $block;
public function __construct()
{
$escape = array(
'\\}' => '}',
'\\\\' => '\\',
'\\{' => '{',
'\\@' => '@',
'\\$' => '$',
);
$this->tokenizer = new Parser();
$this->tokenizer->addLineStopper('#');
$this->tokenizer->addQuote('qq{', '}', $escape);
$this->tokenizer->addQuote('q{', '}', $escape);
$this->tokenizer->addQuote('"', '"');
$this->tokenizer->addQuote("'", "'");
$this->tokenizer->addBracket('[', ']');
$this->tokenizer->addDelimiter(';');
$this->renderer = new Renderer();
$this->renderer
->setBindKey('-~z', 'z~-')
->strip('#')
->keepBoundaries('[');
}
public static function setUpDefinition(\Yaoi\Command\Definition $definition, $options)
{
$definition->name = 'update-data';
$definition->description = 'Tool for converting char tables for Behat/Transliterator from Perl to PHP';
}
public function performAction()
{
$rollingCurl = new RollingCurl();
foreach ($this->getPerlTablesUrlList() as $url) {
$rollingCurl->get($url);
}
$rollingCurl->setCallback(function (Request $request, RollingCurl $rollingCurl) {
$this->response->addContent($request->getUrl());
$content = $request->getResponseText();
$this->parsePerlTable($content);
})
->execute();
}
private function removePhpCharTable($phpFilePath, $reason)
{
$this->response->addContent($reason);
if (file_exists($phpFilePath)) {
if (unlink($phpFilePath)) {
$this->response->success('Deleted');
} else {
$this->response->error('Failed to delete');
}
} else {
$this->response->success('No PHP file, skipped');
}
}
private function pushItem($item)
{
if ($this->itemIndex >= 16) {
$this->phpTable = trim($this->phpTable);
$this->phpTable .= "\n";
$this->itemIndex = 0;
}
++$this->itemIndex;
$item = new StringValue($item);
if ($item->starts('\x') || $item->starts('\n')) {
$this->phpTable .= '"' . $item . '", ';
$this->nonQuestionBoxFound = true;
} else {
// TODO check if this hack should be removed for chinese letters
if ($item->value === '[?] ') {
$item->value = '[?]';
}
//
if ($item->value !== '[?]') {
$this->nonQuestionBoxFound = true;
}
$this->phpTable .= "'" . str_replace(array('\\', '\''), array('\\\\', '\\\''), $item) . "', ";
}
}
private function tokenizePerlTable($content)
{
$tokens = $this->tokenizer->tokenize($content);
$expression = $this->renderer->getExpression($tokens);
$statement = $expression->getStatement();
/** @var Parsed[] $binds */
$binds = $expression->getBinds();
$parser = new StringParser($statement);
$block = (string)$parser->inner('$Text::Unidecode::Char[', ']');
if (!$block) {
throw new \Exception('Block not found');
}
$this->block = $this->renderer->getExpression($binds[$block])->getStatement();
$itemsBind = (string)$parser->inner('[', ']');
if (!$itemsBind) {
$items = array();
}
else {
$items = $binds[$itemsBind];
}
return $items;
}
private function parsePerlTable($content)
{
$items = $this->tokenizePerlTable($content);
$phpFilePath = __DIR__ . '/data/' . substr($this->block, 1) . '.php';
if (!$items) {
$this->removePhpCharTable($phpFilePath, 'Empty char table for block ' . $this->block);
return;
}
$this->phpTable = <<<PHP
<?php
\$UTF8_TO_ASCII[$this->block] = array(
PHP;
$itemsExpression = $this->renderer->getExpression($items);
$itemsStatement = $itemsExpression->getStatement();
$itemsBinds = $itemsExpression->getBinds();
$itemsStatement = explode(',', $itemsStatement);
$this->itemIndex = 0;
$this->nonQuestionBoxFound = false;
foreach ($itemsStatement as $item) {
$item = trim($item);
if (!$item) {
break;
}
if (isset($itemsBinds[$item])) {
/** @var Token $token */
$token = $itemsBinds[$item];
$item = $token->unEscapedContent;
}
$this->pushItem($item);
}
if ($this->nonQuestionBoxFound) {
$this->phpTable = trim($this->phpTable) . "\n" . ');' . "\n";
if (file_put_contents($phpFilePath, $this->phpTable)) {
$this->response->success('Block ' . $this->block . ' converted to ' . $phpFilePath);
} else {
$this->response->error('Failed to save ' . $phpFilePath);
}
} else {
$this->removePhpCharTable($phpFilePath, 'Block ' . $this->block . ' contains only [?]');
}
}
private function getPerlTablesUrlList()
{
$client = new Client();
$list = array();
$page = $client->fetch('http://cpansearch.perl.org/src/SBURKE/Text-Unidecode-' . self::LIB_VERSION . '/lib/Text/Unidecode/');
foreach (StringParser::create($page)->innerAll('.pm">', '</a>') as $xXXpm) {
$list[] = 'http://cpansearch.perl.org/src/SBURKE/Text-Unidecode-' . self::LIB_VERSION . '/lib/Text/Unidecode/'
. $xXXpm;
}
return $list;
}
}

107
tests/integration/vendor/bin/yaml-lint vendored Executable file
View file

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

View file

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

View file

@ -0,0 +1,356 @@
<?php return array(
'root' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'd3018a7656471a5b0c2d3ad2d01f0fa626d28c7e',
'name' => '__root__',
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'd3018a7656471a5b0c2d3ad2d01f0fa626d28c7e',
'dev_requirement' => false,
),
'behat/behat' => array(
'pretty_version' => 'v3.10.0',
'version' => '3.10.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../behat/behat',
'aliases' => array(),
'reference' => 'a55661154079cf881ef643b303bfaf67bae3a09f',
'dev_requirement' => true,
),
'behat/gherkin' => array(
'pretty_version' => 'v4.9.0',
'version' => '4.9.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../behat/gherkin',
'aliases' => array(),
'reference' => '0bc8d1e30e96183e4f36db9dc79caead300beff4',
'dev_requirement' => true,
),
'behat/transliterator' => array(
'pretty_version' => 'v1.3.0',
'version' => '1.3.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../behat/transliterator',
'aliases' => array(),
'reference' => '3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc',
'dev_requirement' => true,
),
'guzzlehttp/guzzle' => array(
'pretty_version' => '7.4.1',
'version' => '7.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(),
'reference' => 'ee0a041b1760e6a53d2a39c8c34115adc2af2c79',
'dev_requirement' => true,
),
'guzzlehttp/promises' => array(
'pretty_version' => '1.5.1',
'version' => '1.5.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(),
'reference' => 'fe752aedc9fd8fcca3fe7ad05d419d32998a06da',
'dev_requirement' => true,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.1.0',
'version' => '2.1.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'reference' => '089edd38f5b8abba6cb01567c2a8aaa47cec4c72',
'dev_requirement' => true,
),
'psr/container' => array(
'pretty_version' => '1.1.2',
'version' => '1.1.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea',
'dev_requirement' => true,
),
'psr/container-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0',
),
),
'psr/event-dispatcher' => array(
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/event-dispatcher',
'aliases' => array(),
'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
'dev_requirement' => true,
),
'psr/event-dispatcher-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0',
),
),
'psr/http-client' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
'dev_requirement' => true,
),
'psr/http-client-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0',
),
),
'psr/http-factory' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
'dev_requirement' => true,
),
'psr/http-factory-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0',
),
),
'psr/http-message' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
'dev_requirement' => true,
),
'psr/http-message-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0',
),
),
'psr/log-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0|2.0',
),
),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'dev_requirement' => true,
),
'symfony/config' => array(
'pretty_version' => 'v5.4.3',
'version' => '5.4.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/config',
'aliases' => array(),
'reference' => 'd65e1bd990c740e31feb07d2b0927b8d4df9956f',
'dev_requirement' => true,
),
'symfony/console' => array(
'pretty_version' => 'v5.4.5',
'version' => '5.4.5.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(),
'reference' => 'd8111acc99876953f52fe16d4c50eb60940d49ad',
'dev_requirement' => true,
),
'symfony/dependency-injection' => array(
'pretty_version' => 'v5.4.5',
'version' => '5.4.5.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/dependency-injection',
'aliases' => array(),
'reference' => '17f31bc13ef2b577d3c652d71af49d000cbd5894',
'dev_requirement' => true,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v2.5.0',
'version' => '2.5.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'reference' => '6f981ee24cf69ee7ce9736146d1c57c2780598a8',
'dev_requirement' => true,
),
'symfony/event-dispatcher' => array(
'pretty_version' => 'v5.4.3',
'version' => '5.4.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/event-dispatcher',
'aliases' => array(),
'reference' => 'dec8a9f58d20df252b9cd89f1c6c1530f747685d',
'dev_requirement' => true,
),
'symfony/event-dispatcher-contracts' => array(
'pretty_version' => 'v2.5.0',
'version' => '2.5.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
'aliases' => array(),
'reference' => '66bea3b09be61613cd3b4043a65a8ec48cfa6d2a',
'dev_requirement' => true,
),
'symfony/event-dispatcher-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '2.0',
),
),
'symfony/filesystem' => array(
'pretty_version' => 'v5.4.5',
'version' => '5.4.5.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
'reference' => '797680071ea8f71b94eb958680c50d0e002638f5',
'dev_requirement' => true,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'reference' => '30885182c981ab175d4d034db0f6f469898070ab',
'dev_requirement' => true,
),
'symfony/polyfill-intl-grapheme' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
'aliases' => array(),
'reference' => '81b86b50cf841a64252b439e738e97f4a34e2783',
'dev_requirement' => true,
),
'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(),
'reference' => '8590a5f561694770bdcd3f9b5c69dde6945028e8',
'dev_requirement' => true,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825',
'dev_requirement' => true,
),
'symfony/polyfill-php73' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php73',
'aliases' => array(),
'reference' => 'cc5db0e22b3cb4111010e48785a97f670b350ca5',
'dev_requirement' => true,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'reference' => '57b712b08eddb97c762a8caa32c84e037892d2e9',
'dev_requirement' => true,
),
'symfony/polyfill-php81' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php81',
'aliases' => array(),
'reference' => '5de4ba2d41b15f9bd0e19b2ab9674135813ec98f',
'dev_requirement' => true,
),
'symfony/service-contracts' => array(
'pretty_version' => 'v2.5.0',
'version' => '2.5.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(),
'reference' => '1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc',
'dev_requirement' => true,
),
'symfony/service-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0|2.0',
),
),
'symfony/string' => array(
'pretty_version' => 'v5.4.3',
'version' => '5.4.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(),
'reference' => '92043b7d8383e48104e411bc9434b260dbeb5a10',
'dev_requirement' => true,
),
'symfony/translation' => array(
'pretty_version' => 'v5.4.5',
'version' => '5.4.5.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation',
'aliases' => array(),
'reference' => '7e4d52d39e5d86f3f04bef46fa29a1091786bc73',
'dev_requirement' => true,
),
'symfony/translation-contracts' => array(
'pretty_version' => 'v2.5.0',
'version' => '2.5.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(),
'reference' => 'd28150f0f44ce854e942b671fc2620a98aae1b1e',
'dev_requirement' => true,
),
'symfony/translation-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '2.3',
),
),
'symfony/yaml' => array(
'pretty_version' => 'v5.4.3',
'version' => '5.4.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/yaml',
'aliases' => array(),
'reference' => 'e80f87d2c9495966768310fc531b487ce64237a2',
'dev_requirement' => true,
),
),
);

View file

@ -0,0 +1,28 @@
<?php
namespace GuzzleHttp;
use Psr\Http\Message\MessageInterface;
final class BodySummarizer implements BodySummarizerInterface
{
/**
* @var int|null
*/
private $truncateAt;
public function __construct(int $truncateAt = null)
{
$this->truncateAt = $truncateAt;
}
/**
* Returns a summarized message body.
*/
public function summarize(MessageInterface $message): ?string
{
return $this->truncateAt === null
? \GuzzleHttp\Psr7\Message::bodySummary($message)
: \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt);
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace GuzzleHttp;
use Psr\Http\Message\MessageInterface;
interface BodySummarizerInterface
{
/**
* Returns a summarized message body.
*/
public function summarize(MessageInterface $message): ?string;
}

View file

@ -0,0 +1,241 @@
<?php
namespace GuzzleHttp;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/**
* Client interface for sending HTTP requests.
*/
trait ClientTrait
{
/**
* Create and send an HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string $method HTTP method.
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
abstract public function request(string $method, $uri, array $options = []): ResponseInterface;
/**
* Create and send an HTTP GET request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function get($uri, array $options = []): ResponseInterface
{
return $this->request('GET', $uri, $options);
}
/**
* Create and send an HTTP HEAD request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function head($uri, array $options = []): ResponseInterface
{
return $this->request('HEAD', $uri, $options);
}
/**
* Create and send an HTTP PUT request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function put($uri, array $options = []): ResponseInterface
{
return $this->request('PUT', $uri, $options);
}
/**
* Create and send an HTTP POST request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function post($uri, array $options = []): ResponseInterface
{
return $this->request('POST', $uri, $options);
}
/**
* Create and send an HTTP PATCH request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function patch($uri, array $options = []): ResponseInterface
{
return $this->request('PATCH', $uri, $options);
}
/**
* Create and send an HTTP DELETE request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function delete($uri, array $options = []): ResponseInterface
{
return $this->request('DELETE', $uri, $options);
}
/**
* Create and send an asynchronous HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string $method HTTP method
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;
/**
* Create and send an asynchronous HTTP GET request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function getAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('GET', $uri, $options);
}
/**
* Create and send an asynchronous HTTP HEAD request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function headAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('HEAD', $uri, $options);
}
/**
* Create and send an asynchronous HTTP PUT request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function putAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('PUT', $uri, $options);
}
/**
* Create and send an asynchronous HTTP POST request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function postAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('POST', $uri, $options);
}
/**
* Create and send an asynchronous HTTP PATCH request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function patchAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('PATCH', $uri, $options);
}
/**
* Create and send an asynchronous HTTP DELETE request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function deleteAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('DELETE', $uri, $options);
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace GuzzleHttp\Exception;
final class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException
{
}

View file

@ -0,0 +1,42 @@
<?php
namespace GuzzleHttp\Handler;
use GuzzleHttp\Utils;
/**
* @internal
*/
final class HeaderProcessor
{
/**
* Returns the HTTP version, status code, reason phrase, and headers.
*
* @param string[] $headers
*
* @throws \RuntimeException
*
* @return array{0:string, 1:int, 2:?string, 3:array}
*/
public static function parseHeaders(array $headers): array
{
if ($headers === []) {
throw new \RuntimeException('Expected a non-empty array of header data');
}
$parts = \explode(' ', \array_shift($headers), 3);
$version = \explode('/', $parts[0])[1] ?? null;
if ($version === null) {
throw new \RuntimeException('HTTP version missing from header data');
}
$status = $parts[1] ?? null;
if ($status === null) {
throw new \RuntimeException('HTTP status code missing from header data');
}
return [$version, (int) $status, $parts[2] ?? null, Utils::headersFromLines($headers)];
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace GuzzleHttp;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
interface MessageFormatterInterface
{
/**
* Returns a formatted message string.
*
* @param RequestInterface $request Request that was sent
* @param ResponseInterface|null $response Response that was received
* @param \Throwable|null $error Exception that was received
*/
public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string;
}

View file

@ -0,0 +1,382 @@
<?php
namespace GuzzleHttp;
use GuzzleHttp\Exception\InvalidArgumentException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\Handler\Proxy;
use GuzzleHttp\Handler\StreamHandler;
use Psr\Http\Message\UriInterface;
final class Utils
{
/**
* Debug function used to describe the provided value type and class.
*
* @param mixed $input
*
* @return string Returns a string containing the type of the variable and
* if a class is provided, the class name.
*/
public static function describeType($input): string
{
switch (\gettype($input)) {
case 'object':
return 'object(' . \get_class($input) . ')';
case 'array':
return 'array(' . \count($input) . ')';
default:
\ob_start();
\var_dump($input);
// normalize float vs double
/** @var string $varDumpContent */
$varDumpContent = \ob_get_clean();
return \str_replace('double(', 'float(', \rtrim($varDumpContent));
}
}
/**
* Parses an array of header lines into an associative array of headers.
*
* @param iterable $lines Header lines array of strings in the following
* format: "Name: Value"
*/
public static function headersFromLines(iterable $lines): array
{
$headers = [];
foreach ($lines as $line) {
$parts = \explode(':', $line, 2);
$headers[\trim($parts[0])][] = isset($parts[1]) ? \trim($parts[1]) : null;
}
return $headers;
}
/**
* Returns a debug stream based on the provided variable.
*
* @param mixed $value Optional value
*
* @return resource
*/
public static function debugResource($value = null)
{
if (\is_resource($value)) {
return $value;
}
if (\defined('STDOUT')) {
return \STDOUT;
}
return \GuzzleHttp\Psr7\Utils::tryFopen('php://output', 'w');
}
/**
* Chooses and creates a default handler to use based on the environment.
*
* The returned handler is not wrapped by any default middlewares.
*
* @throws \RuntimeException if no viable Handler is available.
*
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
*/
public static function chooseHandler(): callable
{
$handler = null;
if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) {
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
} elseif (\function_exists('curl_exec')) {
$handler = new CurlHandler();
} elseif (\function_exists('curl_multi_exec')) {
$handler = new CurlMultiHandler();
}
if (\ini_get('allow_url_fopen')) {
$handler = $handler
? Proxy::wrapStreaming($handler, new StreamHandler())
: new StreamHandler();
} elseif (!$handler) {
throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.');
}
return $handler;
}
/**
* Get the default User-Agent string to use with Guzzle.
*/
public static function defaultUserAgent(): string
{
return sprintf('GuzzleHttp/%d', ClientInterface::MAJOR_VERSION);
}
/**
* Returns the default cacert bundle for the current system.
*
* First, the openssl.cafile and curl.cainfo php.ini settings are checked.
* If those settings are not configured, then the common locations for
* bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
* and Windows are checked. If any of these file locations are found on
* disk, they will be utilized.
*
* Note: the result of this function is cached for subsequent calls.
*
* @throws \RuntimeException if no bundle can be found.
*
* @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+.
*/
public static function defaultCaBundle(): string
{
static $cached = null;
static $cafiles = [
// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
'/etc/pki/tls/certs/ca-bundle.crt',
// Ubuntu, Debian (provided by the ca-certificates package)
'/etc/ssl/certs/ca-certificates.crt',
// FreeBSD (provided by the ca_root_nss package)
'/usr/local/share/certs/ca-root-nss.crt',
// SLES 12 (provided by the ca-certificates package)
'/var/lib/ca-certificates/ca-bundle.pem',
// OS X provided by homebrew (using the default path)
'/usr/local/etc/openssl/cert.pem',
// Google app engine
'/etc/ca-certificates.crt',
// Windows?
'C:\\windows\\system32\\curl-ca-bundle.crt',
'C:\\windows\\curl-ca-bundle.crt',
];
if ($cached) {
return $cached;
}
if ($ca = \ini_get('openssl.cafile')) {
return $cached = $ca;
}
if ($ca = \ini_get('curl.cainfo')) {
return $cached = $ca;
}
foreach ($cafiles as $filename) {
if (\file_exists($filename)) {
return $cached = $filename;
}
}
throw new \RuntimeException(
<<< EOT
No system CA bundle could be found in any of the the common system locations.
PHP versions earlier than 5.6 are not properly configured to use the system's
CA bundle by default. In order to verify peer certificates, you will need to
supply the path on disk to a certificate bundle to the 'verify' request
option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
need a specific certificate bundle, then Mozilla provides a commonly used CA
bundle which can be downloaded here (provided by the maintainer of cURL):
https://curl.haxx.se/ca/cacert.pem. Once
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
ini setting to point to the path to the file, allowing you to omit the 'verify'
request option. See https://curl.haxx.se/docs/sslcerts.html for more
information.
EOT
);
}
/**
* Creates an associative array of lowercase header names to the actual
* header casing.
*/
public static function normalizeHeaderKeys(array $headers): array
{
$result = [];
foreach (\array_keys($headers) as $key) {
$result[\strtolower($key)] = $key;
}
return $result;
}
/**
* Returns true if the provided host matches any of the no proxy areas.
*
* This method will strip a port from the host if it is present. Each pattern
* can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
* partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
* "baz.foo.com", but ".foo.com" != "foo.com").
*
* Areas are matched in the following cases:
* 1. "*" (without quotes) always matches any hosts.
* 2. An exact match.
* 3. The area starts with "." and the area is the last part of the host. e.g.
* '.mit.edu' will match any host that ends with '.mit.edu'.
*
* @param string $host Host to check against the patterns.
* @param string[] $noProxyArray An array of host patterns.
*
* @throws InvalidArgumentException
*/
public static function isHostInNoProxy(string $host, array $noProxyArray): bool
{
if (\strlen($host) === 0) {
throw new InvalidArgumentException('Empty host provided');
}
// Strip port if present.
[$host] = \explode(':', $host, 2);
foreach ($noProxyArray as $area) {
// Always match on wildcards.
if ($area === '*') {
return true;
}
if (empty($area)) {
// Don't match on empty values.
continue;
}
if ($area === $host) {
// Exact matches.
return true;
}
// Special match if the area when prefixed with ".". Remove any
// existing leading "." and add a new leading ".".
$area = '.' . \ltrim($area, '.');
if (\substr($host, -(\strlen($area))) === $area) {
return true;
}
}
return false;
}
/**
* Wrapper for json_decode that throws when an error occurs.
*
* @param string $json JSON data to parse
* @param bool $assoc When true, returned objects will be converted
* into associative arrays.
* @param int $depth User specified recursion depth.
* @param int $options Bitmask of JSON decode options.
*
* @return object|array|string|int|float|bool|null
*
* @throws InvalidArgumentException if the JSON cannot be decoded.
*
* @link https://www.php.net/manual/en/function.json-decode.php
*/
public static function jsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
{
$data = \json_decode($json, $assoc, $depth, $options);
if (\JSON_ERROR_NONE !== \json_last_error()) {
throw new InvalidArgumentException('json_decode error: ' . \json_last_error_msg());
}
return $data;
}
/**
* Wrapper for JSON encoding that throws when an error occurs.
*
* @param mixed $value The value being encoded
* @param int $options JSON encode option bitmask
* @param int $depth Set the maximum depth. Must be greater than zero.
*
* @throws InvalidArgumentException if the JSON cannot be encoded.
*
* @link https://www.php.net/manual/en/function.json-encode.php
*/
public static function jsonEncode($value, int $options = 0, int $depth = 512): string
{
$json = \json_encode($value, $options, $depth);
if (\JSON_ERROR_NONE !== \json_last_error()) {
throw new InvalidArgumentException('json_encode error: ' . \json_last_error_msg());
}
/** @var string */
return $json;
}
/**
* Wrapper for the hrtime() or microtime() functions
* (depending on the PHP version, one of the two is used)
*
* @return float UNIX timestamp
*
* @internal
*/
public static function currentTime(): float
{
return (float) \function_exists('hrtime') ? \hrtime(true) / 1e9 : \microtime(true);
}
/**
* @throws InvalidArgumentException
*
* @internal
*/
public static function idnUriConvert(UriInterface $uri, int $options = 0): UriInterface
{
if ($uri->getHost()) {
$asciiHost = self::idnToAsci($uri->getHost(), $options, $info);
if ($asciiHost === false) {
$errorBitSet = $info['errors'] ?? 0;
$errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool {
return substr($name, 0, 11) === 'IDNA_ERROR_';
});
$errors = [];
foreach ($errorConstants as $errorConstant) {
if ($errorBitSet & constant($errorConstant)) {
$errors[] = $errorConstant;
}
}
$errorMessage = 'IDN conversion failed';
if ($errors) {
$errorMessage .= ' (errors: ' . implode(', ', $errors) . ')';
}
throw new InvalidArgumentException($errorMessage);
}
if ($uri->getHost() !== $asciiHost) {
// Replace URI only if the ASCII version is different
$uri = $uri->withHost($asciiHost);
}
}
return $uri;
}
/**
* @internal
*/
public static function getenv(string $name): ?string
{
if (isset($_SERVER[$name])) {
return (string) $_SERVER[$name];
}
if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) {
return (string) $value;
}
return null;
}
/**
* @return string|false
*/
private static function idnToAsci(string $domain, int $options, ?array &$info = [])
{
if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) {
return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info);
}
throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old');
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace GuzzleHttp\Promise;
final class Create
{
/**
* Creates a promise for a value if the value is not a promise.
*
* @param mixed $value Promise or value.
*
* @return PromiseInterface
*/
public static function promiseFor($value)
{
if ($value instanceof PromiseInterface) {
return $value;
}
// Return a Guzzle promise that shadows the given promise.
if (is_object($value) && method_exists($value, 'then')) {
$wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
$cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
$promise = new Promise($wfn, $cfn);
$value->then([$promise, 'resolve'], [$promise, 'reject']);
return $promise;
}
return new FulfilledPromise($value);
}
/**
* Creates a rejected promise for a reason if the reason is not a promise.
* If the provided reason is a promise, then it is returned as-is.
*
* @param mixed $reason Promise or reason.
*
* @return PromiseInterface
*/
public static function rejectionFor($reason)
{
if ($reason instanceof PromiseInterface) {
return $reason;
}
return new RejectedPromise($reason);
}
/**
* Create an exception for a rejected promise value.
*
* @param mixed $reason
*
* @return \Exception|\Throwable
*/
public static function exceptionFor($reason)
{
if ($reason instanceof \Exception || $reason instanceof \Throwable) {
return $reason;
}
return new RejectionException($reason);
}
/**
* Returns an iterator for the given value.
*
* @param mixed $value
*
* @return \Iterator
*/
public static function iterFor($value)
{
if ($value instanceof \Iterator) {
return $value;
}
if (is_array($value)) {
return new \ArrayIterator($value);
}
return new \ArrayIterator([$value]);
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace GuzzleHttp\Promise;
final class Each
{
/**
* Given an iterator that yields promises or values, returns a promise that
* is fulfilled with a null value when the iterator has been consumed or
* the aggregate promise has been fulfilled or rejected.
*
* $onFulfilled is a function that accepts the fulfilled value, iterator
* index, and the aggregate promise. The callback can invoke any necessary
* side effects and choose to resolve or reject the aggregate if needed.
*
* $onRejected is a function that accepts the rejection reason, iterator
* index, and the aggregate promise. The callback can invoke any necessary
* side effects and choose to resolve or reject the aggregate if needed.
*
* @param mixed $iterable Iterator or array to iterate over.
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return PromiseInterface
*/
public static function of(
$iterable,
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected
]))->promise();
}
/**
* Like of, but only allows a certain number of outstanding promises at any
* given time.
*
* $concurrency may be an integer or a function that accepts the number of
* pending promises and returns a numeric concurrency limit value to allow
* for dynamic a concurrency size.
*
* @param mixed $iterable
* @param int|callable $concurrency
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return PromiseInterface
*/
public static function ofLimit(
$iterable,
$concurrency,
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected,
'concurrency' => $concurrency
]))->promise();
}
/**
* Like limit, but ensures that no promise in the given $iterable argument
* is rejected. If any promise is rejected, then the aggregate promise is
* rejected with the encountered rejection.
*
* @param mixed $iterable
* @param int|callable $concurrency
* @param callable $onFulfilled
*
* @return PromiseInterface
*/
public static function ofLimitAll(
$iterable,
$concurrency,
callable $onFulfilled = null
) {
return each_limit(
$iterable,
$concurrency,
$onFulfilled,
function ($reason, $idx, PromiseInterface $aggregate) {
$aggregate->reject($reason);
}
);
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace GuzzleHttp\Promise;
final class Is
{
/**
* Returns true if a promise is pending.
*
* @return bool
*/
public static function pending(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::PENDING;
}
/**
* Returns true if a promise is fulfilled or rejected.
*
* @return bool
*/
public static function settled(PromiseInterface $promise)
{
return $promise->getState() !== PromiseInterface::PENDING;
}
/**
* Returns true if a promise is fulfilled.
*
* @return bool
*/
public static function fulfilled(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::FULFILLED;
}
/**
* Returns true if a promise is rejected.
*
* @return bool
*/
public static function rejected(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::REJECTED;
}
}

View file

@ -0,0 +1,276 @@
<?php
namespace GuzzleHttp\Promise;
final class Utils
{
/**
* Get the global task queue used for promise resolution.
*
* This task queue MUST be run in an event loop in order for promises to be
* settled asynchronously. It will be automatically run when synchronously
* waiting on a promise.
*
* <code>
* while ($eventLoop->isRunning()) {
* GuzzleHttp\Promise\Utils::queue()->run();
* }
* </code>
*
* @param TaskQueueInterface $assign Optionally specify a new queue instance.
*
* @return TaskQueueInterface
*/
public static function queue(TaskQueueInterface $assign = null)
{
static $queue;
if ($assign) {
$queue = $assign;
} elseif (!$queue) {
$queue = new TaskQueue();
}
return $queue;
}
/**
* Adds a function to run in the task queue when it is next `run()` and
* returns a promise that is fulfilled or rejected with the result.
*
* @param callable $task Task function to run.
*
* @return PromiseInterface
*/
public static function task(callable $task)
{
$queue = self::queue();
$promise = new Promise([$queue, 'run']);
$queue->add(function () use ($task, $promise) {
try {
if (Is::pending($promise)) {
$promise->resolve($task());
}
} catch (\Throwable $e) {
$promise->reject($e);
} catch (\Exception $e) {
$promise->reject($e);
}
});
return $promise;
}
/**
* Synchronously waits on a promise to resolve and returns an inspection
* state array.
*
* Returns a state associative array containing a "state" key mapping to a
* valid promise state. If the state of the promise is "fulfilled", the
* array will contain a "value" key mapping to the fulfilled value of the
* promise. If the promise is rejected, the array will contain a "reason"
* key mapping to the rejection reason of the promise.
*
* @param PromiseInterface $promise Promise or value.
*
* @return array
*/
public static function inspect(PromiseInterface $promise)
{
try {
return [
'state' => PromiseInterface::FULFILLED,
'value' => $promise->wait()
];
} catch (RejectionException $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
} catch (\Throwable $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
} catch (\Exception $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
}
}
/**
* Waits on all of the provided promises, but does not unwrap rejected
* promises as thrown exception.
*
* Returns an array of inspection state arrays.
*
* @see inspect for the inspection state array format.
*
* @param PromiseInterface[] $promises Traversable of promises to wait upon.
*
* @return array
*/
public static function inspectAll($promises)
{
$results = [];
foreach ($promises as $key => $promise) {
$results[$key] = inspect($promise);
}
return $results;
}
/**
* Waits on all of the provided promises and returns the fulfilled values.
*
* Returns an array that contains the value of each promise (in the same
* order the promises were provided). An exception is thrown if any of the
* promises are rejected.
*
* @param iterable<PromiseInterface> $promises Iterable of PromiseInterface objects to wait on.
*
* @return array
*
* @throws \Exception on error
* @throws \Throwable on error in PHP >=7
*/
public static function unwrap($promises)
{
$results = [];
foreach ($promises as $key => $promise) {
$results[$key] = $promise->wait();
}
return $results;
}
/**
* Given an array of promises, return a promise that is fulfilled when all
* the items in the array are fulfilled.
*
* The promise's fulfillment value is an array with fulfillment values at
* respective positions to the original array. If any promise in the array
* rejects, the returned promise is rejected with the rejection reason.
*
* @param mixed $promises Promises or values.
* @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
*
* @return PromiseInterface
*/
public static function all($promises, $recursive = false)
{
$results = [];
$promise = Each::of(
$promises,
function ($value, $idx) use (&$results) {
$results[$idx] = $value;
},
function ($reason, $idx, Promise $aggregate) {
$aggregate->reject($reason);
}
)->then(function () use (&$results) {
ksort($results);
return $results;
});
if (true === $recursive) {
$promise = $promise->then(function ($results) use ($recursive, &$promises) {
foreach ($promises as $promise) {
if (Is::pending($promise)) {
return self::all($promises, $recursive);
}
}
return $results;
});
}
return $promise;
}
/**
* Initiate a competitive race between multiple promises or values (values
* will become immediately fulfilled promises).
*
* When count amount of promises have been fulfilled, the returned promise
* is fulfilled with an array that contains the fulfillment values of the
* winners in order of resolution.
*
* This promise is rejected with a {@see AggregateException} if the number
* of fulfilled promises is less than the desired $count.
*
* @param int $count Total number of promises.
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*/
public static function some($count, $promises)
{
$results = [];
$rejections = [];
return Each::of(
$promises,
function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
if (Is::settled($p)) {
return;
}
$results[$idx] = $value;
if (count($results) >= $count) {
$p->resolve(null);
}
},
function ($reason) use (&$rejections) {
$rejections[] = $reason;
}
)->then(
function () use (&$results, &$rejections, $count) {
if (count($results) !== $count) {
throw new AggregateException(
'Not enough promises to fulfill count',
$rejections
);
}
ksort($results);
return array_values($results);
}
);
}
/**
* Like some(), with 1 as count. However, if the promise fulfills, the
* fulfillment value is not an array of 1 but the value directly.
*
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*/
public static function any($promises)
{
return self::some(1, $promises)->then(function ($values) {
return $values[0];
});
}
/**
* Returns a promise that is fulfilled when all of the provided promises have
* been fulfilled or rejected.
*
* The returned promise is fulfilled with an array of inspection state arrays.
*
* @see inspect for the inspection state array format.
*
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*/
public static function settle($promises)
{
$results = [];
return Each::of(
$promises,
function ($value, $idx) use (&$results) {
$results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
},
function ($reason, $idx) use (&$results) {
$results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
}
)->then(function () use (&$results) {
ksort($results);
return $results;
});
}
}

View file

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Psr7\Exception;
use InvalidArgumentException;
/**
* Exception thrown if a URI cannot be parsed because it's malformed.
*/
class MalformedUriException extends InvalidArgumentException
{
}

View file

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Psr7;
final class Header
{
/**
* Parse an array of header values containing ";" separated data into an
* array of associative arrays representing the header key value pair data
* of the header. When a parameter does not contain a value, but just
* contains a key, this function will inject a key with a '' string value.
*
* @param string|array $header Header to parse into components.
*/
public static function parse($header): array
{
static $trimmed = "\"' \n\t\r";
$params = $matches = [];
foreach (self::normalize($header) as $val) {
$part = [];
foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
$m = $matches[0];
if (isset($m[1])) {
$part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
} else {
$part[] = trim($m[0], $trimmed);
}
}
}
if ($part) {
$params[] = $part;
}
}
return $params;
}
/**
* Converts an array of header values that may contain comma separated
* headers into an array of headers with no comma separated values.
*
* @param string|array $header Header to normalize.
*/
public static function normalize($header): array
{
if (!is_array($header)) {
return array_map('trim', explode(',', $header));
}
$result = [];
foreach ($header as $value) {
foreach ((array) $value as $v) {
if (strpos($v, ',') === false) {
$result[] = $v;
continue;
}
foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
$result[] = trim($vv);
}
}
}
return $result;
}
}

View file

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
/**
* Implements all of the PSR-17 interfaces.
*
* Note: in consuming code it is recommended to require the implemented interfaces
* and inject the instance of this class multiple times.
*/
final class HttpFactory implements
RequestFactoryInterface,
ResponseFactoryInterface,
ServerRequestFactoryInterface,
StreamFactoryInterface,
UploadedFileFactoryInterface,
UriFactoryInterface
{
public function createUploadedFile(
StreamInterface $stream,
int $size = null,
int $error = \UPLOAD_ERR_OK,
string $clientFilename = null,
string $clientMediaType = null
): UploadedFileInterface {
if ($size === null) {
$size = $stream->getSize();
}
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
}
public function createStream(string $content = ''): StreamInterface
{
return Utils::streamFor($content);
}
public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface
{
try {
$resource = Utils::tryFopen($file, $mode);
} catch (\RuntimeException $e) {
if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e);
}
throw $e;
}
return Utils::streamFor($resource);
}
public function createStreamFromResource($resource): StreamInterface
{
return Utils::streamFor($resource);
}
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
{
if (empty($method)) {
if (!empty($serverParams['REQUEST_METHOD'])) {
$method = $serverParams['REQUEST_METHOD'];
} else {
throw new \InvalidArgumentException('Cannot determine HTTP method');
}
}
return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
}
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
{
return new Response($code, [], null, '1.1', $reasonPhrase);
}
public function createRequest(string $method, $uri): RequestInterface
{
return new Request($method, $uri);
}
public function createUri(string $uri = ''): UriInterface
{
return new Uri($uri);
}
}

View file

@ -0,0 +1,242 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
final class Message
{
/**
* Returns the string representation of an HTTP message.
*
* @param MessageInterface $message Message to convert to a string.
*/
public static function toString(MessageInterface $message): string
{
if ($message instanceof RequestInterface) {
$msg = trim($message->getMethod() . ' '
. $message->getRequestTarget())
. ' HTTP/' . $message->getProtocolVersion();
if (!$message->hasHeader('host')) {
$msg .= "\r\nHost: " . $message->getUri()->getHost();
}
} elseif ($message instanceof ResponseInterface) {
$msg = 'HTTP/' . $message->getProtocolVersion() . ' '
. $message->getStatusCode() . ' '
. $message->getReasonPhrase();
} else {
throw new \InvalidArgumentException('Unknown message type');
}
foreach ($message->getHeaders() as $name => $values) {
if (strtolower($name) === 'set-cookie') {
foreach ($values as $value) {
$msg .= "\r\n{$name}: " . $value;
}
} else {
$msg .= "\r\n{$name}: " . implode(', ', $values);
}
}
return "{$msg}\r\n\r\n" . $message->getBody();
}
/**
* Get a short summary of the message body.
*
* Will return `null` if the response is not printable.
*
* @param MessageInterface $message The message to get the body summary
* @param int $truncateAt The maximum allowed size of the summary
*/
public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string
{
$body = $message->getBody();
if (!$body->isSeekable() || !$body->isReadable()) {
return null;
}
$size = $body->getSize();
if ($size === 0) {
return null;
}
$summary = $body->read($truncateAt);
$body->rewind();
if ($size > $truncateAt) {
$summary .= ' (truncated...)';
}
// Matches any printable character, including unicode characters:
// letters, marks, numbers, punctuation, spacing, and separators.
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) {
return null;
}
return $summary;
}
/**
* Attempts to rewind a message body and throws an exception on failure.
*
* The body of the message will only be rewound if a call to `tell()`
* returns a value other than `0`.
*
* @param MessageInterface $message Message to rewind
*
* @throws \RuntimeException
*/
public static function rewindBody(MessageInterface $message): void
{
$body = $message->getBody();
if ($body->tell()) {
$body->rewind();
}
}
/**
* Parses an HTTP message into an associative array.
*
* The array contains the "start-line" key containing the start line of
* the message, "headers" key containing an associative array of header
* array values, and a "body" key containing the body of the message.
*
* @param string $message HTTP request or response to parse.
*/
public static function parseMessage(string $message): array
{
if (!$message) {
throw new \InvalidArgumentException('Invalid message');
}
$message = ltrim($message, "\r\n");
$messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
if ($messageParts === false || count($messageParts) !== 2) {
throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
}
[$rawHeaders, $body] = $messageParts;
$rawHeaders .= "\r\n"; // Put back the delimiter we split previously
$headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
if ($headerParts === false || count($headerParts) !== 2) {
throw new \InvalidArgumentException('Invalid message: Missing status line');
}
[$startLine, $rawHeaders] = $headerParts;
if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
// Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
$rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
}
/** @var array[] $headerLines */
$count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
// If these aren't the same, then one line didn't match and there's an invalid header.
if ($count !== substr_count($rawHeaders, "\n")) {
// Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
}
throw new \InvalidArgumentException('Invalid header syntax');
}
$headers = [];
foreach ($headerLines as $headerLine) {
$headers[$headerLine[1]][] = $headerLine[2];
}
return [
'start-line' => $startLine,
'headers' => $headers,
'body' => $body,
];
}
/**
* Constructs a URI for an HTTP request message.
*
* @param string $path Path from the start-line
* @param array $headers Array of headers (each value an array).
*/
public static function parseRequestUri(string $path, array $headers): string
{
$hostKey = array_filter(array_keys($headers), function ($k) {
return strtolower($k) === 'host';
});
// If no host is found, then a full URI cannot be constructed.
if (!$hostKey) {
return $path;
}
$host = $headers[reset($hostKey)][0];
$scheme = substr($host, -4) === ':443' ? 'https' : 'http';
return $scheme . '://' . $host . '/' . ltrim($path, '/');
}
/**
* Parses a request message string into a request object.
*
* @param string $message Request message string.
*/
public static function parseRequest(string $message): RequestInterface
{
$data = self::parseMessage($message);
$matches = [];
if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
throw new \InvalidArgumentException('Invalid request string');
}
$parts = explode(' ', $data['start-line'], 3);
$version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
$request = new Request(
$parts[0],
$matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
$data['headers'],
$data['body'],
$version
);
return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
}
/**
* Parses a response message string into a response object.
*
* @param string $message Response message string.
*/
public static function parseResponse(string $message): ResponseInterface
{
$data = self::parseMessage($message);
// According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
// between status-code and reason-phrase is required. But browsers accept
// responses without space and reason as well.
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
}
$parts = explode(' ', $data['start-line'], 3);
return new Response(
(int) $parts[1],
$data['headers'],
$data['body'],
explode('/', $parts[0])[1],
$parts[2] ?? null
);
}
}

View file

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Psr7;
final class MimeType
{
private const MIME_TYPES = [
'3gp' => 'video/3gpp',
'7z' => 'application/x-7z-compressed',
'aac' => 'audio/x-aac',
'ai' => 'application/postscript',
'aif' => 'audio/x-aiff',
'asc' => 'text/plain',
'asf' => 'video/x-ms-asf',
'atom' => 'application/atom+xml',
'avi' => 'video/x-msvideo',
'bmp' => 'image/bmp',
'bz2' => 'application/x-bzip2',
'cer' => 'application/pkix-cert',
'crl' => 'application/pkix-crl',
'crt' => 'application/x-x509-ca-cert',
'css' => 'text/css',
'csv' => 'text/csv',
'cu' => 'application/cu-seeme',
'deb' => 'application/x-debian-package',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dvi' => 'application/x-dvi',
'eot' => 'application/vnd.ms-fontobject',
'eps' => 'application/postscript',
'epub' => 'application/epub+zip',
'etx' => 'text/x-setext',
'flac' => 'audio/flac',
'flv' => 'video/x-flv',
'gif' => 'image/gif',
'gz' => 'application/gzip',
'htm' => 'text/html',
'html' => 'text/html',
'ico' => 'image/x-icon',
'ics' => 'text/calendar',
'ini' => 'text/plain',
'iso' => 'application/x-iso9660-image',
'jar' => 'application/java-archive',
'jpe' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'js' => 'text/javascript',
'json' => 'application/json',
'latex' => 'application/x-latex',
'log' => 'text/plain',
'm4a' => 'audio/mp4',
'm4v' => 'video/mp4',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mov' => 'video/quicktime',
'mkv' => 'video/x-matroska',
'mp3' => 'audio/mpeg',
'mp4' => 'video/mp4',
'mp4a' => 'audio/mp4',
'mp4v' => 'video/mp4',
'mpe' => 'video/mpeg',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpg4' => 'video/mp4',
'oga' => 'audio/ogg',
'ogg' => 'audio/ogg',
'ogv' => 'video/ogg',
'ogx' => 'application/ogg',
'pbm' => 'image/x-portable-bitmap',
'pdf' => 'application/pdf',
'pgm' => 'image/x-portable-graymap',
'png' => 'image/png',
'pnm' => 'image/x-portable-anymap',
'ppm' => 'image/x-portable-pixmap',
'ppt' => 'application/vnd.ms-powerpoint',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'ps' => 'application/postscript',
'qt' => 'video/quicktime',
'rar' => 'application/x-rar-compressed',
'ras' => 'image/x-cmu-raster',
'rss' => 'application/rss+xml',
'rtf' => 'application/rtf',
'sgm' => 'text/sgml',
'sgml' => 'text/sgml',
'svg' => 'image/svg+xml',
'swf' => 'application/x-shockwave-flash',
'tar' => 'application/x-tar',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'torrent' => 'application/x-bittorrent',
'ttf' => 'application/x-font-ttf',
'txt' => 'text/plain',
'wav' => 'audio/x-wav',
'webm' => 'video/webm',
'webp' => 'image/webp',
'wma' => 'audio/x-ms-wma',
'wmv' => 'video/x-ms-wmv',
'woff' => 'application/x-font-woff',
'wsdl' => 'application/wsdl+xml',
'xbm' => 'image/x-xbitmap',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xml' => 'application/xml',
'xpm' => 'image/x-xpixmap',
'xwd' => 'image/x-xwindowdump',
'yaml' => 'text/yaml',
'yml' => 'text/yaml',
'zip' => 'application/zip',
];
/**
* Determines the mimetype of a file by looking at its extension.
*/
public static function fromFilename(string $filename): ?string
{
return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION));
}
/**
* Maps a file extensions to a mimetype.
*
* @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
*/
public static function fromExtension(string $extension): ?string
{
return self::MIME_TYPES[strtolower($extension)] ?? null;
}
}

View file

@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Psr7;
final class Query
{
/**
* Parse a query string into an associative array.
*
* If multiple values are found for the same key, the value of that key
* value pair will become an array. This function does not parse nested
* PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
* will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
*
* @param string $str Query string to parse
* @param int|bool $urlEncoding How the query string is encoded
*/
public static function parse(string $str, $urlEncoding = true): array
{
$result = [];
if ($str === '') {
return $result;
}
if ($urlEncoding === true) {
$decoder = function ($value) {
return rawurldecode(str_replace('+', ' ', (string) $value));
};
} elseif ($urlEncoding === PHP_QUERY_RFC3986) {
$decoder = 'rawurldecode';
} elseif ($urlEncoding === PHP_QUERY_RFC1738) {
$decoder = 'urldecode';
} else {
$decoder = function ($str) {
return $str;
};
}
foreach (explode('&', $str) as $kvp) {
$parts = explode('=', $kvp, 2);
$key = $decoder($parts[0]);
$value = isset($parts[1]) ? $decoder($parts[1]) : null;
if (!isset($result[$key])) {
$result[$key] = $value;
} else {
if (!is_array($result[$key])) {
$result[$key] = [$result[$key]];
}
$result[$key][] = $value;
}
}
return $result;
}
/**
* Build a query string from an array of key value pairs.
*
* This function can use the return value of `parse()` to build a query
* string. This function does not modify the provided keys when an array is
* encountered (like `http_build_query()` would).
*
* @param array $params Query string parameters.
* @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
* to encode using RFC3986, or PHP_QUERY_RFC1738
* to encode using RFC1738.
*/
public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string
{
if (!$params) {
return '';
}
if ($encoding === false) {
$encoder = function (string $str): string {
return $str;
};
} elseif ($encoding === PHP_QUERY_RFC3986) {
$encoder = 'rawurlencode';
} elseif ($encoding === PHP_QUERY_RFC1738) {
$encoder = 'urlencode';
} else {
throw new \InvalidArgumentException('Invalid type');
}
$qs = '';
foreach ($params as $k => $v) {
$k = $encoder((string) $k);
if (!is_array($v)) {
$qs .= $k;
$v = is_bool($v) ? (int) $v : $v;
if ($v !== null) {
$qs .= '=' . $encoder((string) $v);
}
$qs .= '&';
} else {
foreach ($v as $vv) {
$qs .= $k;
$vv = is_bool($vv) ? (int) $vv : $vv;
if ($vv !== null) {
$qs .= '=' . $encoder((string) $vv);
}
$qs .= '&';
}
}
}
return $qs ? (string) substr($qs, 0, -1) : '';
}
}

View file

@ -0,0 +1,412 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
final class Utils
{
/**
* Remove the items given by the keys, case insensitively from the data.
*
* @param string[] $keys
*/
public static function caselessRemove(array $keys, array $data): array
{
$result = [];
foreach ($keys as &$key) {
$key = strtolower($key);
}
foreach ($data as $k => $v) {
if (!is_string($k) || !in_array(strtolower($k), $keys)) {
$result[$k] = $v;
}
}
return $result;
}
/**
* Copy the contents of a stream into another stream until the given number
* of bytes have been read.
*
* @param StreamInterface $source Stream to read from
* @param StreamInterface $dest Stream to write to
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
*
* @throws \RuntimeException on error.
*/
public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void
{
$bufferSize = 8192;
if ($maxLen === -1) {
while (!$source->eof()) {
if (!$dest->write($source->read($bufferSize))) {
break;
}
}
} else {
$remaining = $maxLen;
while ($remaining > 0 && !$source->eof()) {
$buf = $source->read(min($bufferSize, $remaining));
$len = strlen($buf);
if (!$len) {
break;
}
$remaining -= $len;
$dest->write($buf);
}
}
}
/**
* Copy the contents of a stream into a string until the given number of
* bytes have been read.
*
* @param StreamInterface $stream Stream to read
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
*
* @throws \RuntimeException on error.
*/
public static function copyToString(StreamInterface $stream, int $maxLen = -1): string
{
$buffer = '';
if ($maxLen === -1) {
while (!$stream->eof()) {
$buf = $stream->read(1048576);
if ($buf === '') {
break;
}
$buffer .= $buf;
}
return $buffer;
}
$len = 0;
while (!$stream->eof() && $len < $maxLen) {
$buf = $stream->read($maxLen - $len);
if ($buf === '') {
break;
}
$buffer .= $buf;
$len = strlen($buffer);
}
return $buffer;
}
/**
* Calculate a hash of a stream.
*
* This method reads the entire stream to calculate a rolling hash, based
* on PHP's `hash_init` functions.
*
* @param StreamInterface $stream Stream to calculate the hash for
* @param string $algo Hash algorithm (e.g. md5, crc32, etc)
* @param bool $rawOutput Whether or not to use raw output
*
* @throws \RuntimeException on error.
*/
public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string
{
$pos = $stream->tell();
if ($pos > 0) {
$stream->rewind();
}
$ctx = hash_init($algo);
while (!$stream->eof()) {
hash_update($ctx, $stream->read(1048576));
}
$out = hash_final($ctx, (bool) $rawOutput);
$stream->seek($pos);
return $out;
}
/**
* Clone and modify a request with the given changes.
*
* This method is useful for reducing the number of clones needed to mutate
* a message.
*
* The changes can be one of:
* - method: (string) Changes the HTTP method.
* - set_headers: (array) Sets the given headers.
* - remove_headers: (array) Remove the given headers.
* - body: (mixed) Sets the given body.
* - uri: (UriInterface) Set the URI.
* - query: (string) Set the query string value of the URI.
* - version: (string) Set the protocol version.
*
* @param RequestInterface $request Request to clone and modify.
* @param array $changes Changes to apply.
*/
public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface
{
if (!$changes) {
return $request;
}
$headers = $request->getHeaders();
if (!isset($changes['uri'])) {
$uri = $request->getUri();
} else {
// Remove the host header if one is on the URI
if ($host = $changes['uri']->getHost()) {
$changes['set_headers']['Host'] = $host;
if ($port = $changes['uri']->getPort()) {
$standardPorts = ['http' => 80, 'https' => 443];
$scheme = $changes['uri']->getScheme();
if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
$changes['set_headers']['Host'] .= ':' . $port;
}
}
}
$uri = $changes['uri'];
}
if (!empty($changes['remove_headers'])) {
$headers = self::caselessRemove($changes['remove_headers'], $headers);
}
if (!empty($changes['set_headers'])) {
$headers = self::caselessRemove(array_keys($changes['set_headers']), $headers);
$headers = $changes['set_headers'] + $headers;
}
if (isset($changes['query'])) {
$uri = $uri->withQuery($changes['query']);
}
if ($request instanceof ServerRequestInterface) {
$new = (new ServerRequest(
$changes['method'] ?? $request->getMethod(),
$uri,
$headers,
$changes['body'] ?? $request->getBody(),
$changes['version'] ?? $request->getProtocolVersion(),
$request->getServerParams()
))
->withParsedBody($request->getParsedBody())
->withQueryParams($request->getQueryParams())
->withCookieParams($request->getCookieParams())
->withUploadedFiles($request->getUploadedFiles());
foreach ($request->getAttributes() as $key => $value) {
$new = $new->withAttribute($key, $value);
}
return $new;
}
return new Request(
$changes['method'] ?? $request->getMethod(),
$uri,
$headers,
$changes['body'] ?? $request->getBody(),
$changes['version'] ?? $request->getProtocolVersion()
);
}
/**
* Read a line from the stream up to the maximum allowed buffer length.
*
* @param StreamInterface $stream Stream to read from
* @param int|null $maxLength Maximum buffer length
*/
public static function readLine(StreamInterface $stream, ?int $maxLength = null): string
{
$buffer = '';
$size = 0;
while (!$stream->eof()) {
if ('' === ($byte = $stream->read(1))) {
return $buffer;
}
$buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached
if ($byte === "\n" || ++$size === $maxLength - 1) {
break;
}
}
return $buffer;
}
/**
* Create a new stream based on the input type.
*
* Options is an associative array that can contain the following keys:
* - metadata: Array of custom metadata.
* - size: Size of the stream.
*
* This method accepts the following `$resource` types:
* - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
* - `string`: Creates a stream object that uses the given string as the contents.
* - `resource`: Creates a stream object that wraps the given PHP stream resource.
* - `Iterator`: If the provided value implements `Iterator`, then a read-only
* stream object will be created that wraps the given iterable. Each time the
* stream is read from, data from the iterator will fill a buffer and will be
* continuously called until the buffer is equal to the requested read size.
* Subsequent read calls will first read from the buffer and then call `next`
* on the underlying iterator until it is exhausted.
* - `object` with `__toString()`: If the object has the `__toString()` method,
* the object will be cast to a string and then a stream will be returned that
* uses the string value.
* - `NULL`: When `null` is passed, an empty stream object is returned.
* - `callable` When a callable is passed, a read-only stream object will be
* created that invokes the given callable. The callable is invoked with the
* number of suggested bytes to read. The callable can return any number of
* bytes, but MUST return `false` when there is no more data to return. The
* stream object that wraps the callable will invoke the callable until the
* number of requested bytes are available. Any additional bytes will be
* buffered and used in subsequent reads.
*
* @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
* @param array{size?: int, metadata?: array} $options Additional options
*
* @throws \InvalidArgumentException if the $resource arg is not valid.
*/
public static function streamFor($resource = '', array $options = []): StreamInterface
{
if (is_scalar($resource)) {
$stream = self::tryFopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, (string) $resource);
fseek($stream, 0);
}
return new Stream($stream, $options);
}
switch (gettype($resource)) {
case 'resource':
/*
* The 'php://input' is a special stream with quirks and inconsistencies.
* We avoid using that stream by reading it into php://temp
*/
/** @var resource $resource */
if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') {
$stream = self::tryFopen('php://temp', 'w+');
fwrite($stream, stream_get_contents($resource));
fseek($stream, 0);
$resource = $stream;
}
return new Stream($resource, $options);
case 'object':
/** @var object $resource */
if ($resource instanceof StreamInterface) {
return $resource;
} elseif ($resource instanceof \Iterator) {
return new PumpStream(function () use ($resource) {
if (!$resource->valid()) {
return false;
}
$result = $resource->current();
$resource->next();
return $result;
}, $options);
} elseif (method_exists($resource, '__toString')) {
return self::streamFor((string) $resource, $options);
}
break;
case 'NULL':
return new Stream(self::tryFopen('php://temp', 'r+'), $options);
}
if (is_callable($resource)) {
return new PumpStream($resource, $options);
}
throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
}
/**
* Safely opens a PHP stream resource using a filename.
*
* When fopen fails, PHP normally raises a warning. This function adds an
* error handler that checks for errors and throws an exception instead.
*
* @param string $filename File to open
* @param string $mode Mode used to open the file
*
* @return resource
*
* @throws \RuntimeException if the file cannot be opened
*/
public static function tryFopen(string $filename, string $mode)
{
$ex = null;
set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool {
$ex = new \RuntimeException(sprintf(
'Unable to open "%s" using mode "%s": %s',
$filename,
$mode,
$errstr
));
return true;
});
try {
/** @var resource $handle */
$handle = fopen($filename, $mode);
} catch (\Throwable $e) {
$ex = new \RuntimeException(sprintf(
'Unable to open "%s" using mode "%s": %s',
$filename,
$mode,
$e->getMessage()
), 0, $e);
}
restore_error_handler();
if ($ex) {
/** @var $ex \RuntimeException */
throw $ex;
}
return $handle;
}
/**
* Returns a UriInterface for the given value.
*
* This function accepts a string or UriInterface and returns a
* UriInterface for the given value. If the value is already a
* UriInterface, it is returned as-is.
*
* @param string|UriInterface $uri
*
* @throws \InvalidArgumentException
*/
public static function uriFor($uri): UriInterface
{
if ($uri instanceof UriInterface) {
return $uri;
}
if (is_string($uri)) {
return new Uri($uri);
}
throw new \InvalidArgumentException('URI must be a string or UriInterface');
}
}

View file

@ -0,0 +1,9 @@
{
"require": {
"php": "^7.2.5 || ^8.0",
"friendsofphp/php-cs-fixer": "3.2.1"
},
"config": {
"preferred-install": "dist"
}
}

View file

@ -0,0 +1,10 @@
{
"require": {
"php": "^7.2.5 || ^8.0",
"phpstan/phpstan": "0.12.81",
"phpstan/phpstan-deprecation-rules": "0.12.6"
},
"config": {
"preferred-install": "dist"
}
}

View file

@ -0,0 +1,9 @@
{
"require": {
"php": "^7.2.5 || ^8.0",
"psalm/phar": "4.6.2"
},
"config": {
"preferred-install": "dist"
}
}

View file

@ -0,0 +1,3 @@
composer.lock
composer.phar
/vendor/

View file

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

View file

@ -0,0 +1,13 @@
Container interface
==============
This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url].
Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container.
The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
[psr-url]: https://www.php-fig.org/psr/psr-11/
[package-url]: https://packagist.org/packages/psr/container
[implementation-url]: https://packagist.org/providers/psr/container-implementation

View file

@ -0,0 +1,22 @@
{
"name": "psr/container",
"type": "library",
"description": "Common Container Interface (PHP FIG PSR-11)",
"keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"],
"homepage": "https://github.com/php-fig/container",
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"require": {
"php": ">=7.4.0"
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
}
}

View file

@ -0,0 +1,12 @@
<?php
namespace Psr\Container;
use Throwable;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface extends Throwable
{
}

View file

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get(string $id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has(string $id);
}

View file

@ -0,0 +1,10 @@
<?php
namespace Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}

View file

@ -0,0 +1,15 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[Makefile]
indent_style = tab

View file

@ -0,0 +1,2 @@
/vendor/
composer.lock

View file

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

View file

@ -0,0 +1,6 @@
PSR Event Dispatcher
====================
This repository holds the interfaces related to [PSR-14](http://www.php-fig.org/psr/psr-14/).
Note that this is not an Event Dispatcher implementation of its own. It is merely interfaces that describe the components of an Event Dispatcher. See the specification for more details.

View file

@ -0,0 +1,26 @@
{
"name": "psr/event-dispatcher",
"description": "Standard interfaces for event handling.",
"type": "library",
"keywords": ["psr", "psr-14", "events"],
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"require": {
"php": ">=7.2.0"
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Defines a dispatcher for events.
*/
interface EventDispatcherInterface
{
/**
* Provide all relevant listeners with an event to process.
*
* @param object $event
* The object to process.
*
* @return object
* The Event that was passed, now modified by listeners.
*/
public function dispatch(object $event);
}

View file

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Mapper from an event to the listeners that are applicable to that event.
*/
interface ListenerProviderInterface
{
/**
* @param object $event
* An event for which to return the relevant listeners.
* @return iterable[callable]
* An iterable (array, iterator, or generator) of callables. Each
* callable MUST be type-compatible with $event.
*/
public function getListenersForEvent(object $event) : iterable;
}

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* An Event whose processing may be interrupted when the event has been handled.
*
* A Dispatcher implementation MUST check to determine if an Event
* is marked as stopped after each listener is called. If it is then it should
* return immediately without calling any further Listeners.
*/
interface StoppableEventInterface
{
/**
* Is propagation stopped?
*
* This will typically only be used by the Dispatcher to determine if the
* previous listener halted propagation.
*
* @return bool
* True if the Event is complete and no further listeners should be called.
* False to continue calling listeners.
*/
public function isPropagationStopped() : bool;
}

View file

@ -0,0 +1,23 @@
# Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 1.0.1
Allow installation with PHP 8. No code changes.
## 1.0.0
First stable release. No changes since 0.3.0.
## 0.3.0
Added Interface suffix on exceptions
## 0.2.0
All exceptions are in `Psr\Http\Client` namespace
## 0.1.0
First release

View file

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

View file

@ -0,0 +1,12 @@
HTTP Client
===========
This repository holds all the common code related to [PSR-18 (HTTP Client)][psr-url].
Note that this is not a HTTP Client implementation of its own. It is merely abstractions that describe the components of a HTTP Client.
The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
[psr-url]: http://www.php-fig.org/psr/psr-18
[package-url]: https://packagist.org/packages/psr/http-client
[implementation-url]: https://packagist.org/providers/psr/http-client-implementation

View file

@ -0,0 +1,27 @@
{
"name": "psr/http-client",
"description": "Common interface for HTTP clients",
"keywords": ["psr", "psr-18", "http", "http-client"],
"homepage": "https://github.com/php-fig/http-client",
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0"
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace Psr\Http\Client;
/**
* Every HTTP client related exception MUST implement this interface.
*/
interface ClientExceptionInterface extends \Throwable
{
}

View file

@ -0,0 +1,20 @@
<?php
namespace Psr\Http\Client;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
interface ClientInterface
{
/**
* Sends a PSR-7 request and returns a PSR-7 response.
*
* @param RequestInterface $request
*
* @return ResponseInterface
*
* @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
*/
public function sendRequest(RequestInterface $request): ResponseInterface;
}

View file

@ -0,0 +1,24 @@
<?php
namespace Psr\Http\Client;
use Psr\Http\Message\RequestInterface;
/**
* Thrown when the request cannot be completed because of network issues.
*
* There is no response object as this exception is thrown when no response has been received.
*
* Example: the target host name can not be resolved or the connection failed.
*/
interface NetworkExceptionInterface extends ClientExceptionInterface
{
/**
* Returns the request.
*
* The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
*
* @return RequestInterface
*/
public function getRequest(): RequestInterface;
}

View file

@ -0,0 +1,24 @@
<?php
namespace Psr\Http\Client;
use Psr\Http\Message\RequestInterface;
/**
* Exception for when a request failed.
*
* Examples:
* - Request is invalid (e.g. method is missing)
* - Runtime request errors (e.g. the body stream is not seekable)
*/
interface RequestExceptionInterface extends ClientExceptionInterface
{
/**
* Returns the request.
*
* The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
*
* @return RequestInterface
*/
public function getRequest(): RequestInterface;
}

View file

@ -0,0 +1,2 @@
composer.lock
vendor/

View file

@ -0,0 +1,7 @@
extends: default
reviewers:
-
name: contributors
required: 1
teams:
- http-factory-contributors

View file

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

View file

@ -0,0 +1,10 @@
HTTP Factories
==============
This repository holds all interfaces related to [PSR-17 (HTTP Message Factories)][psr-17].
Please refer to the specification for a description.
You can find implementations of the specification by looking for packages providing the
[psr/http-factory-implementation](https://packagist.org/providers/psr/http-factory-implementation) virtual package.
[psr-17]: https://www.php-fig.org/psr/psr-17/

View file

@ -0,0 +1,35 @@
{
"name": "psr/http-factory",
"description": "Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"psr",
"psr-7",
"psr-17",
"http",
"factory",
"message",
"request",
"response"
],
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"require": {
"php": ">=7.0.0",
"psr/http-message": "^1.0"
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Psr\Http\Message;
interface RequestFactoryInterface
{
/**
* Create a new request.
*
* @param string $method The HTTP method associated with the request.
* @param UriInterface|string $uri The URI associated with the request. If
* the value is a string, the factory MUST create a UriInterface
* instance based on it.
*
* @return RequestInterface
*/
public function createRequest(string $method, $uri): RequestInterface;
}

View file

@ -0,0 +1,18 @@
<?php
namespace Psr\Http\Message;
interface ResponseFactoryInterface
{
/**
* Create a new response.
*
* @param int $code HTTP status code; defaults to 200
* @param string $reasonPhrase Reason phrase to associate with status code
* in generated response; if none is provided implementations MAY use
* the defaults as suggested in the HTTP specification.
*
* @return ResponseInterface
*/
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
}

View file

@ -0,0 +1,24 @@
<?php
namespace Psr\Http\Message;
interface ServerRequestFactoryInterface
{
/**
* Create a new server request.
*
* Note that server-params are taken precisely as given - no parsing/processing
* of the given values is performed, and, in particular, no attempt is made to
* determine the HTTP method or URI, which must be provided explicitly.
*
* @param string $method The HTTP method associated with the request.
* @param UriInterface|string $uri The URI associated with the request. If
* the value is a string, the factory MUST create a UriInterface
* instance based on it.
* @param array $serverParams Array of SAPI parameters with which to seed
* the generated request instance.
*
* @return ServerRequestInterface
*/
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
}

View file

@ -0,0 +1,45 @@
<?php
namespace Psr\Http\Message;
interface StreamFactoryInterface
{
/**
* Create a new stream from a string.
*
* The stream SHOULD be created with a temporary resource.
*
* @param string $content String content with which to populate the stream.
*
* @return StreamInterface
*/
public function createStream(string $content = ''): StreamInterface;
/**
* Create a stream from an existing file.
*
* The file MUST be opened using the given mode, which may be any mode
* supported by the `fopen` function.
*
* The `$filename` MAY be any string supported by `fopen()`.
*
* @param string $filename Filename or stream URI to use as basis of stream.
* @param string $mode Mode with which to open the underlying filename/stream.
*
* @return StreamInterface
* @throws \RuntimeException If the file cannot be opened.
* @throws \InvalidArgumentException If the mode is invalid.
*/
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
/**
* Create a new stream from an existing resource.
*
* The stream MUST be readable and may be writable.
*
* @param resource $resource PHP resource to use as basis of stream.
*
* @return StreamInterface
*/
public function createStreamFromResource($resource): StreamInterface;
}

View file

@ -0,0 +1,34 @@
<?php
namespace Psr\Http\Message;
interface UploadedFileFactoryInterface
{
/**
* Create a new uploaded file.
*
* If a size is not provided it will be determined by checking the size of
* the file.
*
* @see http://php.net/manual/features.file-upload.post-method.php
* @see http://php.net/manual/features.file-upload.errors.php
*
* @param StreamInterface $stream Underlying stream representing the
* uploaded file content.
* @param int $size in bytes
* @param int $error PHP file upload error
* @param string $clientFilename Filename as provided by the client, if any.
* @param string $clientMediaType Media type as provided by the client, if any.
*
* @return UploadedFileInterface
*
* @throws \InvalidArgumentException If the file resource is not readable.
*/
public function createUploadedFile(
StreamInterface $stream,
int $size = null,
int $error = \UPLOAD_ERR_OK,
string $clientFilename = null,
string $clientMediaType = null
): UploadedFileInterface;
}

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