pax_global_header00006660000000000000000000000064135610107030014506gustar00rootroot0000000000000052 comment=f51bca9de06b7a25b19a4155da7bebad099a5def contracts-1.1.8/000077500000000000000000000000001356101070300135155ustar00rootroot00000000000000contracts-1.1.8/.gitignore000066400000000000000000000000421356101070300155010ustar00rootroot00000000000000composer.lock phpunit.xml vendor/ contracts-1.1.8/CHANGELOG.md000066400000000000000000000015521356101070300153310ustar00rootroot00000000000000CHANGELOG ========= 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/000077500000000000000000000000001356101070300145205ustar00rootroot00000000000000contracts-1.1.8/Cache/.gitignore000066400000000000000000000000421356101070300165040ustar00rootroot00000000000000vendor/ composer.lock phpunit.xml contracts-1.1.8/Cache/CacheInterface.php000066400000000000000000000046051356101070300200620ustar00rootroot00000000000000 * * 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.php000066400000000000000000000046271356101070300172510ustar00rootroot00000000000000 * * 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.php000066400000000000000000000014531356101070300205510ustar00rootroot00000000000000 * * 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.php000066400000000000000000000033201356101070300177460ustar00rootroot00000000000000 * * 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/LICENSE000066400000000000000000000020511356101070300155230ustar00rootroot00000000000000Copyright (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.md000066400000000000000000000005121356101070300157750ustar00rootroot00000000000000Symfony 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.php000066400000000000000000000017551356101070300215010ustar00rootroot00000000000000 * * 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.json000066400000000000000000000015661356101070300172520ustar00rootroot00000000000000{ "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/000077500000000000000000000000001356101070300166055ustar00rootroot00000000000000contracts-1.1.8/EventDispatcher/.gitignore000066400000000000000000000000421356101070300205710ustar00rootroot00000000000000vendor/ composer.lock phpunit.xml contracts-1.1.8/EventDispatcher/Event.php000066400000000000000000000060461356101070300204050ustar00rootroot00000000000000 * * 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 * @author Jonathan Wage * @author Roman Borschel * @author Bernhard Schussek * @author Nicolas Grekas */ 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 * @author Jonathan Wage * @author Roman Borschel * @author Bernhard Schussek * @author Nicolas Grekas */ 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.php000066400000000000000000000043711356101070300242340ustar00rootroot00000000000000 * * 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/LICENSE000066400000000000000000000020511356101070300176100ustar00rootroot00000000000000Copyright (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.md000066400000000000000000000005361356101070300200700ustar00rootroot00000000000000Symfony 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.json000066400000000000000000000016471356101070300213370ustar00rootroot00000000000000{ "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/000077500000000000000000000000001356101070300155735ustar00rootroot00000000000000contracts-1.1.8/HttpClient/.gitignore000066400000000000000000000000421356101070300175570ustar00rootroot00000000000000vendor/ composer.lock phpunit.xml contracts-1.1.8/HttpClient/ChunkInterface.php000066400000000000000000000040331356101070300211750ustar00rootroot00000000000000 * * 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/000077500000000000000000000000001356101070300175315ustar00rootroot00000000000000contracts-1.1.8/HttpClient/Exception/ClientExceptionInterface.php000066400000000000000000000007331356101070300251630ustar00rootroot00000000000000 * * 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.php000066400000000000000000000007761356101070300254700ustar00rootroot00000000000000 * * 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.php000066400000000000000000000007371356101070300240300ustar00rootroot00000000000000 * * 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.php000066400000000000000000000011231356101070300246560ustar00rootroot00000000000000 * * 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 HttpExceptionInterface extends ExceptionInterface { public function getResponse(): ResponseInterface; } contracts-1.1.8/HttpClient/Exception/RedirectionExceptionInterface.php000066400000000000000000000010201356101070300262020ustar00rootroot00000000000000 * * 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 3xx response is returned and the "max_redirects" option has been reached. * * @author Nicolas Grekas * * @experimental in 1.1 */ interface RedirectionExceptionInterface extends HttpExceptionInterface { } contracts-1.1.8/HttpClient/Exception/ServerExceptionInterface.php000066400000000000000000000007331356101070300252130ustar00rootroot00000000000000 * * 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.php000066400000000000000000000007501356101070300257400ustar00rootroot00000000000000 * * 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.php000066400000000000000000000132051356101070300222040ustar00rootroot00000000000000 * * 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/LICENSE000066400000000000000000000020511356101070300165760ustar00rootroot00000000000000Copyright (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.md000066400000000000000000000005241356101070300170530ustar00rootroot00000000000000Symfony 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.php000066400000000000000000000114571356101070300217330ustar00rootroot00000000000000 * * 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.php000066400000000000000000000011031356101070300230720ustar00rootroot00000000000000 * * 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/000077500000000000000000000000001356101070300165125ustar00rootroot00000000000000contracts-1.1.8/HttpClient/Test/Fixtures/000077500000000000000000000000001356101070300203235ustar00rootroot00000000000000contracts-1.1.8/HttpClient/Test/Fixtures/web/000077500000000000000000000000001356101070300211005ustar00rootroot00000000000000contracts-1.1.8/HttpClient/Test/Fixtures/web/index.php000066400000000000000000000076641356101070300227350ustar00rootroot00000000000000 $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.php000066400000000000000000000747341356101070300227540ustar00rootroot00000000000000 * * 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.php000066400000000000000000000022671356101070300222000ustar00rootroot00000000000000 * * 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.json000066400000000000000000000015571356101070300203250ustar00rootroot00000000000000{ "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/LICENSE000066400000000000000000000020511356101070300145200ustar00rootroot00000000000000Copyright (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.md000066400000000000000000000044671356101070300150070ustar00rootroot00000000000000Symfony 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/000077500000000000000000000000001356101070300151155ustar00rootroot00000000000000contracts-1.1.8/Service/.gitignore000066400000000000000000000000421356101070300171010ustar00rootroot00000000000000vendor/ composer.lock phpunit.xml contracts-1.1.8/Service/LICENSE000066400000000000000000000020511356101070300161200ustar00rootroot00000000000000Copyright (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.md000066400000000000000000000005161356101070300163760ustar00rootroot00000000000000Symfony 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.php000066400000000000000000000017471356101070300205420ustar00rootroot00000000000000 * * 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.php000066400000000000000000000067051356101070300215660ustar00rootroot00000000000000 * * 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 * @author Nicolas Grekas */ 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.php000066400000000000000000000022721356101070300225650ustar00rootroot00000000000000 * * 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 ServiceProviderInterface extends ContainerInterface { /** * Returns an associative array of service types keyed by the identifiers provided by the current container. * * Examples: * * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface * * ['foo' => '?'] means the container provides service name "foo" of unspecified type * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null * * @return string[] The provided service types, keyed by service names */ public function getProvidedServices(): array; } contracts-1.1.8/Service/ServiceSubscriberInterface.php000066400000000000000000000043061356101070300230760ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Service; /** * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. * * The getSubscribedServices method returns an array of service types required by such instances, * optionally keyed by the service names used internally. Service types that start with an interrogation * mark "?" are optional, while the other ones are mandatory service dependencies. * * The injected service locators SHOULD NOT allow access to any other services not specified by the method. * * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. * This interface does not dictate any injection method for these service locators, although constructor * injection is recommended. * * @author Nicolas Grekas */ 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.php000066400000000000000000000033441356101070300222620ustar00rootroot00000000000000 * * 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 */ trait ServiceSubscriberTrait { /** @var ContainerInterface */ protected $container; public static function getSubscribedServices(): array { static $services; if (null !== $services) { return $services; } $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : []; foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { continue; } if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) { $services[self::class.'::'.$method->name] = '?'.$returnType->getName(); } } return $services; } /** * @required */ public function setContainer(ContainerInterface $container) { $this->container = $container; if (\is_callable(['parent', __FUNCTION__])) { return parent::setContainer($container); } return null; } } contracts-1.1.8/Service/Test/000077500000000000000000000000001356101070300160345ustar00rootroot00000000000000contracts-1.1.8/Service/Test/ServiceLocatorTest.php000066400000000000000000000055171356101070300223410ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Service\Test; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Symfony\Contracts\Service\ServiceLocatorTrait; abstract class ServiceLocatorTest extends TestCase { protected function getServiceLocator(array $factories) { return new class($factories) implements ContainerInterface { use ServiceLocatorTrait; }; } public function testHas() { $locator = $this->getServiceLocator([ 'foo' => function () { return 'bar'; }, 'bar' => function () { return 'baz'; }, function () { return 'dummy'; }, ]); $this->assertTrue($locator->has('foo')); $this->assertTrue($locator->has('bar')); $this->assertFalse($locator->has('dummy')); } public function testGet() { $locator = $this->getServiceLocator([ 'foo' => function () { return 'bar'; }, 'bar' => function () { return 'baz'; }, ]); $this->assertSame('bar', $locator->get('foo')); $this->assertSame('baz', $locator->get('bar')); } public function testGetDoesNotMemoize() { $i = 0; $locator = $this->getServiceLocator([ 'foo' => function () use (&$i) { ++$i; return 'bar'; }, ]); $this->assertSame('bar', $locator->get('foo')); $this->assertSame('bar', $locator->get('foo')); $this->assertSame(2, $i); } public function testThrowsOnUndefinedInternalService() { if (!$this->getExpectedException()) { $this->expectException('Psr\Container\NotFoundExceptionInterface'); $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); } $locator = $this->getServiceLocator([ 'foo' => function () use (&$locator) { return $locator->get('bar'); }, ]); $locator->get('foo'); } public function testThrowsOnCircularReference() { $this->expectException('Psr\Container\ContainerExceptionInterface'); $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); $locator = $this->getServiceLocator([ 'foo' => function () use (&$locator) { return $locator->get('bar'); }, 'bar' => function () use (&$locator) { return $locator->get('baz'); }, 'baz' => function () use (&$locator) { return $locator->get('bar'); }, ]); $locator->get('foo'); } } contracts-1.1.8/Service/composer.json000066400000000000000000000016111356101070300176360ustar00rootroot00000000000000{ "name": "symfony/service-contracts", "type": "library", "description": "Generic abstractions related to writing services", "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/container": "^1.0" }, "suggest": { "symfony/service-implementation": "" }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-master": "1.1-dev" } } } contracts-1.1.8/Tests/000077500000000000000000000000001356101070300146175ustar00rootroot00000000000000contracts-1.1.8/Tests/Cache/000077500000000000000000000000001356101070300156225ustar00rootroot00000000000000contracts-1.1.8/Tests/Cache/CacheTraitTest.php000066400000000000000000000077431356101070300212150ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Tests\Cache; use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Symfony\Contracts\Cache\CacheTrait; /** * @author Tobias Nyholm */ class CacheTraitTest extends TestCase { public function testSave() { $item = $this->getMockBuilder(CacheItemInterface::class)->getMock(); $item->method('set') ->willReturn($item); $item->method('isHit') ->willReturn(false); $item->expects($this->once()) ->method('set') ->with('computed data'); $cache = $this->getMockBuilder(TestPool::class) ->setMethods(['getItem', 'save']) ->getMock(); $cache->expects($this->once()) ->method('getItem') ->with('key') ->willReturn($item); $cache->expects($this->once()) ->method('save'); $callback = function (CacheItemInterface $item) { return 'computed data'; }; $cache->get('key', $callback); } public function testNoCallbackCallOnHit() { $item = $this->getMockBuilder(CacheItemInterface::class)->getMock(); $item->method('isHit') ->willReturn(true); $item->expects($this->never()) ->method('set'); $cache = $this->getMockBuilder(TestPool::class) ->setMethods(['getItem', 'save']) ->getMock(); $cache->expects($this->once()) ->method('getItem') ->with('key') ->willReturn($item); $cache->expects($this->never()) ->method('save'); $callback = function (CacheItemInterface $item) { $this->assertTrue(false, 'This code should never be reached'); }; $cache->get('key', $callback); } public function testRecomputeOnBetaInf() { $item = $this->getMockBuilder(CacheItemInterface::class)->getMock(); $item->method('set') ->willReturn($item); $item->method('isHit') // We want to recompute even if it is a hit ->willReturn(true); $item->expects($this->once()) ->method('set') ->with('computed data'); $cache = $this->getMockBuilder(TestPool::class) ->setMethods(['getItem', 'save']) ->getMock(); $cache->expects($this->once()) ->method('getItem') ->with('key') ->willReturn($item); $cache->expects($this->once()) ->method('save'); $callback = function (CacheItemInterface $item) { return 'computed data'; }; $cache->get('key', $callback, INF); } public function testExceptionOnNegativeBeta() { $cache = $this->getMockBuilder(TestPool::class) ->setMethods(['getItem', 'save']) ->getMock(); $callback = function (CacheItemInterface $item) { return 'computed data'; }; $this->expectException(\InvalidArgumentException::class); $cache->get('key', $callback, -2); } } class TestPool implements CacheItemPoolInterface { use CacheTrait; public function hasItem($key): bool { } public function deleteItem($key): bool { } public function deleteItems(array $keys = []): bool { } public function getItem($key): CacheItemInterface { } public function getItems(array $key = []): iterable { } public function saveDeferred(CacheItemInterface $item): bool { } public function save(CacheItemInterface $item): bool { } public function commit(): bool { } public function clear(): bool { } } contracts-1.1.8/Tests/Service/000077500000000000000000000000001356101070300162175ustar00rootroot00000000000000contracts-1.1.8/Tests/Service/ServiceSubscriberTraitTest.php000066400000000000000000000030741356101070300242240ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Tests\Service; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Symfony\Contracts\Service\ServiceLocatorTrait; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Symfony\Contracts\Service\ServiceSubscriberTrait; class ServiceSubscriberTraitTest extends TestCase { public function testMethodsOnParentsAndChildrenAreIgnoredInGetSubscribedServices() { $expected = [TestService::class.'::aService' => '?Symfony\Contracts\Tests\Service\Service2']; $this->assertEquals($expected, ChildTestService::getSubscribedServices()); } public function testSetContainerIsCalledOnParent() { $container = new class([]) implements ContainerInterface { use ServiceLocatorTrait; }; $this->assertSame($container, (new TestService())->setContainer($container)); } } class ParentTestService { public function aParentService(): Service1 { } public function setContainer(ContainerInterface $container) { return $container; } } class TestService extends ParentTestService implements ServiceSubscriberInterface { use ServiceSubscriberTrait; public function aService(): Service2 { } } class ChildTestService extends TestService { public function aChildService(): Service3 { } } contracts-1.1.8/Translation/000077500000000000000000000000001356101070300160135ustar00rootroot00000000000000contracts-1.1.8/Translation/.gitignore000066400000000000000000000000421356101070300177770ustar00rootroot00000000000000vendor/ composer.lock phpunit.xml contracts-1.1.8/Translation/LICENSE000066400000000000000000000020511356101070300170160ustar00rootroot00000000000000Copyright (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/Translation/LocaleAwareInterface.php000066400000000000000000000012311356101070300225210ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Translation; interface LocaleAwareInterface { /** * Sets the current locale. * * @param string $locale The locale * * @throws \InvalidArgumentException If the locale contains invalid characters */ public function setLocale($locale); /** * Returns the current locale. * * @return string The locale */ public function getLocale(); } contracts-1.1.8/Translation/README.md000066400000000000000000000005261356101070300172750ustar00rootroot00000000000000Symfony Translation 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/Translation/Test/000077500000000000000000000000001356101070300167325ustar00rootroot00000000000000contracts-1.1.8/Translation/Test/TranslatorTest.php000066400000000000000000000345551356101070300224500ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Translation\Test; use PHPUnit\Framework\TestCase; use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorTrait; /** * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms. * * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. * The mozilla code is also interesting to check for. * * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 * * The goal to cover all languages is to far fetched so this test case is smaller. * * @author Clemens Tolboom clemens@build2be.nl */ class TranslatorTest extends TestCase { public function getTranslator() { return new class() implements TranslatorInterface { use TranslatorTrait; }; } /** * @dataProvider getTransTests */ public function testTrans($expected, $id, $parameters) { $translator = $this->getTranslator(); $this->assertEquals($expected, $translator->trans($id, $parameters)); } /** * @dataProvider getTransChoiceTests */ public function testTransChoiceWithExplicitLocale($expected, $id, $number) { $translator = $this->getTranslator(); $translator->setLocale('en'); $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); } /** * @dataProvider getTransChoiceTests */ public function testTransChoiceWithDefaultLocale($expected, $id, $number) { \Locale::setDefault('en'); $translator = $this->getTranslator(); $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); } public function testGetSetLocale() { $translator = $this->getTranslator(); $translator->setLocale('en'); $this->assertEquals('en', $translator->getLocale()); } /** * @requires extension intl */ public function testGetLocaleReturnsDefaultLocaleIfNotSet() { $translator = $this->getTranslator(); \Locale::setDefault('pt_BR'); $this->assertEquals('pt_BR', $translator->getLocale()); \Locale::setDefault('en'); $this->assertEquals('en', $translator->getLocale()); } public function getTransTests() { return [ ['Symfony is great!', 'Symfony is great!', []], ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], ]; } public function getTransChoiceTests() { return [ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0], ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1], ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10], // custom validation messages may be coded with a fixed value ['There are 2 apples', 'There are 2 apples', 2], ]; } /** * @dataProvider getInternal */ public function testInterval($expected, $number, $interval) { $translator = $this->getTranslator(); $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); } public function getInternal() { return [ ['foo', 3, '{1,2, 3 ,4}'], ['bar', 10, '{1,2, 3 ,4}'], ['bar', 3, '[1,2]'], ['foo', 1, '[1,2]'], ['foo', 2, '[1,2]'], ['bar', 1, ']1,2['], ['bar', 2, ']1,2['], ['foo', log(0), '[-Inf,2['], ['foo', -log(0), '[-2,+Inf]'], ]; } /** * @dataProvider getChooseTests */ public function testChoose($expected, $id, $number) { $translator = $this->getTranslator(); $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); } public function testReturnMessageIfExactlyOneStandardRuleIsGiven() { $translator = $this->getTranslator(); $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); } /** * @dataProvider getNonMatchingMessages */ public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) { $this->expectException('InvalidArgumentException'); $translator = $this->getTranslator(); $translator->trans($id, ['%count%' => $number]); } public function getNonMatchingMessages() { return [ ['{0} There are no apples|{1} There is one apple', 2], ['{1} There is one apple|]1,Inf] There are %count% apples', 0], ['{1} There is one apple|]2,Inf] There are %count% apples', 2], ['{0} There are no apples|There is one apple', 2], ]; } public function getChooseTests() { return [ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10], ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], ['There are 0 apples', 'There is one apple|There are %count% apples', 0], ['There is one apple', 'There is one apple|There are %count% apples', 1], ['There are 10 apples', 'There is one apple|There are %count% apples', 10], ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0], ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1], ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10], ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0], ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1], ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10], ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0], ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1], // Indexed only tests which are Gettext PoFile* compatible strings. ['There are 0 apples', 'There is one apple|There are %count% apples', 0], ['There is one apple', 'There is one apple|There are %count% apples', 1], ['There are 2 apples', 'There is one apple|There are %count% apples', 2], // Tests for float numbers ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7], ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1], ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7], ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0], ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], // Test texts with new-lines // with double-quotes and \n in id & double-quotes and actual newlines in text ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a new-line in it. Selector = 0.|{1}This is a text with a new-line in it. Selector = 1.|[1,Inf]This is a text with a new-line in it. Selector > 1.', 0], // with double-quotes and \n in id and single-quotes and actual newlines in text ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a new-line in it. Selector = 0.|{1}This is a text with a new-line in it. Selector = 1.|[1,Inf]This is a text with a new-line in it. Selector > 1.', 1], ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a new-line in it. Selector = 0.|{1}This is a text with a new-line in it. Selector = 1.|[1,Inf]This is a text with a new-line in it. Selector > 1.', 5], // with double-quotes and id split accros lines ['This is a text with a new-line in it. Selector = 1.', '{0}This is a text with a new-line in it. Selector = 0.|{1}This is a text with a new-line in it. Selector = 1.|[1,Inf]This is a text with a new-line in it. Selector > 1.', 1], // with single-quotes and id split accros lines ['This is a text with a new-line in it. Selector > 1.', '{0}This is a text with a new-line in it. Selector = 0.|{1}This is a text with a new-line in it. Selector = 1.|[1,Inf]This is a text with a new-line in it. Selector > 1.', 5], // with single-quotes and \n in text ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0], // with double-quotes and id split accros lines ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1], // esacape pipe ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0], // Empty plural set (2 plural forms) from a .PO file ['', '|', 1], // Empty plural set (3 plural forms) from a .PO file ['', '||', 1], ]; } /** * @dataProvider failingLangcodes */ public function testFailedLangcodes($nplural, $langCodes) { $matrix = $this->generateTestData($langCodes); $this->validateMatrix($nplural, $matrix, false); } /** * @dataProvider successLangcodes */ public function testLangcodes($nplural, $langCodes) { $matrix = $this->generateTestData($langCodes); $this->validateMatrix($nplural, $matrix); } /** * This array should contain all currently known langcodes. * * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. * * @return array */ public function successLangcodes() { return [ ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM']], ['3', ['be', 'bs', 'cs', 'hr']], ['4', ['cy', 'mt', 'sl']], ['6', ['ar']], ]; } /** * This array should be at least empty within the near future. * * This both depends on a complete list trying to add above as understanding * the plural rules of the current failing languages. * * @return array with nplural together with langcodes */ public function failingLangcodes() { return [ ['1', ['fa']], ['2', ['jbo']], ['3', ['cbs']], ['4', ['gd', 'kw']], ['5', ['ga']], ]; } /** * We validate only on the plural coverage. Thus the real rules is not tested. * * @param string $nplural Plural expected * @param array $matrix Containing langcodes and their plural index values * @param bool $expectSuccess */ protected function validateMatrix($nplural, $matrix, $expectSuccess = true) { foreach ($matrix as $langCode => $data) { $indexes = array_flip($data); if ($expectSuccess) { $this->assertEquals($nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); } else { $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); } } } protected function generateTestData($langCodes) { $translator = new class() { use TranslatorTrait { getPluralizationRule as public; } }; $matrix = []; foreach ($langCodes as $langCode) { for ($count = 0; $count < 200; ++$count) { $plural = $translator->getPluralizationRule($count, $langCode); $matrix[$langCode][$count] = $plural; } } return $matrix; } } contracts-1.1.8/Translation/TranslatorInterface.php000066400000000000000000000046711356101070300225060ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Translation; /** * @author Fabien Potencier */ interface TranslatorInterface { /** * Translates the given message. * * When a number is provided as a parameter named "%count%", the message is parsed for plural * forms and a translation is chosen according to this number using the following rules: * * Given a message with different plural translations separated by a * pipe (|), this method returns the correct portion of the message based * on the given number, locale and the pluralization rules in the message * itself. * * The message supports two different types of pluralization rules: * * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples * indexed: There is one apple|There are %count% apples * * The indexed solution can also contain labels (e.g. one: There is one apple). * This is purely for making the translations more clear - it does not * affect the functionality. * * The two methods can also be mixed: * {0} There are no apples|one: There is one apple|more: There are %count% apples * * An interval can represent a finite set of numbers: * {1,2,3,4} * * An interval can represent numbers between two numbers: * [1, +Inf] * ]-1,2[ * * The left delimiter can be [ (inclusive) or ] (exclusive). * The right delimiter can be [ (exclusive) or ] (inclusive). * Beside numbers, you can use -Inf and +Inf for the infinite. * * @see https://en.wikipedia.org/wiki/ISO_31-11 * * @param string $id The message id (may also be an object that can be cast to string) * @param array $parameters An array of parameters for the message * @param string|null $domain The domain for the message or null to use the default * @param string|null $locale The locale or null to use the default * * @return string The translated string * * @throws \InvalidArgumentException If the locale contains invalid characters */ public function trans($id, array $parameters = [], $domain = null, $locale = null); } contracts-1.1.8/Translation/TranslatorTrait.php000066400000000000000000000201371356101070300216640ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Contracts\Translation; use Symfony\Component\Translation\Exception\InvalidArgumentException; /** * A trait to help implement TranslatorInterface and LocaleAwareInterface. * * @author Fabien Potencier */ trait TranslatorTrait { private $locale; /** * {@inheritdoc} */ public function setLocale($locale) { $this->locale = (string) $locale; } /** * {@inheritdoc} */ public function getLocale() { return $this->locale ?: \Locale::getDefault(); } /** * {@inheritdoc} */ public function trans($id, array $parameters = [], $domain = null, $locale = null) { if ('' === $id = (string) $id) { return ''; } if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { return strtr($id, $parameters); } $number = (float) $parameters['%count%']; $locale = (string) $locale ?: $this->getLocale(); $parts = []; if (preg_match('/^\|++$/', $id)) { $parts = explode('|', $id); } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { $parts = $matches[0]; } $intervalRegexp = <<<'EOF' /^(?P ({\s* (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) \s*}) | (?P[\[\]]) \s* (?P-Inf|\-?\d+(\.\d+)?) \s*,\s* (?P\+?Inf|\-?\d+(\.\d+)?) \s* (?P[\[\]]) )\s*(?P.*?)$/xs EOF; $standardRules = []; foreach ($parts as $part) { $part = trim(str_replace('||', '|', $part)); // try to match an explicit rule, then fallback to the standard ones if (preg_match($intervalRegexp, $part, $matches)) { if ($matches[2]) { foreach (explode(',', $matches[3]) as $n) { if ($number == $n) { return strtr($matches['message'], $parameters); } } } else { $leftNumber = '-Inf' === $matches['left'] ? -INF : (float) $matches['left']; $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : INF; if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) ) { return strtr($matches['message'], $parameters); } } } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { $standardRules[] = $matches[1]; } else { $standardRules[] = $part; } } $position = $this->getPluralizationRule($number, $locale); if (!isset($standardRules[$position])) { // when there's exactly one rule given, and that rule is a standard // rule, use this rule if (1 === \count($parts) && isset($standardRules[0])) { return strtr($standardRules[0], $parameters); } $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); if (class_exists(InvalidArgumentException::class)) { throw new InvalidArgumentException($message); } throw new \InvalidArgumentException($message); } return strtr($standardRules[$position], $parameters); } /** * Returns the plural position to use for the given locale and number. * * The plural rules are derived from code of the Zend Framework (2010-09-25), * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) */ private function getPluralizationRule(int $number, string $locale): int { switch ('pt_BR' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { case 'af': case 'bn': case 'bg': case 'ca': case 'da': case 'de': case 'el': case 'en': case 'eo': case 'es': case 'et': case 'eu': case 'fa': case 'fi': case 'fo': case 'fur': case 'fy': case 'gl': case 'gu': case 'ha': case 'he': case 'hu': case 'is': case 'it': case 'ku': case 'lb': case 'ml': case 'mn': case 'mr': case 'nah': case 'nb': case 'ne': case 'nl': case 'nn': case 'no': case 'oc': case 'om': case 'or': case 'pa': case 'pap': case 'ps': case 'pt': case 'so': case 'sq': case 'sv': case 'sw': case 'ta': case 'te': case 'tk': case 'ur': case 'zu': return (1 == $number) ? 0 : 1; case 'am': case 'bh': case 'fil': case 'fr': case 'gun': case 'hi': case 'hy': case 'ln': case 'mg': case 'nso': case 'pt_BR': case 'ti': case 'wa': return ((0 == $number) || (1 == $number)) ? 0 : 1; case 'be': case 'bs': case 'hr': case 'ru': case 'sh': case 'sr': case 'uk': return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); case 'cs': case 'sk': return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); case 'ga': return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2); case 'lt': return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); case 'sl': return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)); case 'mk': return (1 == $number % 10) ? 0 : 1; case 'mt': return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); case 'lv': return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2); case 'pl': return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); case 'cy': return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)); case 'ro': return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); case 'ar': return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))); default: return 0; } } } contracts-1.1.8/Translation/composer.json000066400000000000000000000015571356101070300205450ustar00rootroot00000000000000{ "name": "symfony/translation-contracts", "type": "library", "description": "Generic abstractions related to translation", "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/translation-implementation": "" }, "autoload": { "psr-4": { "Symfony\\Contracts\\Translation\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-master": "1.1-dev" } } } contracts-1.1.8/composer.json000066400000000000000000000031631356101070300162420ustar00rootroot00000000000000{ "name": "symfony/contracts", "type": "library", "description": "A set of abstractions extracted out of the Symfony components", "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", "psr/container": "^1.0" }, "require-dev": { "symfony/polyfill-intl-idn": "^1.10" }, "replace": { "symfony/cache-contracts": "self.version", "symfony/event-dispatcher-contracts": "self.version", "symfony/http-client-contracts": "self.version", "symfony/service-contracts": "self.version", "symfony/translation-contracts": "self.version" }, "suggest": { "psr/event-dispatcher": "When using the EventDispatcher contracts", "symfony/cache-implementation": "", "symfony/event-dispatcher-implementation": "", "symfony/http-client-implementation": "", "symfony/service-implementation": "", "symfony/translation-implementation": "" }, "autoload": { "psr-4": { "Symfony\\Contracts\\": "" }, "exclude-from-classmap": [ "**/Tests/" ] }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-master": "1.1-dev" } } } contracts-1.1.8/phpunit.xml.dist000066400000000000000000000020271356101070300166710ustar00rootroot00000000000000 ./Tests/ ./Service/Test/ ./Translation/Test/ ./ ./Tests ./Service/Test/ ./Translation/Test/ ./vendor