pax_global_header 0000666 0000000 0000000 00000000064 13561010703 0014506 g ustar 00root root 0000000 0000000 52 comment=f51bca9de06b7a25b19a4155da7bebad099a5def contracts-1.1.8/ 0000775 0000000 0000000 00000000000 13561010703 0013515 5 ustar 00root root 0000000 0000000 contracts-1.1.8/.gitignore 0000664 0000000 0000000 00000000042 13561010703 0015501 0 ustar 00root root 0000000 0000000 composer.lock phpunit.xml vendor/ contracts-1.1.8/CHANGELOG.md 0000664 0000000 0000000 00000001552 13561010703 0015331 0 ustar 00root root 0000000 0000000 CHANGELOG ========= 1.1.0 ----- * added `HttpClient` namespace with contracts for implementing flexible HTTP clients * added `EventDispatcherInterface` and `Event` in namespace `EventDispatcher` * added `ServiceProviderInterface` in namespace `Service` 1.0.0 ----- * added `Service\ResetInterface` to provide a way to reset an object to its initial state * added `Translation\TranslatorInterface` and `Translation\TranslatorTrait` * added `Cache` contract to extend PSR-6 with tag invalidation, callback-based computation and stampede protection * added `Service\ServiceSubscriberInterface` to declare the dependencies of a class that consumes a service locator * added `Service\ServiceSubscriberTrait` to implement `Service\ServiceSubscriberInterface` using methods' return types * added `Service\ServiceLocatorTrait` to help implement PSR-11 service locators contracts-1.1.8/Cache/ 0000775 0000000 0000000 00000000000 13561010703 0014520 5 ustar 00root root 0000000 0000000 contracts-1.1.8/Cache/.gitignore 0000664 0000000 0000000 00000000042 13561010703 0016504 0 ustar 00root root 0000000 0000000 vendor/ composer.lock phpunit.xml contracts-1.1.8/Cache/CacheInterface.php 0000664 0000000 0000000 00000004605 13561010703 0020062 0 ustar 00root root 0000000 0000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Cache; use Psr\Cache\CacheItemInterface; use Psr\Cache\InvalidArgumentException; /** * Covers most simple to advanced caching needs. * * @author Nicolas Grekas
*/ interface CacheInterface { /** * Fetches a value from the pool or computes it if not found. * * On cache misses, a callback is called that should return the missing value. * This callback is given a PSR-6 CacheItemInterface instance corresponding to the * requested key, that could be used e.g. for expiration control. It could also * be an ItemInterface instance when its additional features are needed. * * @param string $key The key of the item to retrieve from the cache * @param callable|CallbackInterface $callback Should return the computed value for the given key/item * @param float|null $beta A float that, as it grows, controls the likeliness of triggering * early expiration. 0 disables it, INF forces immediate expiration. * The default (or providing null) is implementation dependent but should * typically be 1.0, which should provide optimal stampede protection. * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} * * @return mixed The value corresponding to the provided key * * @throws InvalidArgumentException When $key is not valid or when $beta is negative */ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null); /** * Removes an item from the pool. * * @param string $key The key to delete * * @throws InvalidArgumentException When $key is not valid * * @return bool True if the item was successfully removed, false if there was any error */ public function delete(string $key): bool; } contracts-1.1.8/Cache/CacheTrait.php 0000664 0000000 0000000 00000004627 13561010703 0017251 0 ustar 00root root 0000000 0000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Cache; use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\InvalidArgumentException; use Psr\Log\LoggerInterface; /** * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. * * @author Nicolas Grekas
*/ trait CacheTrait { /** * {@inheritdoc} */ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) { return $this->doGet($this, $key, $callback, $beta, $metadata); } /** * {@inheritdoc} */ public function delete(string $key): bool { return $this->deleteItem($key); } private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null) { if (0 > $beta = $beta ?? 1.0) { throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta)) extends \InvalidArgumentException implements InvalidArgumentException { }; } $item = $pool->getItem($key); $recompute = !$item->isHit() || INF === $beta; $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; if (!$recompute && $metadata) { $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false; $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false; if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX)) { // force applying defaultLifetime to expiry $item->expiresAt(null); $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ 'key' => $key, 'delta' => sprintf('%.1f', $expiry - $now), ]); } } if ($recompute) { $save = true; $item->set($callback($item, $save)); if ($save) { $pool->save($item); } } return $item->get(); } } contracts-1.1.8/Cache/CallbackInterface.php 0000664 0000000 0000000 00000001453 13561010703 0020551 0 ustar 00root root 0000000 0000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Cache; use Psr\Cache\CacheItemInterface; /** * Computes and returns the cached value of an item. * * @author Nicolas Grekas
*/ interface CallbackInterface { /** * @param CacheItemInterface|ItemInterface $item The item to compute the value for * @param bool &$save Should be set to false when the value should not be saved in the pool * * @return mixed The computed value for the passed item */ public function __invoke(CacheItemInterface $item, bool &$save); } contracts-1.1.8/Cache/ItemInterface.php 0000664 0000000 0000000 00000003320 13561010703 0017746 0 ustar 00root root 0000000 0000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Cache; use Psr\Cache\CacheException; use Psr\Cache\CacheItemInterface; use Psr\Cache\InvalidArgumentException; /** * Augments PSR-6's CacheItemInterface with support for tags and metadata. * * @author Nicolas Grekas
*/ interface ItemInterface extends CacheItemInterface { /** * References the Unix timestamp stating when the item will expire. */ const METADATA_EXPIRY = 'expiry'; /** * References the time the item took to be created, in milliseconds. */ const METADATA_CTIME = 'ctime'; /** * References the list of tags that were assigned to the item, as string[]. */ const METADATA_TAGS = 'tags'; /** * Reserved characters that cannot be used in a key or tag. */ const RESERVED_CHARACTERS = '{}()/\@:'; /** * Adds a tag to a cache item. * * Tags are strings that follow the same validation rules as keys. * * @param string|string[] $tags A tag or array of tags * * @return $this * * @throws InvalidArgumentException When $tag is not valid * @throws CacheException When the item comes from a pool that is not tag-aware */ public function tag($tags): self; /** * Returns a list of metadata info that were saved alongside with the cached value. * * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. */ public function getMetadata(): array; } contracts-1.1.8/Cache/LICENSE 0000664 0000000 0000000 00000002051 13561010703 0015523 0 ustar 00root root 0000000 0000000 Copyright (c) 2018-2019 Fabien Potencier 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. contracts-1.1.8/Cache/README.md 0000664 0000000 0000000 00000000512 13561010703 0015775 0 ustar 00root root 0000000 0000000 Symfony Cache Contracts ======================= A set of abstractions extracted out of the Symfony components. Can be used to build on semantics that the Symfony components proved useful - and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/master/README.md for more information. contracts-1.1.8/Cache/TagAwareCacheInterface.php 0000664 0000000 0000000 00000001755 13561010703 0021501 0 ustar 00root root 0000000 0000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Cache; use Psr\Cache\InvalidArgumentException; /** * Allows invalidating cached items using tags. * * @author Nicolas Grekas
*/
interface TagAwareCacheInterface extends CacheInterface
{
/**
* Invalidates cached items using tags.
*
* When implemented on a PSR-6 pool, invalidation should not apply
* to deferred items. Instead, they should be committed as usual.
* This allows replacing old tagged values by new ones without
* race conditions.
*
* @param string[] $tags An array of tags to invalidate
*
* @return bool True on success
*
* @throws InvalidArgumentException When $tags is not valid
*/
public function invalidateTags(array $tags);
}
contracts-1.1.8/Cache/composer.json 0000664 0000000 0000000 00000001566 13561010703 0017252 0 ustar 00root root 0000000 0000000 {
"name": "symfony/cache-contracts",
"type": "library",
"description": "Generic abstractions related to caching",
"keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.1.3",
"psr/cache": "^1.0"
},
"suggest": {
"symfony/cache-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\Cache\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
}
}
contracts-1.1.8/EventDispatcher/ 0000775 0000000 0000000 00000000000 13561010703 0016605 5 ustar 00root root 0000000 0000000 contracts-1.1.8/EventDispatcher/.gitignore 0000664 0000000 0000000 00000000042 13561010703 0020571 0 ustar 00root root 0000000 0000000 vendor/
composer.lock
phpunit.xml
contracts-1.1.8/EventDispatcher/Event.php 0000664 0000000 0000000 00000006046 13561010703 0020405 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
if (interface_exists(StoppableEventInterface::class)) {
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco
*/
class Event implements StoppableEventInterface
{
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*/
public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}
} else {
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco
*/
class Event
{
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*/
public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}
}
contracts-1.1.8/EventDispatcher/EventDispatcherInterface.php 0000664 0000000 0000000 00000004371 13561010703 0024234 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\EventDispatcher;
use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface;
if (interface_exists(PsrEventDispatcherInterface::class)) {
/**
* Allows providing hooks on domain-specific lifecycles by dispatching events.
*/
interface EventDispatcherInterface extends PsrEventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* For BC with Symfony 4, the $eventName argument is not declared explicitly on the
* signature of the method. Implementations that are not bound by this BC constraint
* MUST declare it explicitly, as allowed by PHP.
*
* @param object $event The event to pass to the event handlers/listeners
* @param string|null $eventName The name of the event to dispatch. If not supplied,
* the class of $event should be used instead.
*
* @return object The passed $event MUST be returned
*/
public function dispatch($event/*, string $eventName = null*/);
}
} else {
/**
* Allows providing hooks on domain-specific lifecycles by dispatching events.
*/
interface EventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* For BC with Symfony 4, the $eventName argument is not declared explicitly on the
* signature of the method. Implementations that are not bound by this BC constraint
* MUST declare it explicitly, as allowed by PHP.
*
* @param object $event The event to pass to the event handlers/listeners
* @param string|null $eventName The name of the event to dispatch. If not supplied,
* the class of $event should be used instead.
*
* @return object The passed $event MUST be returned
*/
public function dispatch($event/*, string $eventName = null*/);
}
}
contracts-1.1.8/EventDispatcher/LICENSE 0000664 0000000 0000000 00000002051 13561010703 0017610 0 ustar 00root root 0000000 0000000 Copyright (c) 2018-2019 Fabien Potencier
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.
contracts-1.1.8/EventDispatcher/README.md 0000664 0000000 0000000 00000000536 13561010703 0020070 0 ustar 00root root 0000000 0000000 Symfony EventDispatcher Contracts
=================================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/master/README.md for more information.
contracts-1.1.8/EventDispatcher/composer.json 0000664 0000000 0000000 00000001647 13561010703 0021337 0 ustar 00root root 0000000 0000000 {
"name": "symfony/event-dispatcher-contracts",
"type": "library",
"description": "Generic abstractions related to dispatching event",
"keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.1.3"
},
"suggest": {
"psr/event-dispatcher": "",
"symfony/event-dispatcher-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
}
}
contracts-1.1.8/HttpClient/ 0000775 0000000 0000000 00000000000 13561010703 0015573 5 ustar 00root root 0000000 0000000 contracts-1.1.8/HttpClient/.gitignore 0000664 0000000 0000000 00000000042 13561010703 0017557 0 ustar 00root root 0000000 0000000 vendor/
composer.lock
phpunit.xml
contracts-1.1.8/HttpClient/ChunkInterface.php 0000664 0000000 0000000 00000004033 13561010703 0021175 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
/**
* The interface of chunks returned by ResponseStreamInterface::current().
*
* When the chunk is first, last or timeout, the content MUST be empty.
* When an unchecked timeout or a network error occurs, a TransportExceptionInterface
* MUST be thrown by the destructor unless one was already thrown by another method.
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface ChunkInterface
{
/**
* Tells when the idle timeout has been reached.
*
* @throws TransportExceptionInterface on a network error
*/
public function isTimeout(): bool;
/**
* Tells when headers just arrived.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function isFirst(): bool;
/**
* Tells when the body just completed.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function isLast(): bool;
/**
* Returns a [status code, headers] tuple when a 1xx status code was just received.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function getInformationalStatus(): ?array;
/**
* Returns the content of the response chunk.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function getContent(): string;
/**
* Returns the offset of the chunk in the response body.
*/
public function getOffset(): int;
/**
* In case of error, returns the message that describes it.
*/
public function getError(): ?string;
}
contracts-1.1.8/HttpClient/Exception/ 0000775 0000000 0000000 00000000000 13561010703 0017531 5 ustar 00root root 0000000 0000000 contracts-1.1.8/HttpClient/Exception/ClientExceptionInterface.php 0000664 0000000 0000000 00000000733 13561010703 0025163 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* When a 4xx response is returned.
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface ClientExceptionInterface extends HttpExceptionInterface
{
}
contracts-1.1.8/HttpClient/Exception/DecodingExceptionInterface.php 0000664 0000000 0000000 00000000776 13561010703 0025470 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* When a content-type cannot be decoded to the expected representation.
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface DecodingExceptionInterface extends ExceptionInterface
{
}
contracts-1.1.8/HttpClient/Exception/ExceptionInterface.php 0000664 0000000 0000000 00000000737 13561010703 0024030 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* The base interface for all exceptions in the contract.
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface ExceptionInterface extends \Throwable
{
}
contracts-1.1.8/HttpClient/Exception/HttpExceptionInterface.php 0000664 0000000 0000000 00000001123 13561010703 0024656 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
use Symfony\Contracts\HttpClient\ResponseInterface;
/**
* Base interface for HTTP-related exceptions.
*
* @author Anton Chernikov
*
* @experimental in 1.1
*/
interface RedirectionExceptionInterface extends HttpExceptionInterface
{
}
contracts-1.1.8/HttpClient/Exception/ServerExceptionInterface.php 0000664 0000000 0000000 00000000733 13561010703 0025213 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* When a 5xx response is returned.
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface ServerExceptionInterface extends HttpExceptionInterface
{
}
contracts-1.1.8/HttpClient/Exception/TransportExceptionInterface.php 0000664 0000000 0000000 00000000750 13561010703 0025740 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* When any error happens at the transport level.
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface TransportExceptionInterface extends ExceptionInterface
{
}
contracts-1.1.8/HttpClient/HttpClientInterface.php 0000664 0000000 0000000 00000013205 13561010703 0022204 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\Test\HttpClientTestCase;
/**
* Provides flexible methods for requesting HTTP resources synchronously or asynchronously.
*
* @see HttpClientTestCase for a reference test suite
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface HttpClientInterface
{
public const OPTIONS_DEFAULTS = [
'auth_basic' => null, // array|string - an array containing the username as first value, and optionally the
// password as the second one; or string like username:password - enabling HTTP Basic
// authentication (RFC 7617)
'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750)
'query' => [], // string[] - associative array of query string values to merge with the request's URL
'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of values
'body' => '', // array|string|resource|\Traversable|\Closure - the callback SHOULD yield a string
// smaller than the amount requested as argument; the empty string signals EOF; if
// an array is passed, it is meant as a form payload of field names and values
'json' => null, // mixed - if set, implementations MUST set the "body" option to the JSON-encoded
// value and set the "content-type" header to a JSON-compatible value if it is not
// explicitly defined in the headers option - typically "application/json"
'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) that
// MUST be available via $response->getInfo('user_data') - not used internally
'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower than or equal to 0
// means redirects should not be followed; "Authorization" and "Cookie" headers MUST
// NOT follow except for the initial host name
'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0
'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2
'buffer' => true, // bool|resource|\Closure - whether the content of the response should be buffered or not,
// or a stream resource where the response body should be written,
// or a closure telling if/where the response should be buffered based on its headers
'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort
// the request; it MUST be called on DNS resolution, on arrival of headers and on
// completion; it SHOULD be called on upload/download of data and at least 1/s
'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
'timeout' => null, // float - the idle timeout - defaults to ini_get('default_socket_timeout')
'max_duration' => 0, // float - the maximum execution time for the request+response as a whole;
// a value lower than or equal to 0 means it is unlimited
'bindto' => '0', // string - the interface or the local socket to bind to
'verify_peer' => true, // see https://php.net/context.ssl for the following options
'verify_host' => true,
'cafile' => null,
'capath' => null,
'local_cert' => null,
'local_pk' => null,
'passphrase' => null,
'ciphers' => null,
'peer_fingerprint' => null,
'capture_peer_cert_chain' => false,
'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options
];
/**
* Requests an HTTP resource.
*
* Responses MUST be lazy, but their status code MUST be
* checked even if none of their public methods are called.
*
* Implementations are not required to support all options described above; they can also
* support more custom options; but in any case, they MUST throw a TransportExceptionInterface
* when an unsupported option is passed.
*
* @throws TransportExceptionInterface When an unsupported option is passed
*/
public function request(string $method, string $url, array $options = []): ResponseInterface;
/**
* Yields responses chunk by chunk as they complete.
*
* @param ResponseInterface|ResponseInterface[]|iterable $responses One or more responses created by the current HTTP client
* @param float|null $timeout The idle timeout before yielding timeout chunks
*/
public function stream($responses, float $timeout = null): ResponseStreamInterface;
}
contracts-1.1.8/HttpClient/LICENSE 0000664 0000000 0000000 00000002051 13561010703 0016576 0 ustar 00root root 0000000 0000000 Copyright (c) 2018-2019 Fabien Potencier
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.
contracts-1.1.8/HttpClient/README.md 0000664 0000000 0000000 00000000524 13561010703 0017053 0 ustar 00root root 0000000 0000000 Symfony HttpClient Contracts
============================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/master/README.md for more information.
contracts-1.1.8/HttpClient/ResponseInterface.php 0000664 0000000 0000000 00000011457 13561010703 0021733 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
/**
* A (lazily retrieved) HTTP response.
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface ResponseInterface
{
/**
* Gets the HTTP status code of the response.
*
* @throws TransportExceptionInterface when a network error occurs
*/
public function getStatusCode(): int;
/**
* Gets the HTTP headers of the response.
*
* @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes
*
* @return string[][] The headers of the response keyed by header names in lowercase
*
* @throws TransportExceptionInterface When a network error occurs
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
* @throws ClientExceptionInterface On a 4xx when $throw is true
* @throws ServerExceptionInterface On a 5xx when $throw is true
*/
public function getHeaders(bool $throw = true): array;
/**
* Gets the response body as a string.
*
* @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes
*
* @throws TransportExceptionInterface When a network error occurs
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
* @throws ClientExceptionInterface On a 4xx when $throw is true
* @throws ServerExceptionInterface On a 5xx when $throw is true
*/
public function getContent(bool $throw = true): string;
/**
* Gets the response body decoded as array, typically from a JSON payload.
*
* @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes
*
* @throws DecodingExceptionInterface When the body cannot be decoded to an array
* @throws TransportExceptionInterface When a network error occurs
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
* @throws ClientExceptionInterface On a 4xx when $throw is true
* @throws ServerExceptionInterface On a 5xx when $throw is true
*/
public function toArray(bool $throw = true): array;
/**
* Closes the response stream and all related buffers.
*
* No further chunk will be yielded after this method has been called.
*/
public function cancel(): void;
/**
* Returns info coming from the transport layer.
*
* This method SHOULD NOT throw any ExceptionInterface and SHOULD be non-blocking.
* The returned info is "live": it can be empty and can change from one call to
* another, as the request/response progresses.
*
* The following info MUST be returned:
* - canceled (bool) - true if the response was canceled using ResponseInterface::cancel(), false otherwise
* - error (string|null) - the error message when the transfer was aborted, null otherwise
* - http_code (int) - the last response code or 0 when it is not known yet
* - http_method (string) - the HTTP verb of the last request
* - redirect_count (int) - the number of redirects followed while executing the request
* - redirect_url (string|null) - the resolved location of redirect responses, null otherwise
* - response_headers (array) - an array modelled after the special $http_response_header variable
* - start_time (float) - the time when the request was sent or 0.0 when it's pending
* - url (string) - the last effective URL of the request
* - user_data (mixed|null) - the value of the "user_data" request option, null if not set
*
* When the "capture_peer_cert_chain" option is true, the "peer_certificate_chain"
* attribute SHOULD list the peer certificates as an array of OpenSSL X.509 resources.
*
* Other info SHOULD be named after curl_getinfo()'s associative return value.
*
* @return array|mixed|null An array of all available info, or one of them when $type is
* provided, or null when an unsupported type is requested
*/
public function getInfo(string $type = null);
}
contracts-1.1.8/HttpClient/ResponseStreamInterface.php 0000664 0000000 0000000 00000001103 13561010703 0023072 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient;
/**
* Yields response chunks, returned by HttpClientInterface::stream().
*
* @author Nicolas Grekas
*
* @experimental in 1.1
*/
interface ResponseStreamInterface extends \Iterator
{
public function key(): ResponseInterface;
public function current(): ChunkInterface;
}
contracts-1.1.8/HttpClient/Test/ 0000775 0000000 0000000 00000000000 13561010703 0016512 5 ustar 00root root 0000000 0000000 contracts-1.1.8/HttpClient/Test/Fixtures/ 0000775 0000000 0000000 00000000000 13561010703 0020323 5 ustar 00root root 0000000 0000000 contracts-1.1.8/HttpClient/Test/Fixtures/web/ 0000775 0000000 0000000 00000000000 13561010703 0021100 5 ustar 00root root 0000000 0000000 contracts-1.1.8/HttpClient/Test/Fixtures/web/index.php 0000664 0000000 0000000 00000007664 13561010703 0022735 0 ustar 00root root 0000000 0000000 $v) {
switch ($k) {
default:
if (0 !== strpos($k, 'HTTP_')) {
continue 2;
}
// no break
case 'SERVER_NAME':
case 'SERVER_PROTOCOL':
case 'REQUEST_URI':
case 'REQUEST_METHOD':
case 'PHP_AUTH_USER':
case 'PHP_AUTH_PW':
$vars[$k] = $v;
}
}
$json = json_encode($vars, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
switch ($vars['REQUEST_URI']) {
default:
exit;
case '/head':
header('Content-Length: '.strlen($json), true);
break;
case '/':
case '/?a=a&b=b':
case 'http://127.0.0.1:8057/':
case 'http://localhost:8057/':
ob_start('ob_gzhandler');
break;
case '/103':
header('HTTP/1.1 103 Early Hints');
header('Link: ; rel=preload; as=style', false);
header('Link: ; rel=preload; as=script', false);
flush();
usleep(1000);
echo "HTTP/1.1 200 OK\r\n";
echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n";
echo "Content-Length: 13\r\n";
echo "\r\n";
echo 'Here the body';
exit;
case '/404':
header('Content-Type: application/json', true, 404);
break;
case '/301':
if ('Basic Zm9vOmJhcg==' === $vars['HTTP_AUTHORIZATION']) {
header('Location: http://127.0.0.1:8057/302', true, 301);
}
break;
case '/301/bad-tld':
header('Location: http://foo.example.', true, 301);
break;
case '/301/invalid':
header('Location: //?foo=bar', true, 301);
break;
case '/302':
if (!isset($vars['HTTP_AUTHORIZATION'])) {
header('Location: http://localhost:8057/', true, 302);
}
break;
case '/302/relative':
header('Location: ..', true, 302);
break;
case '/304':
header('Content-Length: 10', true, 304);
echo '12345';
return;
case '/307':
header('Location: http://localhost:8057/post', true, 307);
break;
case '/length-broken':
header('Content-Length: 1000');
break;
case '/post':
$output = json_encode($_POST + ['REQUEST_METHOD' => $vars['REQUEST_METHOD']], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
header('Content-Type: application/json', true);
header('Content-Length: '.strlen($output));
echo $output;
exit;
case '/timeout-header':
usleep(300000);
break;
case '/timeout-body':
echo '<1>';
@ob_flush();
flush();
usleep(500000);
echo '<2>';
exit;
case '/timeout-long':
ignore_user_abort(false);
sleep(1);
while (true) {
echo '<1>';
@ob_flush();
flush();
usleep(500);
}
exit;
case '/chunked':
header('Transfer-Encoding: chunked');
echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\nesome!\r\n0\r\n\r\n";
exit;
case '/chunked-broken':
header('Transfer-Encoding: chunked');
echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\ne";
exit;
case '/gzip-broken':
header('Content-Encoding: gzip');
echo str_repeat('-', 1000);
exit;
case '/max-duration':
ignore_user_abort(false);
while (true) {
echo '<1>';
@ob_flush();
flush();
usleep(500);
}
exit;
}
header('Content-Type: application/json', true);
echo $json;
contracts-1.1.8/HttpClient/Test/HttpClientTestCase.php 0000664 0000000 0000000 00000074734 13561010703 0022754 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* A reference test suite for HttpClientInterface implementations.
*
* @experimental in 1.1
*/
abstract class HttpClientTestCase extends TestCase
{
private static $server;
public static function setUpBeforeClass(): void
{
TestHttpServer::start();
}
abstract protected function getHttpClient(string $testCase): HttpClientInterface;
public function testGetRequest()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'headers' => ['Foo' => 'baR'],
'user_data' => $data = new \stdClass(),
]);
$this->assertSame([], $response->getInfo('response_headers'));
$this->assertSame($data, $response->getInfo()['user_data']);
$this->assertSame(200, $response->getStatusCode());
$info = $response->getInfo();
$this->assertNull($info['error']);
$this->assertSame(0, $info['redirect_count']);
$this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]);
$this->assertSame('Host: localhost:8057', $info['response_headers'][1]);
$this->assertSame('http://localhost:8057/', $info['url']);
$headers = $response->getHeaders();
$this->assertSame('localhost:8057', $headers['host'][0]);
$this->assertSame(['application/json'], $headers['content-type']);
$body = json_decode($response->getContent(), true);
$this->assertSame($body, $response->toArray());
$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
$this->assertSame('/', $body['REQUEST_URI']);
$this->assertSame('GET', $body['REQUEST_METHOD']);
$this->assertSame('localhost:8057', $body['HTTP_HOST']);
$this->assertSame('baR', $body['HTTP_FOO']);
$response = $client->request('GET', 'http://localhost:8057/length-broken');
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testHeadRequest()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('HEAD', 'http://localhost:8057/head', [
'headers' => ['Foo' => 'baR'],
'user_data' => $data = new \stdClass(),
'buffer' => false,
]);
$this->assertSame([], $response->getInfo('response_headers'));
$this->assertSame(200, $response->getStatusCode());
$info = $response->getInfo();
$this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]);
$this->assertSame('Host: localhost:8057', $info['response_headers'][1]);
$headers = $response->getHeaders();
$this->assertSame('localhost:8057', $headers['host'][0]);
$this->assertSame(['application/json'], $headers['content-type']);
$this->assertTrue(0 < $headers['content-length'][0]);
$this->assertSame('', $response->getContent());
}
public function testNonBufferedGetRequest()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'buffer' => false,
'headers' => ['Foo' => 'baR'],
]);
$body = $response->toArray();
$this->assertSame('baR', $body['HTTP_FOO']);
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testBufferSink()
{
$sink = fopen('php://temp', 'w+');
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'buffer' => $sink,
'headers' => ['Foo' => 'baR'],
]);
$body = $response->toArray();
$this->assertSame('baR', $body['HTTP_FOO']);
rewind($sink);
$sink = stream_get_contents($sink);
$this->assertSame($sink, $response->getContent());
}
public function testConditionalBuffering()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057');
$firstContent = $response->getContent();
$secondContent = $response->getContent();
$this->assertSame($firstContent, $secondContent);
$response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () { return false; }]);
$response->getContent();
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testReentrantBufferCallback()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () use (&$response) {
$response->cancel();
return true;
}]);
$this->assertSame(200, $response->getStatusCode());
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testThrowingBufferCallback()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () {
throw new \Exception('Boo');
}]);
$this->assertSame(200, $response->getStatusCode());
$this->expectException(TransportExceptionInterface::class);
$this->expectExceptionMessage('Boo');
$response->getContent();
}
public function testUnsupportedOption()
{
$client = $this->getHttpClient(__FUNCTION__);
$this->expectException(\InvalidArgumentException::class);
$client->request('GET', 'http://localhost:8057', [
'capture_peer_cert' => 1.0,
]);
}
public function testHttpVersion()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'http_version' => 1.0,
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('HTTP/1.0 200 OK', $response->getInfo('response_headers')[0]);
$body = $response->toArray();
$this->assertSame('HTTP/1.0', $body['SERVER_PROTOCOL']);
$this->assertSame('GET', $body['REQUEST_METHOD']);
$this->assertSame('/', $body['REQUEST_URI']);
}
public function testChunkedEncoding()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/chunked');
$this->assertSame(['chunked'], $response->getHeaders()['transfer-encoding']);
$this->assertSame('Symfony is awesome!', $response->getContent());
$response = $client->request('GET', 'http://localhost:8057/chunked-broken');
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testClientError()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/404');
$client->stream($response)->valid();
$this->assertSame(404, $response->getInfo('http_code'));
try {
$response->getHeaders();
$this->fail(ClientExceptionInterface::class.' expected');
} catch (ClientExceptionInterface $e) {
}
try {
$response->getContent();
$this->fail(ClientExceptionInterface::class.' expected');
} catch (ClientExceptionInterface $e) {
}
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['application/json'], $response->getHeaders(false)['content-type']);
$this->assertNotEmpty($response->getContent(false));
$response = $client->request('GET', 'http://localhost:8057/404');
try {
foreach ($client->stream($response) as $chunk) {
$this->assertTrue($chunk->isFirst());
}
$this->fail(ClientExceptionInterface::class.' expected');
} catch (ClientExceptionInterface $e) {
}
}
public function testIgnoreErrors()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/404');
$this->assertSame(404, $response->getStatusCode());
}
public function testDnsError()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/301/bad-tld');
try {
$response->getStatusCode();
$this->fail(TransportExceptionInterface::class.' expected');
} catch (TransportExceptionInterface $e) {
$this->addToAssertionCount(1);
}
try {
$response->getStatusCode();
$this->fail(TransportExceptionInterface::class.' still expected');
} catch (TransportExceptionInterface $e) {
$this->addToAssertionCount(1);
}
$response = $client->request('GET', 'http://localhost:8057/301/bad-tld');
try {
foreach ($client->stream($response) as $r => $chunk) {
}
$this->fail(TransportExceptionInterface::class.' expected');
} catch (TransportExceptionInterface $e) {
$this->addToAssertionCount(1);
}
$this->assertSame($response, $r);
$this->assertNotNull($chunk->getError());
$this->expectException(TransportExceptionInterface::class);
foreach ($client->stream($response) as $chunk) {
}
}
public function testInlineAuth()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://foo:bar%3Dbar@localhost:8057');
$body = $response->toArray();
$this->assertSame('foo', $body['PHP_AUTH_USER']);
$this->assertSame('bar=bar', $body['PHP_AUTH_PW']);
}
public function testBadRequestBody()
{
$client = $this->getHttpClient(__FUNCTION__);
$this->expectException(TransportExceptionInterface::class);
$response = $client->request('POST', 'http://localhost:8057/', [
'body' => function () { yield []; },
]);
$response->getStatusCode();
}
public function test304()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/304', [
'headers' => ['If-Match' => '"abc"'],
'buffer' => false,
]);
$this->assertSame(304, $response->getStatusCode());
$this->assertSame('', $response->getContent(false));
}
public function testRedirects()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/301', [
'auth_basic' => 'foo:bar',
'body' => function () {
yield 'foo=bar';
},
]);
$body = $response->toArray();
$this->assertSame('GET', $body['REQUEST_METHOD']);
$this->assertSame('Basic Zm9vOmJhcg==', $body['HTTP_AUTHORIZATION']);
$this->assertSame('http://localhost:8057/', $response->getInfo('url'));
$this->assertSame(2, $response->getInfo('redirect_count'));
$this->assertNull($response->getInfo('redirect_url'));
$expected = [
'HTTP/1.1 301 Moved Permanently',
'Location: http://127.0.0.1:8057/302',
'Content-Type: application/json',
'HTTP/1.1 302 Found',
'Location: http://localhost:8057/',
'Content-Type: application/json',
'HTTP/1.1 200 OK',
'Content-Type: application/json',
];
$filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) {
return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true) && 'Content-Encoding: gzip' !== $h;
}));
$this->assertSame($expected, $filteredHeaders);
}
public function testInvalidRedirect()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/301/invalid');
$this->assertSame(301, $response->getStatusCode());
$this->assertSame(['//?foo=bar'], $response->getHeaders(false)['location']);
$this->assertSame(0, $response->getInfo('redirect_count'));
$this->assertNull($response->getInfo('redirect_url'));
$this->expectException(RedirectionExceptionInterface::class);
$response->getHeaders();
}
public function testRelativeRedirects()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/302/relative');
$body = $response->toArray();
$this->assertSame('/', $body['REQUEST_URI']);
$this->assertNull($response->getInfo('redirect_url'));
$response = $client->request('GET', 'http://localhost:8057/302/relative', [
'max_redirects' => 0,
]);
$this->assertSame(302, $response->getStatusCode());
$this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url'));
}
public function testRedirect307()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/307', [
'body' => function () {
yield 'foo=bar';
},
'max_redirects' => 0,
]);
$this->assertSame(307, $response->getStatusCode());
$response = $client->request('POST', 'http://localhost:8057/307', [
'body' => 'foo=bar',
]);
$body = $response->toArray();
$this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body);
}
public function testMaxRedirects()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/301', [
'max_redirects' => 1,
'auth_basic' => 'foo:bar',
]);
try {
$response->getHeaders();
$this->fail(RedirectionExceptionInterface::class.' expected');
} catch (RedirectionExceptionInterface $e) {
}
$this->assertSame(302, $response->getStatusCode());
$this->assertSame(1, $response->getInfo('redirect_count'));
$this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url'));
$expected = [
'HTTP/1.1 301 Moved Permanently',
'Location: http://127.0.0.1:8057/302',
'Content-Type: application/json',
'HTTP/1.1 302 Found',
'Location: http://localhost:8057/',
'Content-Type: application/json',
];
$filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) {
return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true);
}));
$this->assertSame($expected, $filteredHeaders);
}
public function testStream()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057');
$chunks = $client->stream($response);
$result = [];
foreach ($chunks as $r => $chunk) {
if ($chunk->isTimeout()) {
$result[] = 't';
} elseif ($chunk->isLast()) {
$result[] = 'l';
} elseif ($chunk->isFirst()) {
$result[] = 'f';
}
}
$this->assertSame($response, $r);
$this->assertSame(['f', 'l'], $result);
$chunk = null;
$i = 0;
foreach ($client->stream($response) as $chunk) {
++$i;
}
$this->assertSame(1, $i);
$this->assertTrue($chunk->isLast());
}
public function testAddToStream()
{
$client = $this->getHttpClient(__FUNCTION__);
$r1 = $client->request('GET', 'http://localhost:8057');
$completed = [];
$pool = [$r1];
while ($pool) {
$chunks = $client->stream($pool);
$pool = [];
foreach ($chunks as $r => $chunk) {
if (!$chunk->isLast()) {
continue;
}
if ($r1 === $r) {
$r2 = $client->request('GET', 'http://localhost:8057');
$pool[] = $r2;
}
$completed[] = $r;
}
}
$this->assertSame([$r1, $r2], $completed);
}
public function testCompleteTypeError()
{
$client = $this->getHttpClient(__FUNCTION__);
$this->expectException(\TypeError::class);
$client->stream(123);
}
public function testOnProgress()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'headers' => ['Content-Length' => 14],
'body' => 'foo=0123456789',
'on_progress' => function (...$state) use (&$steps) { $steps[] = $state; },
]);
$body = $response->toArray();
$this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body);
$this->assertSame([0, 0], \array_slice($steps[0], 0, 2));
$lastStep = \array_slice($steps, -1)[0];
$this->assertSame([57, 57], \array_slice($lastStep, 0, 2));
$this->assertSame('http://localhost:8057/post', $steps[0][2]['url']);
}
public function testPostJson()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'json' => ['foo' => 'bar'],
]);
$body = $response->toArray();
$this->assertStringContainsString('json', $body['content-type']);
unset($body['content-type']);
$this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body);
}
public function testPostArray()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'body' => ['foo' => 'bar'],
]);
$this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $response->toArray());
}
public function testPostResource()
{
$client = $this->getHttpClient(__FUNCTION__);
$h = fopen('php://temp', 'w+');
fwrite($h, 'foo=0123456789');
rewind($h);
$response = $client->request('POST', 'http://localhost:8057/post', [
'body' => $h,
]);
$body = $response->toArray();
$this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body);
}
public function testPostCallback()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'body' => function () {
yield 'foo';
yield '';
yield '=';
yield '0123456789';
},
]);
$this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $response->toArray());
}
public function testCancel()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-header');
$response->cancel();
$this->expectException(TransportExceptionInterface::class);
$response->getHeaders();
}
public function testInfoOnCanceledResponse()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-header');
$this->assertFalse($response->getInfo('canceled'));
$response->cancel();
$this->assertTrue($response->getInfo('canceled'));
}
public function testCancelInStream()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/404');
foreach ($client->stream($response) as $chunk) {
$response->cancel();
}
$this->expectException(TransportExceptionInterface::class);
foreach ($client->stream($response) as $chunk) {
}
}
public function testOnProgressCancel()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body', [
'on_progress' => function ($dlNow) {
if (0 < $dlNow) {
throw new \Exception('Aborting the request');
}
},
]);
try {
foreach ($client->stream([$response]) as $chunk) {
}
$this->fail(ClientExceptionInterface::class.' expected');
} catch (TransportExceptionInterface $e) {
$this->assertSame('Aborting the request', $e->getPrevious()->getMessage());
}
$this->assertNotNull($response->getInfo('error'));
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testOnProgressError()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body', [
'on_progress' => function ($dlNow) {
if (0 < $dlNow) {
throw new \Error('BUG');
}
},
]);
try {
foreach ($client->stream([$response]) as $chunk) {
}
$this->fail('Error expected');
} catch (\Error $e) {
$this->assertSame('BUG', $e->getMessage());
}
$this->assertNotNull($response->getInfo('error'));
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testResolve()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://symfony.com:8057/', [
'resolve' => ['symfony.com' => '127.0.0.1'],
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(200, $client->request('GET', 'http://symfony.com:8057/')->getStatusCode());
$response = null;
$this->expectException(TransportExceptionInterface::class);
$client->request('GET', 'http://symfony.com:8057/', ['timeout' => 1]);
}
public function testNotATimeout()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-header', [
'timeout' => 0.9,
]);
sleep(1);
$this->assertSame(200, $response->getStatusCode());
}
public function testTimeoutOnAccess()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-header', [
'timeout' => 0.1,
]);
$this->expectException(TransportExceptionInterface::class);
$response->getHeaders();
}
public function testTimeoutOnStream()
{
usleep(300000); // wait for the previous test to release the server
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body');
$this->assertSame(200, $response->getStatusCode());
$chunks = $client->stream([$response], 0.2);
$result = [];
foreach ($chunks as $r => $chunk) {
if ($chunk->isTimeout()) {
$result[] = 't';
} else {
$result[] = $chunk->getContent();
}
}
$this->assertSame(['<1>', 't'], $result);
$chunks = $client->stream([$response]);
foreach ($chunks as $r => $chunk) {
$this->assertSame('<2>', $chunk->getContent());
$this->assertSame('<1><2>', $r->getContent());
return;
}
$this->fail('The response should have completed');
}
public function testUncheckedTimeoutThrows()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body');
$chunks = $client->stream([$response], 0.1);
$this->expectException(TransportExceptionInterface::class);
foreach ($chunks as $r => $chunk) {
}
}
public function testDestruct()
{
$client = $this->getHttpClient(__FUNCTION__);
$start = microtime(true);
$client->request('GET', 'http://localhost:8057/timeout-long');
$client = null;
$duration = microtime(true) - $start;
$this->assertGreaterThan(1, $duration);
$this->assertLessThan(4, $duration);
}
public function testProxy()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/', [
'proxy' => 'http://localhost:8057',
]);
$body = $response->toArray();
$this->assertSame('localhost:8057', $body['HTTP_HOST']);
$this->assertRegexp('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']);
$response = $client->request('GET', 'http://localhost:8057/', [
'proxy' => 'http://foo:b%3Dar@localhost:8057',
]);
$body = $response->toArray();
$this->assertSame('Basic Zm9vOmI9YXI=', $body['HTTP_PROXY_AUTHORIZATION']);
}
public function testNoProxy()
{
putenv('no_proxy='.$_SERVER['no_proxy'] = 'example.com, localhost');
try {
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/', [
'proxy' => 'http://localhost:8057',
]);
$body = $response->toArray();
$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
$this->assertSame('/', $body['REQUEST_URI']);
$this->assertSame('GET', $body['REQUEST_METHOD']);
} finally {
putenv('no_proxy');
unset($_SERVER['no_proxy']);
}
}
/**
* @requires extension zlib
*/
public function testAutoEncodingRequest()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057');
$this->assertSame(200, $response->getStatusCode());
$headers = $response->getHeaders();
$this->assertSame(['Accept-Encoding'], $headers['vary']);
$this->assertStringContainsString('gzip', $headers['content-encoding'][0]);
$body = $response->toArray();
$this->assertStringContainsString('gzip', $body['HTTP_ACCEPT_ENCODING']);
}
public function testBaseUri()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', '../404', [
'base_uri' => 'http://localhost:8057/abc/',
]);
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['application/json'], $response->getHeaders(false)['content-type']);
}
public function testQuery()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/?a=a', [
'query' => ['b' => 'b'],
]);
$body = $response->toArray();
$this->assertSame('GET', $body['REQUEST_METHOD']);
$this->assertSame('/?a=a&b=b', $body['REQUEST_URI']);
}
public function testInformationalResponse()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/103');
$this->assertSame('Here the body', $response->getContent());
$this->assertSame(200, $response->getStatusCode());
}
public function testInformationalResponseStream()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/103');
$chunks = [];
foreach ($client->stream($response) as $chunk) {
$chunks[] = $chunk;
}
$this->assertSame(103, $chunks[0]->getInformationalStatus()[0]);
$this->assertSame(['; rel=preload; as=style', '; rel=preload; as=script'], $chunks[0]->getInformationalStatus()[1]['link']);
$this->assertTrue($chunks[1]->isFirst());
$this->assertSame('Here the body', $chunks[2]->getContent());
$this->assertTrue($chunks[3]->isLast());
$this->assertNull($chunks[3]->getInformationalStatus());
$this->assertSame(['date', 'content-length'], array_keys($response->getHeaders()));
$this->assertContains('Link: ; rel=preload; as=style', $response->getInfo('response_headers'));
}
/**
* @requires extension zlib
*/
public function testUserlandEncodingRequest()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'headers' => ['Accept-Encoding' => 'gzip'],
]);
$headers = $response->getHeaders();
$this->assertSame(['Accept-Encoding'], $headers['vary']);
$this->assertStringContainsString('gzip', $headers['content-encoding'][0]);
$body = $response->getContent();
$this->assertSame("\x1F", $body[0]);
$body = json_decode(gzdecode($body), true);
$this->assertSame('gzip', $body['HTTP_ACCEPT_ENCODING']);
}
/**
* @requires extension zlib
*/
public function testGzipBroken()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/gzip-broken');
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}
public function testMaxDuration()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/max-duration', [
'max_duration' => 0.1,
]);
$start = microtime(true);
try {
$response->getContent();
} catch (TransportExceptionInterface $e) {
$this->addToAssertionCount(1);
}
$duration = microtime(true) - $start;
$this->assertLessThan(10, $duration);
}
}
contracts-1.1.8/HttpClient/Test/TestHttpServer.php 0000664 0000000 0000000 00000002267 13561010703 0022200 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Test;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
/**
* @experimental in 1.1
*/
class TestHttpServer
{
private static $server;
public static function start()
{
if (null !== self::$server) {
return;
}
$finder = new PhpExecutableFinder();
$process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:8057']));
$process->setWorkingDirectory(__DIR__.'/Fixtures/web');
$process->setTimeout(300);
$process->start();
self::$server = new class() {
public $process;
public function __destruct()
{
$this->process->stop();
}
};
self::$server->process = $process;
sleep('\\' === \DIRECTORY_SEPARATOR ? 10 : 1);
}
}
contracts-1.1.8/HttpClient/composer.json 0000664 0000000 0000000 00000001557 13561010703 0020325 0 ustar 00root root 0000000 0000000 {
"name": "symfony/http-client-contracts",
"type": "library",
"description": "Generic abstractions related to HTTP clients",
"keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.1.3"
},
"suggest": {
"symfony/http-client-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\HttpClient\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
}
}
contracts-1.1.8/LICENSE 0000664 0000000 0000000 00000002051 13561010703 0014520 0 ustar 00root root 0000000 0000000 Copyright (c) 2018-2019 Fabien Potencier
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.
contracts-1.1.8/README.md 0000664 0000000 0000000 00000004467 13561010703 0015007 0 ustar 00root root 0000000 0000000 Symfony Contracts
=================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
Design Principles
-----------------
* contracts are split by domain, each into their own sub-namespaces;
* contracts are small and consistent sets of PHP interfaces, traits, normative
docblocks and reference test suites when applicable, ...;
* all contracts must have a proven implementation to enter this repository;
* they must be backward compatible with existing Symfony components.
Packages that implement specific contracts should list them in the "provide"
section of their "composer.json" file, using the `symfony/*-implementation`
convention (e.g. `"provide": { "symfony/cache-implementation": "1.0" }`).
FAQ
---
### How to use this package?
The abstractions in this package are useful to achieve loose coupling and
interoperability. By using the provided interfaces as type hints, you are able
to reuse any implementations that match their contracts. It could be a Symfony
component, or another one provided by the PHP community at large.
Depending on their semantics, some interfaces can be combined with autowiring to
seamlessly inject a service in your classes.
Others might be useful as labeling interfaces, to hint about a specific behavior
that could be enabled when using autoconfiguration or manual service tagging (or
any other means provided by your framework.)
### How is this different from PHP-FIG's PSRs?
When applicable, the provided contracts are built on top of PHP-FIG's PSRs. But
the group has different goals and different processes. Here, we're focusing on
providing abstractions that are useful on their own while still compatible with
implementations provided by Symfony. Although not the main target, we hope that
the declared contracts will directly or indirectly contribute to the PHP-FIG.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/contracts.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
contracts-1.1.8/Service/ 0000775 0000000 0000000 00000000000 13561010703 0015115 5 ustar 00root root 0000000 0000000 contracts-1.1.8/Service/.gitignore 0000664 0000000 0000000 00000000042 13561010703 0017101 0 ustar 00root root 0000000 0000000 vendor/
composer.lock
phpunit.xml
contracts-1.1.8/Service/LICENSE 0000664 0000000 0000000 00000002051 13561010703 0016120 0 ustar 00root root 0000000 0000000 Copyright (c) 2018-2019 Fabien Potencier
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.
contracts-1.1.8/Service/README.md 0000664 0000000 0000000 00000000516 13561010703 0016376 0 ustar 00root root 0000000 0000000 Symfony Service Contracts
=========================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/master/README.md for more information.
contracts-1.1.8/Service/ResetInterface.php 0000664 0000000 0000000 00000001747 13561010703 0020542 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
/**
* Provides a way to reset an object to its initial state.
*
* When calling the "reset()" method on an object, it should be put back to its
* initial state. This usually means clearing any internal buffers and forwarding
* the call to internal dependencies. All properties of the object should be put
* back to the same state it had when it was first ready to use.
*
* This method could be called, for example, to recycle objects that are used as
* services, so that they can be used to handle several requests in the same
* process loop (note that we advise making your services stateless instead of
* implementing this interface when possible.)
*/
interface ResetInterface
{
public function reset();
}
contracts-1.1.8/Service/ServiceLocatorTrait.php 0000664 0000000 0000000 00000006705 13561010703 0021566 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* A trait to help implement ServiceProviderInterface.
*
* @author Robin Chalas
*/
trait ServiceLocatorTrait
{
private $factories;
private $loading = [];
private $providedTypes;
/**
* @param callable[] $factories
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($id)
{
return isset($this->factories[$id]);
}
/**
* {@inheritdoc}
*/
public function get($id)
{
if (!isset($this->factories[$id])) {
throw $this->createNotFoundException($id);
}
if (isset($this->loading[$id])) {
$ids = array_values($this->loading);
$ids = \array_slice($this->loading, array_search($id, $ids));
$ids[] = $id;
throw $this->createCircularReferenceException($id, $ids);
}
$this->loading[$id] = $id;
try {
return $this->factories[$id]($this);
} finally {
unset($this->loading[$id]);
}
}
/**
* {@inheritdoc}
*/
public function getProvidedServices(): array
{
if (null === $this->providedTypes) {
$this->providedTypes = [];
foreach ($this->factories as $name => $factory) {
if (!\is_callable($factory)) {
$this->providedTypes[$name] = '?';
} else {
$type = (new \ReflectionFunction($factory))->getReturnType();
$this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').$type->getName() : '?';
}
}
}
return $this->providedTypes;
}
private function createNotFoundException(string $id): NotFoundExceptionInterface
{
if (!$alternatives = array_keys($this->factories)) {
$message = 'is empty...';
} else {
$last = array_pop($alternatives);
if ($alternatives) {
$message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last);
} else {
$message = sprintf('only knows about the "%s" service.', $last);
}
}
if ($this->loading) {
$message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message);
} else {
$message = sprintf('Service "%s" not found: the current service locator %s', $id, $message);
}
return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface {
};
}
private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
{
return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface {
};
}
}
contracts-1.1.8/Service/ServiceProviderInterface.php 0000664 0000000 0000000 00000002272 13561010703 0022565 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* A ServiceProviderInterface exposes the identifiers and the types of services provided by a container.
*
* @author Nicolas Grekas
* @author Mateusz Sip
*/
interface ServiceSubscriberInterface
{
/**
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
*
* For mandatory dependencies:
*
* * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name
* internally to fetch a service which must implement Psr\Log\LoggerInterface.
* * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name
* internally to fetch an iterable of Psr\Log\LoggerInterface instances.
* * ['Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface']
*
* otherwise:
*
* * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency
* * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency
* * ['?Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface']
*
* @return array The required service types, optionally keyed by service names
*/
public static function getSubscribedServices();
}
contracts-1.1.8/Service/ServiceSubscriberTrait.php 0000664 0000000 0000000 00000003344 13561010703 0022262 0 ustar 00root root 0000000 0000000
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* Implementation of ServiceSubscriberInterface that determines subscribed services from
* private method return types. Service ids are available as "ClassName::methodName".
*
* @author Kevin Bond