conteneurisation de l'appli
This commit is contained in:
21
vendor/symfony/amqp-messenger/CHANGELOG.md
vendored
Normal file
21
vendor/symfony/amqp-messenger/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Deprecated the `prefetch_count` parameter, it has no effect and will be removed in Symfony 6.0.
|
||||
* `AmqpReceiver` implements `QueueReceiverInterface` to fetch messages from a specific set of queues.
|
||||
* Add ability to distinguish retry and delay actions
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* Add option to confirm message delivery
|
||||
* DSN now support AMQPS out-of-the-box.
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* Introduced the AMQP bridge.
|
||||
* Deprecated use of invalid options
|
19
vendor/symfony/amqp-messenger/LICENSE
vendored
Normal file
19
vendor/symfony/amqp-messenger/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018-2022 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.
|
12
vendor/symfony/amqp-messenger/README.md
vendored
Normal file
12
vendor/symfony/amqp-messenger/README.md
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
AMQP Messenger
|
||||
==============
|
||||
|
||||
Provides AMQP integration for Symfony Messenger.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [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)
|
39
vendor/symfony/amqp-messenger/Transport/AmqpFactory.php
vendored
Normal file
39
vendor/symfony/amqp-messenger/Transport/AmqpFactory.php
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
class AmqpFactory
|
||||
{
|
||||
public function createConnection(array $credentials): \AMQPConnection
|
||||
{
|
||||
return new \AMQPConnection($credentials);
|
||||
}
|
||||
|
||||
public function createChannel(\AMQPConnection $connection): \AMQPChannel
|
||||
{
|
||||
return new \AMQPChannel($connection);
|
||||
}
|
||||
|
||||
public function createQueue(\AMQPChannel $channel): \AMQPQueue
|
||||
{
|
||||
return new \AMQPQueue($channel);
|
||||
}
|
||||
|
||||
public function createExchange(\AMQPChannel $channel): \AMQPExchange
|
||||
{
|
||||
return new \AMQPExchange($channel);
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpFactory::class, false)) {
|
||||
class_alias(AmqpFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpFactory::class);
|
||||
}
|
43
vendor/symfony/amqp-messenger/Transport/AmqpReceivedStamp.php
vendored
Normal file
43
vendor/symfony/amqp-messenger/Transport/AmqpReceivedStamp.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
|
||||
|
||||
/**
|
||||
* Stamp applied when a message is received from Amqp.
|
||||
*/
|
||||
class AmqpReceivedStamp implements NonSendableStampInterface
|
||||
{
|
||||
private $amqpEnvelope;
|
||||
private $queueName;
|
||||
|
||||
public function __construct(\AMQPEnvelope $amqpEnvelope, string $queueName)
|
||||
{
|
||||
$this->amqpEnvelope = $amqpEnvelope;
|
||||
$this->queueName = $queueName;
|
||||
}
|
||||
|
||||
public function getAmqpEnvelope(): \AMQPEnvelope
|
||||
{
|
||||
return $this->amqpEnvelope;
|
||||
}
|
||||
|
||||
public function getQueueName(): string
|
||||
{
|
||||
return $this->queueName;
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp::class, false)) {
|
||||
class_alias(AmqpReceivedStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp::class);
|
||||
}
|
150
vendor/symfony/amqp-messenger/Transport/AmqpReceiver.php
vendored
Normal file
150
vendor/symfony/amqp-messenger/Transport/AmqpReceiver.php
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Exception\LogicException;
|
||||
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
|
||||
use Symfony\Component\Messenger\Exception\TransportException;
|
||||
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
|
||||
use Symfony\Component\Messenger\Transport\Receiver\QueueReceiverInterface;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
|
||||
/**
|
||||
* Symfony Messenger receiver to get messages from AMQP brokers using PHP's AMQP extension.
|
||||
*
|
||||
* @author Samuel Roze <samuel.roze@gmail.com>
|
||||
*/
|
||||
class AmqpReceiver implements QueueReceiverInterface, MessageCountAwareInterface
|
||||
{
|
||||
private $serializer;
|
||||
private $connection;
|
||||
|
||||
public function __construct(Connection $connection, SerializerInterface $serializer = null)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->serializer = $serializer ?? new PhpSerializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(): iterable
|
||||
{
|
||||
yield from $this->getFromQueues($this->connection->getQueueNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromQueues(array $queueNames): iterable
|
||||
{
|
||||
foreach ($queueNames as $queueName) {
|
||||
yield from $this->getEnvelope($queueName);
|
||||
}
|
||||
}
|
||||
|
||||
private function getEnvelope(string $queueName): iterable
|
||||
{
|
||||
try {
|
||||
$amqpEnvelope = $this->connection->get($queueName);
|
||||
} catch (\AMQPException $exception) {
|
||||
throw new TransportException($exception->getMessage(), 0, $exception);
|
||||
}
|
||||
|
||||
if (null === $amqpEnvelope) {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $amqpEnvelope->getBody();
|
||||
|
||||
try {
|
||||
$envelope = $this->serializer->decode([
|
||||
'body' => false === $body ? '' : $body, // workaround https://github.com/pdezwart/php-amqp/issues/351
|
||||
'headers' => $amqpEnvelope->getHeaders(),
|
||||
]);
|
||||
} catch (MessageDecodingFailedException $exception) {
|
||||
// invalid message of some type
|
||||
$this->rejectAmqpEnvelope($amqpEnvelope, $queueName);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
yield $envelope->with(new AmqpReceivedStamp($amqpEnvelope, $queueName));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ack(Envelope $envelope): void
|
||||
{
|
||||
try {
|
||||
$stamp = $this->findAmqpStamp($envelope);
|
||||
|
||||
$this->connection->ack(
|
||||
$stamp->getAmqpEnvelope(),
|
||||
$stamp->getQueueName()
|
||||
);
|
||||
} catch (\AMQPException $exception) {
|
||||
throw new TransportException($exception->getMessage(), 0, $exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reject(Envelope $envelope): void
|
||||
{
|
||||
$stamp = $this->findAmqpStamp($envelope);
|
||||
|
||||
$this->rejectAmqpEnvelope(
|
||||
$stamp->getAmqpEnvelope(),
|
||||
$stamp->getQueueName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMessageCount(): int
|
||||
{
|
||||
try {
|
||||
return $this->connection->countMessagesInQueues();
|
||||
} catch (\AMQPException $exception) {
|
||||
throw new TransportException($exception->getMessage(), 0, $exception);
|
||||
}
|
||||
}
|
||||
|
||||
private function rejectAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, string $queueName): void
|
||||
{
|
||||
try {
|
||||
$this->connection->nack($amqpEnvelope, $queueName, \AMQP_NOPARAM);
|
||||
} catch (\AMQPException $exception) {
|
||||
throw new TransportException($exception->getMessage(), 0, $exception);
|
||||
}
|
||||
}
|
||||
|
||||
private function findAmqpStamp(Envelope $envelope): AmqpReceivedStamp
|
||||
{
|
||||
$amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
|
||||
if (null === $amqpReceivedStamp) {
|
||||
throw new LogicException('No "AmqpReceivedStamp" stamp found on the Envelope.');
|
||||
}
|
||||
|
||||
return $amqpReceivedStamp;
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver::class, false)) {
|
||||
class_alias(AmqpReceiver::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver::class);
|
||||
}
|
86
vendor/symfony/amqp-messenger/Transport/AmqpSender.php
vendored
Normal file
86
vendor/symfony/amqp-messenger/Transport/AmqpSender.php
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Exception\TransportException;
|
||||
use Symfony\Component\Messenger\Stamp\DelayStamp;
|
||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||
use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
|
||||
/**
|
||||
* Symfony Messenger sender to send messages to AMQP brokers using PHP's AMQP extension.
|
||||
*
|
||||
* @author Samuel Roze <samuel.roze@gmail.com>
|
||||
*/
|
||||
class AmqpSender implements SenderInterface
|
||||
{
|
||||
private $serializer;
|
||||
private $connection;
|
||||
|
||||
public function __construct(Connection $connection, SerializerInterface $serializer = null)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->serializer = $serializer ?? new PhpSerializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function send(Envelope $envelope): Envelope
|
||||
{
|
||||
$encodedMessage = $this->serializer->encode($envelope);
|
||||
|
||||
/** @var DelayStamp|null $delayStamp */
|
||||
$delayStamp = $envelope->last(DelayStamp::class);
|
||||
$delay = $delayStamp ? $delayStamp->getDelay() : 0;
|
||||
|
||||
/** @var AmqpStamp|null $amqpStamp */
|
||||
$amqpStamp = $envelope->last(AmqpStamp::class);
|
||||
if (isset($encodedMessage['headers']['Content-Type'])) {
|
||||
$contentType = $encodedMessage['headers']['Content-Type'];
|
||||
unset($encodedMessage['headers']['Content-Type']);
|
||||
|
||||
if (!$amqpStamp || !isset($amqpStamp->getAttributes()['content_type'])) {
|
||||
$amqpStamp = AmqpStamp::createWithAttributes(['content_type' => $contentType], $amqpStamp);
|
||||
}
|
||||
}
|
||||
|
||||
$amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
|
||||
if ($amqpReceivedStamp instanceof AmqpReceivedStamp) {
|
||||
$amqpStamp = AmqpStamp::createFromAmqpEnvelope(
|
||||
$amqpReceivedStamp->getAmqpEnvelope(),
|
||||
$amqpStamp,
|
||||
$envelope->last(RedeliveryStamp::class) ? $amqpReceivedStamp->getQueueName() : null
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->connection->publish(
|
||||
$encodedMessage['body'],
|
||||
$encodedMessage['headers'] ?? [],
|
||||
$delay,
|
||||
$amqpStamp
|
||||
);
|
||||
} catch (\AMQPException $e) {
|
||||
throw new TransportException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return $envelope;
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender::class, false)) {
|
||||
class_alias(AmqpSender::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender::class);
|
||||
}
|
94
vendor/symfony/amqp-messenger/Transport/AmqpStamp.php
vendored
Normal file
94
vendor/symfony/amqp-messenger/Transport/AmqpStamp.php
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
|
||||
|
||||
/**
|
||||
* @author Guillaume Gammelin <ggammelin@gmail.com>
|
||||
* @author Samuel Roze <samuel.roze@gmail.com>
|
||||
*/
|
||||
final class AmqpStamp implements NonSendableStampInterface
|
||||
{
|
||||
private $routingKey;
|
||||
private $flags;
|
||||
private $attributes;
|
||||
private $isRetryAttempt = false;
|
||||
|
||||
public function __construct(string $routingKey = null, int $flags = \AMQP_NOPARAM, array $attributes = [])
|
||||
{
|
||||
$this->routingKey = $routingKey;
|
||||
$this->flags = $flags;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
public function getRoutingKey(): ?string
|
||||
{
|
||||
return $this->routingKey;
|
||||
}
|
||||
|
||||
public function getFlags(): int
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
public function getAttributes(): array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public static function createFromAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, self $previousStamp = null, string $retryRoutingKey = null): self
|
||||
{
|
||||
$attr = $previousStamp->attributes ?? [];
|
||||
|
||||
$attr['headers'] = $attr['headers'] ?? $amqpEnvelope->getHeaders();
|
||||
$attr['content_type'] = $attr['content_type'] ?? $amqpEnvelope->getContentType();
|
||||
$attr['content_encoding'] = $attr['content_encoding'] ?? $amqpEnvelope->getContentEncoding();
|
||||
$attr['delivery_mode'] = $attr['delivery_mode'] ?? $amqpEnvelope->getDeliveryMode();
|
||||
$attr['priority'] = $attr['priority'] ?? $amqpEnvelope->getPriority();
|
||||
$attr['timestamp'] = $attr['timestamp'] ?? $amqpEnvelope->getTimestamp();
|
||||
$attr['app_id'] = $attr['app_id'] ?? $amqpEnvelope->getAppId();
|
||||
$attr['message_id'] = $attr['message_id'] ?? $amqpEnvelope->getMessageId();
|
||||
$attr['user_id'] = $attr['user_id'] ?? $amqpEnvelope->getUserId();
|
||||
$attr['expiration'] = $attr['expiration'] ?? $amqpEnvelope->getExpiration();
|
||||
$attr['type'] = $attr['type'] ?? $amqpEnvelope->getType();
|
||||
$attr['reply_to'] = $attr['reply_to'] ?? $amqpEnvelope->getReplyTo();
|
||||
$attr['correlation_id'] = $attr['correlation_id'] ?? $amqpEnvelope->getCorrelationId();
|
||||
|
||||
if (null === $retryRoutingKey) {
|
||||
$stamp = new self($previousStamp->routingKey ?? $amqpEnvelope->getRoutingKey(), $previousStamp->flags ?? \AMQP_NOPARAM, $attr);
|
||||
} else {
|
||||
$stamp = new self($retryRoutingKey, $previousStamp->flags ?? \AMQP_NOPARAM, $attr);
|
||||
$stamp->isRetryAttempt = true;
|
||||
}
|
||||
|
||||
return $stamp;
|
||||
}
|
||||
|
||||
public function isRetryAttempt(): bool
|
||||
{
|
||||
return $this->isRetryAttempt;
|
||||
}
|
||||
|
||||
public static function createWithAttributes(array $attributes, self $previousStamp = null): self
|
||||
{
|
||||
return new self(
|
||||
$previousStamp->routingKey ?? null,
|
||||
$previousStamp->flags ?? \AMQP_NOPARAM,
|
||||
array_merge($previousStamp->attributes ?? [], $attributes)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class, false)) {
|
||||
class_alias(AmqpStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class);
|
||||
}
|
107
vendor/symfony/amqp-messenger/Transport/AmqpTransport.php
vendored
Normal file
107
vendor/symfony/amqp-messenger/Transport/AmqpTransport.php
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
|
||||
use Symfony\Component\Messenger\Transport\Receiver\QueueReceiverInterface;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
|
||||
use Symfony\Component\Messenger\Transport\TransportInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class AmqpTransport implements QueueReceiverInterface, TransportInterface, SetupableTransportInterface, MessageCountAwareInterface
|
||||
{
|
||||
private $serializer;
|
||||
private $connection;
|
||||
private $receiver;
|
||||
private $sender;
|
||||
|
||||
public function __construct(Connection $connection, SerializerInterface $serializer = null)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->serializer = $serializer ?? new PhpSerializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(): iterable
|
||||
{
|
||||
return ($this->receiver ?? $this->getReceiver())->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromQueues(array $queueNames): iterable
|
||||
{
|
||||
return ($this->receiver ?? $this->getReceiver())->getFromQueues($queueNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ack(Envelope $envelope): void
|
||||
{
|
||||
($this->receiver ?? $this->getReceiver())->ack($envelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reject(Envelope $envelope): void
|
||||
{
|
||||
($this->receiver ?? $this->getReceiver())->reject($envelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function send(Envelope $envelope): Envelope
|
||||
{
|
||||
return ($this->sender ?? $this->getSender())->send($envelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setup(): void
|
||||
{
|
||||
$this->connection->setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMessageCount(): int
|
||||
{
|
||||
return ($this->receiver ?? $this->getReceiver())->getMessageCount();
|
||||
}
|
||||
|
||||
private function getReceiver(): AmqpReceiver
|
||||
{
|
||||
return $this->receiver = new AmqpReceiver($this->connection, $this->serializer);
|
||||
}
|
||||
|
||||
private function getSender(): AmqpSender
|
||||
{
|
||||
return $this->sender = new AmqpSender($this->connection, $this->serializer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class, false)) {
|
||||
class_alias(AmqpTransport::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class);
|
||||
}
|
38
vendor/symfony/amqp-messenger/Transport/AmqpTransportFactory.php
vendored
Normal file
38
vendor/symfony/amqp-messenger/Transport/AmqpTransportFactory.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
||||
use Symfony\Component\Messenger\Transport\TransportInterface;
|
||||
|
||||
/**
|
||||
* @author Samuel Roze <samuel.roze@gmail.com>
|
||||
*/
|
||||
class AmqpTransportFactory implements TransportFactoryInterface
|
||||
{
|
||||
public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
|
||||
{
|
||||
unset($options['transport_name']);
|
||||
|
||||
return new AmqpTransport(Connection::fromDsn($dsn, $options), $serializer);
|
||||
}
|
||||
|
||||
public function supports(string $dsn, array $options): bool
|
||||
{
|
||||
return 0 === strpos($dsn, 'amqp://') || 0 === strpos($dsn, 'amqps://');
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class, false)) {
|
||||
class_alias(AmqpTransportFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class);
|
||||
}
|
599
vendor/symfony/amqp-messenger/Transport/Connection.php
vendored
Normal file
599
vendor/symfony/amqp-messenger/Transport/Connection.php
vendored
Normal file
@ -0,0 +1,599 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Messenger\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* An AMQP connection.
|
||||
*
|
||||
* @author Samuel Roze <samuel.roze@gmail.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
private const ARGUMENTS_AS_INTEGER = [
|
||||
'x-delay',
|
||||
'x-expires',
|
||||
'x-max-length',
|
||||
'x-max-length-bytes',
|
||||
'x-max-priority',
|
||||
'x-message-ttl',
|
||||
];
|
||||
|
||||
/**
|
||||
* @see https://github.com/php-amqp/php-amqp/blob/master/amqp_connection_resource.h
|
||||
*/
|
||||
private const AVAILABLE_OPTIONS = [
|
||||
'host',
|
||||
'port',
|
||||
'vhost',
|
||||
'user',
|
||||
'login',
|
||||
'password',
|
||||
'queues',
|
||||
'exchange',
|
||||
'delay',
|
||||
'auto_setup',
|
||||
'prefetch_count',
|
||||
'retry',
|
||||
'persistent',
|
||||
'frame_max',
|
||||
'channel_max',
|
||||
'heartbeat',
|
||||
'read_timeout',
|
||||
'write_timeout',
|
||||
'confirm_timeout',
|
||||
'connect_timeout',
|
||||
'rpc_timeout',
|
||||
'cacert',
|
||||
'cert',
|
||||
'key',
|
||||
'verify',
|
||||
'sasl_method',
|
||||
];
|
||||
|
||||
private const AVAILABLE_QUEUE_OPTIONS = [
|
||||
'binding_keys',
|
||||
'binding_arguments',
|
||||
'flags',
|
||||
'arguments',
|
||||
];
|
||||
|
||||
private const AVAILABLE_EXCHANGE_OPTIONS = [
|
||||
'name',
|
||||
'type',
|
||||
'default_publish_routing_key',
|
||||
'flags',
|
||||
'arguments',
|
||||
];
|
||||
|
||||
private $connectionOptions;
|
||||
private $exchangeOptions;
|
||||
private $queuesOptions;
|
||||
private $amqpFactory;
|
||||
private $autoSetupExchange;
|
||||
private $autoSetupDelayExchange;
|
||||
|
||||
/**
|
||||
* @var \AMQPChannel|null
|
||||
*/
|
||||
private $amqpChannel;
|
||||
|
||||
/**
|
||||
* @var \AMQPExchange|null
|
||||
*/
|
||||
private $amqpExchange;
|
||||
|
||||
/**
|
||||
* @var \AMQPQueue[]|null
|
||||
*/
|
||||
private $amqpQueues = [];
|
||||
|
||||
/**
|
||||
* @var \AMQPExchange|null
|
||||
*/
|
||||
private $amqpDelayExchange;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $lastActivityTime = 0;
|
||||
|
||||
public function __construct(array $connectionOptions, array $exchangeOptions, array $queuesOptions, AmqpFactory $amqpFactory = null)
|
||||
{
|
||||
if (!\extension_loaded('amqp')) {
|
||||
throw new LogicException(sprintf('You cannot use the "%s" as the "amqp" extension is not installed.', __CLASS__));
|
||||
}
|
||||
|
||||
$this->connectionOptions = array_replace_recursive([
|
||||
'delay' => [
|
||||
'exchange_name' => 'delays',
|
||||
'queue_name_pattern' => 'delay_%exchange_name%_%routing_key%_%delay%',
|
||||
],
|
||||
], $connectionOptions);
|
||||
$this->autoSetupExchange = $this->autoSetupDelayExchange = $connectionOptions['auto_setup'] ?? true;
|
||||
$this->exchangeOptions = $exchangeOptions;
|
||||
$this->queuesOptions = $queuesOptions;
|
||||
$this->amqpFactory = $amqpFactory ?? new AmqpFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection based on the DSN and options.
|
||||
*
|
||||
* Available options:
|
||||
*
|
||||
* * host: Hostname of the AMQP service
|
||||
* * port: Port of the AMQP service
|
||||
* * vhost: Virtual Host to use with the AMQP service
|
||||
* * user|login: Username to use to connect the AMQP service
|
||||
* * password: Password to use to connect to the AMQP service
|
||||
* * read_timeout: Timeout in for income activity. Note: 0 or greater seconds. May be fractional.
|
||||
* * write_timeout: Timeout in for outcome activity. Note: 0 or greater seconds. May be fractional.
|
||||
* * connect_timeout: Connection timeout. Note: 0 or greater seconds. May be fractional.
|
||||
* * confirm_timeout: Timeout in seconds for confirmation, if none specified transport will not wait for message confirmation. Note: 0 or greater seconds. May be fractional.
|
||||
* * queues[name]: An array of queues, keyed by the name
|
||||
* * binding_keys: The binding keys (if any) to bind to this queue
|
||||
* * binding_arguments: Arguments to be used while binding the queue.
|
||||
* * flags: Queue flags (Default: AMQP_DURABLE)
|
||||
* * arguments: Extra arguments
|
||||
* * exchange:
|
||||
* * name: Name of the exchange
|
||||
* * type: Type of exchange (Default: fanout)
|
||||
* * default_publish_routing_key: Routing key to use when publishing, if none is specified on the message
|
||||
* * flags: Exchange flags (Default: AMQP_DURABLE)
|
||||
* * arguments: Extra arguments
|
||||
* * delay:
|
||||
* * queue_name_pattern: Pattern to use to create the queues (Default: "delay_%exchange_name%_%routing_key%_%delay%")
|
||||
* * exchange_name: Name of the exchange to be used for the delayed/retried messages (Default: "delays")
|
||||
* * auto_setup: Enable or not the auto-setup of queues and exchanges (Default: true)
|
||||
*
|
||||
* * Connection tuning options (see http://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.tune for details):
|
||||
* * channel_max: Specifies highest channel number that the server permits. 0 means standard extension limit
|
||||
* (see PHP_AMQP_MAX_CHANNELS constant)
|
||||
* * frame_max: The largest frame size that the server proposes for the connection, including frame header
|
||||
* and end-byte. 0 means standard extension limit (depends on librabbimq default frame size limit)
|
||||
* * heartbeat: The delay, in seconds, of the connection heartbeat that the server wants.
|
||||
* 0 means the server does not want a heartbeat. Note, librabbitmq has limited heartbeat support,
|
||||
* which means heartbeats checked only during blocking calls.
|
||||
*
|
||||
* TLS support (see https://www.rabbitmq.com/ssl.html for details):
|
||||
* * cacert: Path to the CA cert file in PEM format.
|
||||
* * cert: Path to the client certificate in PEM format.
|
||||
* * key: Path to the client key in PEM format.
|
||||
* * verify: Enable or disable peer verification. If peer verification is enabled then the common name in the
|
||||
* server certificate must match the server name. Peer verification is enabled by default.
|
||||
*/
|
||||
public static function fromDsn(string $dsn, array $options = [], AmqpFactory $amqpFactory = null): self
|
||||
{
|
||||
if (false === $parsedUrl = parse_url($dsn)) {
|
||||
// this is a valid URI that parse_url cannot handle when you want to pass all parameters as options
|
||||
if (!\in_array($dsn, ['amqp://', 'amqps://'])) {
|
||||
throw new InvalidArgumentException(sprintf('The given AMQP DSN "%s" is invalid.', $dsn));
|
||||
}
|
||||
|
||||
$parsedUrl = [];
|
||||
}
|
||||
|
||||
$useAmqps = 0 === strpos($dsn, 'amqps://');
|
||||
$pathParts = isset($parsedUrl['path']) ? explode('/', trim($parsedUrl['path'], '/')) : [];
|
||||
$exchangeName = $pathParts[1] ?? 'messages';
|
||||
parse_str($parsedUrl['query'] ?? '', $parsedQuery);
|
||||
$port = $useAmqps ? 5671 : 5672;
|
||||
|
||||
$amqpOptions = array_replace_recursive([
|
||||
'host' => $parsedUrl['host'] ?? 'localhost',
|
||||
'port' => $parsedUrl['port'] ?? $port,
|
||||
'vhost' => isset($pathParts[0]) ? urldecode($pathParts[0]) : '/',
|
||||
'exchange' => [
|
||||
'name' => $exchangeName,
|
||||
],
|
||||
], $options, $parsedQuery);
|
||||
|
||||
self::validateOptions($amqpOptions);
|
||||
|
||||
if (isset($parsedUrl['user'])) {
|
||||
$amqpOptions['login'] = urldecode($parsedUrl['user']);
|
||||
}
|
||||
|
||||
if (isset($parsedUrl['pass'])) {
|
||||
$amqpOptions['password'] = urldecode($parsedUrl['pass']);
|
||||
}
|
||||
|
||||
if (!isset($amqpOptions['queues'])) {
|
||||
$amqpOptions['queues'][$exchangeName] = [];
|
||||
}
|
||||
|
||||
$exchangeOptions = $amqpOptions['exchange'];
|
||||
$queuesOptions = $amqpOptions['queues'];
|
||||
unset($amqpOptions['queues'], $amqpOptions['exchange']);
|
||||
if (isset($amqpOptions['auto_setup'])) {
|
||||
$amqpOptions['auto_setup'] = filter_var($amqpOptions['auto_setup'], \FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
$queuesOptions = array_map(function ($queueOptions) {
|
||||
if (!\is_array($queueOptions)) {
|
||||
$queueOptions = [];
|
||||
}
|
||||
if (\is_array($queueOptions['arguments'] ?? false)) {
|
||||
$queueOptions['arguments'] = self::normalizeQueueArguments($queueOptions['arguments']);
|
||||
}
|
||||
|
||||
return $queueOptions;
|
||||
}, $queuesOptions);
|
||||
|
||||
if (!$useAmqps) {
|
||||
unset($amqpOptions['cacert'], $amqpOptions['cert'], $amqpOptions['key'], $amqpOptions['verify']);
|
||||
}
|
||||
|
||||
if ($useAmqps && !self::hasCaCertConfigured($amqpOptions)) {
|
||||
throw new InvalidArgumentException('No CA certificate has been provided. Set "amqp.cacert" in your php.ini or pass the "cacert" parameter in the DSN to use SSL. Alternatively, you can use amqp:// to use without SSL.');
|
||||
}
|
||||
|
||||
return new self($amqpOptions, $exchangeOptions, $queuesOptions, $amqpFactory);
|
||||
}
|
||||
|
||||
private static function validateOptions(array $options): void
|
||||
{
|
||||
if (0 < \count($invalidOptions = array_diff(array_keys($options), self::AVAILABLE_OPTIONS))) {
|
||||
trigger_deprecation('symfony/messenger', '5.1', 'Invalid option(s) "%s" passed to the AMQP Messenger transport. Passing invalid options is deprecated.', implode('", "', $invalidOptions));
|
||||
}
|
||||
|
||||
if (isset($options['prefetch_count'])) {
|
||||
trigger_deprecation('symfony/messenger', '5.3', 'The "prefetch_count" option passed to the AMQP Messenger transport has no effect and should not be used.');
|
||||
}
|
||||
|
||||
if (\is_array($options['queues'] ?? false)) {
|
||||
foreach ($options['queues'] as $queue) {
|
||||
if (!\is_array($queue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 < \count($invalidQueueOptions = array_diff(array_keys($queue), self::AVAILABLE_QUEUE_OPTIONS))) {
|
||||
trigger_deprecation('symfony/messenger', '5.1', 'Invalid queue option(s) "%s" passed to the AMQP Messenger transport. Passing invalid queue options is deprecated.', implode('", "', $invalidQueueOptions));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_array($options['exchange'] ?? false)
|
||||
&& 0 < \count($invalidExchangeOptions = array_diff(array_keys($options['exchange']), self::AVAILABLE_EXCHANGE_OPTIONS))) {
|
||||
trigger_deprecation('symfony/messenger', '5.1', 'Invalid exchange option(s) "%s" passed to the AMQP Messenger transport. Passing invalid exchange options is deprecated.', implode('", "', $invalidExchangeOptions));
|
||||
}
|
||||
}
|
||||
|
||||
private static function normalizeQueueArguments(array $arguments): array
|
||||
{
|
||||
foreach (self::ARGUMENTS_AS_INTEGER as $key) {
|
||||
if (!\array_key_exists($key, $arguments)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_numeric($arguments[$key])) {
|
||||
throw new InvalidArgumentException(sprintf('Integer expected for queue argument "%s", "%s" given.', $key, get_debug_type($arguments[$key])));
|
||||
}
|
||||
|
||||
$arguments[$key] = (int) $arguments[$key];
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
private static function hasCaCertConfigured(array $amqpOptions): bool
|
||||
{
|
||||
return (isset($amqpOptions['cacert']) && '' !== $amqpOptions['cacert']) || '' !== \ini_get('amqp.cacert');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \AMQPException
|
||||
*/
|
||||
public function publish(string $body, array $headers = [], int $delayInMs = 0, AmqpStamp $amqpStamp = null): void
|
||||
{
|
||||
$this->clearWhenDisconnected();
|
||||
|
||||
if ($this->autoSetupExchange) {
|
||||
$this->setupExchangeAndQueues(); // also setup normal exchange for delayed messages so delay queue can DLX messages to it
|
||||
}
|
||||
|
||||
if (0 !== $delayInMs) {
|
||||
$this->publishWithDelay($body, $headers, $delayInMs, $amqpStamp);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->publishOnExchange(
|
||||
$this->exchange(),
|
||||
$body,
|
||||
$this->getRoutingKeyForMessage($amqpStamp),
|
||||
$headers,
|
||||
$amqpStamp
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an approximate count of the messages in defined queues.
|
||||
*/
|
||||
public function countMessagesInQueues(): int
|
||||
{
|
||||
return array_sum(array_map(function ($queueName) {
|
||||
return $this->queue($queueName)->declareQueue();
|
||||
}, $this->getQueueNames()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \AMQPException
|
||||
*/
|
||||
private function publishWithDelay(string $body, array $headers, int $delay, AmqpStamp $amqpStamp = null)
|
||||
{
|
||||
$routingKey = $this->getRoutingKeyForMessage($amqpStamp);
|
||||
$isRetryAttempt = $amqpStamp ? $amqpStamp->isRetryAttempt() : false;
|
||||
|
||||
$this->setupDelay($delay, $routingKey, $isRetryAttempt);
|
||||
|
||||
$this->publishOnExchange(
|
||||
$this->getDelayExchange(),
|
||||
$body,
|
||||
$this->getRoutingKeyForDelay($delay, $routingKey, $isRetryAttempt),
|
||||
$headers,
|
||||
$amqpStamp
|
||||
);
|
||||
}
|
||||
|
||||
private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null)
|
||||
{
|
||||
$attributes = $amqpStamp ? $amqpStamp->getAttributes() : [];
|
||||
$attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers);
|
||||
$attributes['delivery_mode'] = $attributes['delivery_mode'] ?? 2;
|
||||
$attributes['timestamp'] = $attributes['timestamp'] ?? time();
|
||||
|
||||
$this->lastActivityTime = time();
|
||||
|
||||
$exchange->publish(
|
||||
$body,
|
||||
$routingKey,
|
||||
$amqpStamp ? $amqpStamp->getFlags() : \AMQP_NOPARAM,
|
||||
$attributes
|
||||
);
|
||||
|
||||
if ('' !== ($this->connectionOptions['confirm_timeout'] ?? '')) {
|
||||
$this->channel()->waitForConfirm((float) $this->connectionOptions['confirm_timeout']);
|
||||
}
|
||||
}
|
||||
|
||||
private function setupDelay(int $delay, ?string $routingKey, bool $isRetryAttempt)
|
||||
{
|
||||
if ($this->autoSetupDelayExchange) {
|
||||
$this->setupDelayExchange();
|
||||
}
|
||||
|
||||
$queue = $this->createDelayQueue($delay, $routingKey, $isRetryAttempt);
|
||||
$queue->declareQueue(); // the delay queue always need to be declared because the name is dynamic and cannot be declared in advance
|
||||
$queue->bind($this->connectionOptions['delay']['exchange_name'], $this->getRoutingKeyForDelay($delay, $routingKey, $isRetryAttempt));
|
||||
}
|
||||
|
||||
private function getDelayExchange(): \AMQPExchange
|
||||
{
|
||||
if (null === $this->amqpDelayExchange) {
|
||||
$this->amqpDelayExchange = $this->amqpFactory->createExchange($this->channel());
|
||||
$this->amqpDelayExchange->setName($this->connectionOptions['delay']['exchange_name']);
|
||||
$this->amqpDelayExchange->setType(\AMQP_EX_TYPE_DIRECT);
|
||||
$this->amqpDelayExchange->setFlags(\AMQP_DURABLE);
|
||||
}
|
||||
|
||||
return $this->amqpDelayExchange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a delay queue that will delay for a certain amount of time.
|
||||
*
|
||||
* This works by setting message TTL for the delay and pointing
|
||||
* the dead letter exchange to the original exchange. The result
|
||||
* is that after the TTL, the message is sent to the dead-letter-exchange,
|
||||
* which is the original exchange, resulting on it being put back into
|
||||
* the original queue.
|
||||
*/
|
||||
private function createDelayQueue(int $delay, ?string $routingKey, bool $isRetryAttempt): \AMQPQueue
|
||||
{
|
||||
$queue = $this->amqpFactory->createQueue($this->channel());
|
||||
$queue->setName($this->getRoutingKeyForDelay($delay, $routingKey, $isRetryAttempt));
|
||||
$queue->setFlags(\AMQP_DURABLE);
|
||||
$queue->setArguments([
|
||||
'x-message-ttl' => $delay,
|
||||
// delete the delay queue 10 seconds after the message expires
|
||||
// publishing another message redeclares the queue which renews the lease
|
||||
'x-expires' => $delay + 10000,
|
||||
// message should be broadcasted to all consumers during delay, but to only one queue during retry
|
||||
// empty name is default direct exchange
|
||||
'x-dead-letter-exchange' => $isRetryAttempt ? '' : $this->exchangeOptions['name'],
|
||||
// after being released from to DLX, make sure the original routing key will be used
|
||||
// we must use an empty string instead of null for the argument to be picked up
|
||||
'x-dead-letter-routing-key' => $routingKey ?? '',
|
||||
]);
|
||||
|
||||
return $queue;
|
||||
}
|
||||
|
||||
private function getRoutingKeyForDelay(int $delay, ?string $finalRoutingKey, bool $isRetryAttempt): string
|
||||
{
|
||||
$action = $isRetryAttempt ? '_retry' : '_delay';
|
||||
|
||||
return str_replace(
|
||||
['%delay%', '%exchange_name%', '%routing_key%'],
|
||||
[$delay, $this->exchangeOptions['name'], $finalRoutingKey ?? ''],
|
||||
$this->connectionOptions['delay']['queue_name_pattern']
|
||||
).$action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a message from the specified queue.
|
||||
*
|
||||
* @throws \AMQPException
|
||||
*/
|
||||
public function get(string $queueName): ?\AMQPEnvelope
|
||||
{
|
||||
$this->clearWhenDisconnected();
|
||||
|
||||
if ($this->autoSetupExchange) {
|
||||
$this->setupExchangeAndQueues();
|
||||
}
|
||||
|
||||
if (false !== $message = $this->queue($queueName)->get()) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function ack(\AMQPEnvelope $message, string $queueName): bool
|
||||
{
|
||||
return $this->queue($queueName)->ack($message->getDeliveryTag());
|
||||
}
|
||||
|
||||
public function nack(\AMQPEnvelope $message, string $queueName, int $flags = \AMQP_NOPARAM): bool
|
||||
{
|
||||
return $this->queue($queueName)->nack($message->getDeliveryTag(), $flags);
|
||||
}
|
||||
|
||||
public function setup(): void
|
||||
{
|
||||
$this->setupExchangeAndQueues();
|
||||
$this->setupDelayExchange();
|
||||
}
|
||||
|
||||
private function setupExchangeAndQueues(): void
|
||||
{
|
||||
$this->exchange()->declareExchange();
|
||||
|
||||
foreach ($this->queuesOptions as $queueName => $queueConfig) {
|
||||
$this->queue($queueName)->declareQueue();
|
||||
foreach ($queueConfig['binding_keys'] ?? [null] as $bindingKey) {
|
||||
$this->queue($queueName)->bind($this->exchangeOptions['name'], $bindingKey, $queueConfig['binding_arguments'] ?? []);
|
||||
}
|
||||
}
|
||||
$this->autoSetupExchange = false;
|
||||
}
|
||||
|
||||
private function setupDelayExchange(): void
|
||||
{
|
||||
$this->getDelayExchange()->declareExchange();
|
||||
$this->autoSetupDelayExchange = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getQueueNames(): array
|
||||
{
|
||||
return array_keys($this->queuesOptions);
|
||||
}
|
||||
|
||||
public function channel(): \AMQPChannel
|
||||
{
|
||||
if (null === $this->amqpChannel) {
|
||||
$connection = $this->amqpFactory->createConnection($this->connectionOptions);
|
||||
$connectMethod = 'true' === ($this->connectionOptions['persistent'] ?? 'false') ? 'pconnect' : 'connect';
|
||||
|
||||
try {
|
||||
$connection->{$connectMethod}();
|
||||
} catch (\AMQPConnectionException $e) {
|
||||
throw new \AMQPException('Could not connect to the AMQP server. Please verify the provided DSN.', 0, $e);
|
||||
}
|
||||
$this->amqpChannel = $this->amqpFactory->createChannel($connection);
|
||||
|
||||
if ('' !== ($this->connectionOptions['confirm_timeout'] ?? '')) {
|
||||
$this->amqpChannel->confirmSelect();
|
||||
$this->amqpChannel->setConfirmCallback(
|
||||
static function (): bool {
|
||||
return false;
|
||||
},
|
||||
static function (): bool {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$this->lastActivityTime = time();
|
||||
} elseif (0 < ($this->connectionOptions['heartbeat'] ?? 0) && time() > $this->lastActivityTime + 2 * $this->connectionOptions['heartbeat']) {
|
||||
$disconnectMethod = 'true' === ($this->connectionOptions['persistent'] ?? 'false') ? 'pdisconnect' : 'disconnect';
|
||||
$this->amqpChannel->getConnection()->{$disconnectMethod}();
|
||||
}
|
||||
|
||||
return $this->amqpChannel;
|
||||
}
|
||||
|
||||
public function queue(string $queueName): \AMQPQueue
|
||||
{
|
||||
if (!isset($this->amqpQueues[$queueName])) {
|
||||
$queueConfig = $this->queuesOptions[$queueName];
|
||||
|
||||
$amqpQueue = $this->amqpFactory->createQueue($this->channel());
|
||||
$amqpQueue->setName($queueName);
|
||||
$amqpQueue->setFlags($queueConfig['flags'] ?? \AMQP_DURABLE);
|
||||
|
||||
if (isset($queueConfig['arguments'])) {
|
||||
$amqpQueue->setArguments($queueConfig['arguments']);
|
||||
}
|
||||
|
||||
$this->amqpQueues[$queueName] = $amqpQueue;
|
||||
}
|
||||
|
||||
return $this->amqpQueues[$queueName];
|
||||
}
|
||||
|
||||
public function exchange(): \AMQPExchange
|
||||
{
|
||||
if (null === $this->amqpExchange) {
|
||||
$this->amqpExchange = $this->amqpFactory->createExchange($this->channel());
|
||||
$this->amqpExchange->setName($this->exchangeOptions['name']);
|
||||
$this->amqpExchange->setType($this->exchangeOptions['type'] ?? \AMQP_EX_TYPE_FANOUT);
|
||||
$this->amqpExchange->setFlags($this->exchangeOptions['flags'] ?? \AMQP_DURABLE);
|
||||
|
||||
if (isset($this->exchangeOptions['arguments'])) {
|
||||
$this->amqpExchange->setArguments($this->exchangeOptions['arguments']);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->amqpExchange;
|
||||
}
|
||||
|
||||
private function clearWhenDisconnected(): void
|
||||
{
|
||||
if (!$this->channel()->isConnected()) {
|
||||
$this->amqpChannel = null;
|
||||
$this->amqpQueues = [];
|
||||
$this->amqpExchange = null;
|
||||
$this->amqpDelayExchange = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultPublishRoutingKey(): ?string
|
||||
{
|
||||
return $this->exchangeOptions['default_publish_routing_key'] ?? null;
|
||||
}
|
||||
|
||||
public function purgeQueues()
|
||||
{
|
||||
foreach ($this->getQueueNames() as $queueName) {
|
||||
$this->queue($queueName)->purge();
|
||||
}
|
||||
}
|
||||
|
||||
private function getRoutingKeyForMessage(?AmqpStamp $amqpStamp): ?string
|
||||
{
|
||||
return (null !== $amqpStamp ? $amqpStamp->getRoutingKey() : null) ?? $this->getDefaultPublishRoutingKey();
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\Connection::class, false)) {
|
||||
class_alias(Connection::class, \Symfony\Component\Messenger\Transport\AmqpExt\Connection::class);
|
||||
}
|
36
vendor/symfony/amqp-messenger/composer.json
vendored
Normal file
36
vendor/symfony/amqp-messenger/composer.json
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "symfony/amqp-messenger",
|
||||
"type": "symfony-messenger-bridge",
|
||||
"description": "Symfony AMQP extension Messenger Bridge",
|
||||
"keywords": [],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/deprecation-contracts": "^2.1|^3",
|
||||
"symfony/messenger": "^5.3|^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/event-dispatcher": "^4.4|^5.0|^6.0",
|
||||
"symfony/process": "^4.4|^5.0|^6.0",
|
||||
"symfony/property-access": "^4.4|^5.0|^6.0",
|
||||
"symfony/serializer": "^4.4|^5.0|^6.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Amqp\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
33
vendor/symfony/asset/CHANGELOG.md
vendored
Normal file
33
vendor/symfony/asset/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* deprecated `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead.
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* added `RemoteJsonManifestVersionStrategy` to download manifest over HTTP.
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* added different protocols to be allowed as asset base_urls
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* added optional arguments `$basePath` and `$secure` in `RequestStackContext::__construct()`
|
||||
to provide a default request context in case the stack is empty
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
* Added `JsonManifestVersionStrategy` as a way to read final,
|
||||
versioned paths from a JSON manifest file.
|
||||
|
||||
2.7.0
|
||||
-----
|
||||
|
||||
* added the component
|
34
vendor/symfony/asset/Context/ContextInterface.php
vendored
Normal file
34
vendor/symfony/asset/Context/ContextInterface.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Context;
|
||||
|
||||
/**
|
||||
* Holds information about the current request.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ContextInterface
|
||||
{
|
||||
/**
|
||||
* Gets the base path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBasePath();
|
||||
|
||||
/**
|
||||
* Checks whether the request is secure or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSecure();
|
||||
}
|
36
vendor/symfony/asset/Context/NullContext.php
vendored
Normal file
36
vendor/symfony/asset/Context/NullContext.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Context;
|
||||
|
||||
/**
|
||||
* A context that does nothing.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class NullContext implements ContextInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBasePath()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSecure()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
57
vendor/symfony/asset/Context/RequestStackContext.php
vendored
Normal file
57
vendor/symfony/asset/Context/RequestStackContext.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Context;
|
||||
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Uses a RequestStack to populate the context.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class RequestStackContext implements ContextInterface
|
||||
{
|
||||
private $requestStack;
|
||||
private $basePath;
|
||||
private $secure;
|
||||
|
||||
public function __construct(RequestStack $requestStack, string $basePath = '', bool $secure = false)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
$this->basePath = $basePath;
|
||||
$this->secure = $secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBasePath()
|
||||
{
|
||||
if (!$request = $this->requestStack->getMainRequest()) {
|
||||
return $this->basePath;
|
||||
}
|
||||
|
||||
return $request->getBasePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSecure()
|
||||
{
|
||||
if (!$request = $this->requestStack->getMainRequest()) {
|
||||
return $this->secure;
|
||||
}
|
||||
|
||||
return $request->isSecure();
|
||||
}
|
||||
}
|
38
vendor/symfony/asset/Exception/AssetNotFoundException.php
vendored
Normal file
38
vendor/symfony/asset/Exception/AssetNotFoundException.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Represents an asset not found in a manifest.
|
||||
*/
|
||||
class AssetNotFoundException extends RuntimeException
|
||||
{
|
||||
private $alternatives;
|
||||
|
||||
/**
|
||||
* @param string $message Exception message to throw
|
||||
* @param array $alternatives List of similar defined names
|
||||
* @param int $code Exception code
|
||||
* @param \Throwable $previous Previous exception used for the exception chaining
|
||||
*/
|
||||
public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->alternatives = $alternatives;
|
||||
}
|
||||
|
||||
public function getAlternatives(): array
|
||||
{
|
||||
return $this->alternatives;
|
||||
}
|
||||
}
|
21
vendor/symfony/asset/Exception/ExceptionInterface.php
vendored
Normal file
21
vendor/symfony/asset/Exception/ExceptionInterface.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Base ExceptionInterface for the Asset component.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
21
vendor/symfony/asset/Exception/InvalidArgumentException.php
vendored
Normal file
21
vendor/symfony/asset/Exception/InvalidArgumentException.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Base InvalidArgumentException for the Asset component.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
21
vendor/symfony/asset/Exception/LogicException.php
vendored
Normal file
21
vendor/symfony/asset/Exception/LogicException.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Base LogicException for the Asset component.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class LogicException extends \LogicException implements ExceptionInterface
|
||||
{
|
||||
}
|
19
vendor/symfony/asset/Exception/RuntimeException.php
vendored
Normal file
19
vendor/symfony/asset/Exception/RuntimeException.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Base RuntimeException for the Asset component.
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
19
vendor/symfony/asset/LICENSE
vendored
Normal file
19
vendor/symfony/asset/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2022 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.
|
78
vendor/symfony/asset/Package.php
vendored
Normal file
78
vendor/symfony/asset/Package.php
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset;
|
||||
|
||||
use Symfony\Component\Asset\Context\ContextInterface;
|
||||
use Symfony\Component\Asset\Context\NullContext;
|
||||
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
|
||||
|
||||
/**
|
||||
* Basic package that adds a version to asset URLs.
|
||||
*
|
||||
* @author Kris Wallsmith <kris@symfony.com>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Package implements PackageInterface
|
||||
{
|
||||
private $versionStrategy;
|
||||
private $context;
|
||||
|
||||
public function __construct(VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
|
||||
{
|
||||
$this->versionStrategy = $versionStrategy;
|
||||
$this->context = $context ?? new NullContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVersion(string $path)
|
||||
{
|
||||
return $this->versionStrategy->getVersion($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl(string $path)
|
||||
{
|
||||
if ($this->isAbsoluteUrl($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return $this->versionStrategy->applyVersion($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContextInterface
|
||||
*/
|
||||
protected function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return VersionStrategyInterface
|
||||
*/
|
||||
protected function getVersionStrategy()
|
||||
{
|
||||
return $this->versionStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAbsoluteUrl(string $url)
|
||||
{
|
||||
return str_contains($url, '://') || '//' === substr($url, 0, 2);
|
||||
}
|
||||
}
|
34
vendor/symfony/asset/PackageInterface.php
vendored
Normal file
34
vendor/symfony/asset/PackageInterface.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset;
|
||||
|
||||
/**
|
||||
* Asset package interface.
|
||||
*
|
||||
* @author Kris Wallsmith <kris@symfony.com>
|
||||
*/
|
||||
interface PackageInterface
|
||||
{
|
||||
/**
|
||||
* Returns the asset version for an asset.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion(string $path);
|
||||
|
||||
/**
|
||||
* Returns an absolute or root-relative public path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl(string $path);
|
||||
}
|
104
vendor/symfony/asset/Packages.php
vendored
Normal file
104
vendor/symfony/asset/Packages.php
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset;
|
||||
|
||||
use Symfony\Component\Asset\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Asset\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Helps manage asset URLs.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Kris Wallsmith <kris@symfony.com>
|
||||
*/
|
||||
class Packages
|
||||
{
|
||||
private $defaultPackage;
|
||||
private $packages = [];
|
||||
|
||||
/**
|
||||
* @param PackageInterface[] $packages Additional packages indexed by name
|
||||
*/
|
||||
public function __construct(PackageInterface $defaultPackage = null, iterable $packages = [])
|
||||
{
|
||||
$this->defaultPackage = $defaultPackage;
|
||||
|
||||
foreach ($packages as $name => $package) {
|
||||
$this->addPackage($name, $package);
|
||||
}
|
||||
}
|
||||
|
||||
public function setDefaultPackage(PackageInterface $defaultPackage)
|
||||
{
|
||||
$this->defaultPackage = $defaultPackage;
|
||||
}
|
||||
|
||||
public function addPackage(string $name, PackageInterface $package)
|
||||
{
|
||||
$this->packages[$name] = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an asset package.
|
||||
*
|
||||
* @param string $name The name of the package or null for the default package
|
||||
*
|
||||
* @return PackageInterface
|
||||
*
|
||||
* @throws InvalidArgumentException If there is no package by that name
|
||||
* @throws LogicException If no default package is defined
|
||||
*/
|
||||
public function getPackage(string $name = null)
|
||||
{
|
||||
if (null === $name) {
|
||||
if (null === $this->defaultPackage) {
|
||||
throw new LogicException('There is no default asset package, configure one first.');
|
||||
}
|
||||
|
||||
return $this->defaultPackage;
|
||||
}
|
||||
|
||||
if (!isset($this->packages[$name])) {
|
||||
throw new InvalidArgumentException(sprintf('There is no "%s" asset package.', $name));
|
||||
}
|
||||
|
||||
return $this->packages[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version to add to public URL.
|
||||
*
|
||||
* @param string $path A public path
|
||||
* @param string $packageName A package name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion(string $path, string $packageName = null)
|
||||
{
|
||||
return $this->getPackage($packageName)->getVersion($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public path.
|
||||
*
|
||||
* Absolute paths (i.e. http://...) are returned unmodified.
|
||||
*
|
||||
* @param string $path A public path
|
||||
* @param string $packageName The name of the asset package to use
|
||||
*
|
||||
* @return string A public path which takes into account the base path and URL path
|
||||
*/
|
||||
public function getUrl(string $path, string $packageName = null)
|
||||
{
|
||||
return $this->getPackage($packageName)->getUrl($path);
|
||||
}
|
||||
}
|
73
vendor/symfony/asset/PathPackage.php
vendored
Normal file
73
vendor/symfony/asset/PathPackage.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset;
|
||||
|
||||
use Symfony\Component\Asset\Context\ContextInterface;
|
||||
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
|
||||
|
||||
/**
|
||||
* Package that adds a base path to asset URLs in addition to a version.
|
||||
*
|
||||
* In addition to the provided base path, this package also automatically
|
||||
* prepends the current request base path if a Context is available to
|
||||
* allow a website to be hosted easily under any given path under the Web
|
||||
* Server root directory.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class PathPackage extends Package
|
||||
{
|
||||
private $basePath;
|
||||
|
||||
/**
|
||||
* @param string $basePath The base path to be prepended to relative paths
|
||||
*/
|
||||
public function __construct(string $basePath, VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
|
||||
{
|
||||
parent::__construct($versionStrategy, $context);
|
||||
|
||||
if (!$basePath) {
|
||||
$this->basePath = '/';
|
||||
} else {
|
||||
if ('/' != $basePath[0]) {
|
||||
$basePath = '/'.$basePath;
|
||||
}
|
||||
|
||||
$this->basePath = rtrim($basePath, '/').'/';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl(string $path)
|
||||
{
|
||||
$versionedPath = parent::getUrl($path);
|
||||
|
||||
// if absolute or begins with /, we're done
|
||||
if ($this->isAbsoluteUrl($versionedPath) || ($versionedPath && '/' === $versionedPath[0])) {
|
||||
return $versionedPath;
|
||||
}
|
||||
|
||||
return $this->getBasePath().ltrim($versionedPath, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBasePath()
|
||||
{
|
||||
return $this->getContext()->getBasePath().$this->basePath;
|
||||
}
|
||||
}
|
14
vendor/symfony/asset/README.md
vendored
Normal file
14
vendor/symfony/asset/README.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
Asset Component
|
||||
===============
|
||||
|
||||
The Asset component manages URL generation and versioning of web assets such as
|
||||
CSS stylesheets, JavaScript files and image files.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/asset/introduction.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)
|
133
vendor/symfony/asset/UrlPackage.php
vendored
Normal file
133
vendor/symfony/asset/UrlPackage.php
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset;
|
||||
|
||||
use Symfony\Component\Asset\Context\ContextInterface;
|
||||
use Symfony\Component\Asset\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Asset\Exception\LogicException;
|
||||
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
|
||||
|
||||
/**
|
||||
* Package that adds a base URL to asset URLs in addition to a version.
|
||||
*
|
||||
* The package allows to use more than one base URLs in which case
|
||||
* it randomly chooses one for each asset; it also guarantees that
|
||||
* any given path will always use the same base URL to be nice with
|
||||
* HTTP caching mechanisms.
|
||||
*
|
||||
* When the request context is available, this package can choose the
|
||||
* best base URL to use based on the current request scheme:
|
||||
*
|
||||
* * For HTTP request, it chooses between all base URLs;
|
||||
* * For HTTPs requests, it chooses between HTTPs base URLs and relative protocol URLs
|
||||
* or falls back to any base URL if no secure ones are available.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class UrlPackage extends Package
|
||||
{
|
||||
private $baseUrls = [];
|
||||
private $sslPackage;
|
||||
|
||||
/**
|
||||
* @param string|string[] $baseUrls Base asset URLs
|
||||
*/
|
||||
public function __construct($baseUrls, VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
|
||||
{
|
||||
parent::__construct($versionStrategy, $context);
|
||||
|
||||
if (!\is_array($baseUrls)) {
|
||||
$baseUrls = (array) $baseUrls;
|
||||
}
|
||||
|
||||
if (!$baseUrls) {
|
||||
throw new LogicException('You must provide at least one base URL.');
|
||||
}
|
||||
|
||||
foreach ($baseUrls as $baseUrl) {
|
||||
$this->baseUrls[] = rtrim($baseUrl, '/');
|
||||
}
|
||||
|
||||
$sslUrls = $this->getSslUrls($baseUrls);
|
||||
|
||||
if ($sslUrls && $baseUrls !== $sslUrls) {
|
||||
$this->sslPackage = new self($sslUrls, $versionStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl(string $path)
|
||||
{
|
||||
if ($this->isAbsoluteUrl($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if (null !== $this->sslPackage && $this->getContext()->isSecure()) {
|
||||
return $this->sslPackage->getUrl($path);
|
||||
}
|
||||
|
||||
$url = $this->getVersionStrategy()->applyVersion($path);
|
||||
|
||||
if ($this->isAbsoluteUrl($url)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ($url && '/' != $url[0]) {
|
||||
$url = '/'.$url;
|
||||
}
|
||||
|
||||
return $this->getBaseUrl($path).$url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base URL for a path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseUrl(string $path)
|
||||
{
|
||||
if (1 === \count($this->baseUrls)) {
|
||||
return $this->baseUrls[0];
|
||||
}
|
||||
|
||||
return $this->baseUrls[$this->chooseBaseUrl($path)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which base URL to use for the given path.
|
||||
*
|
||||
* Override this method to change the default distribution strategy.
|
||||
* This method should always return the same base URL index for a given path.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function chooseBaseUrl(string $path)
|
||||
{
|
||||
return (int) fmod(hexdec(substr(hash('sha256', $path), 0, 10)), \count($this->baseUrls));
|
||||
}
|
||||
|
||||
private function getSslUrls(array $urls)
|
||||
{
|
||||
$sslUrls = [];
|
||||
foreach ($urls as $url) {
|
||||
if ('https://' === substr($url, 0, 8) || '//' === substr($url, 0, 2)) {
|
||||
$sslUrls[] = $url;
|
||||
} elseif (null === parse_url($url, \PHP_URL_SCHEME)) {
|
||||
throw new InvalidArgumentException(sprintf('"%s" is not a valid URL.', $url));
|
||||
}
|
||||
}
|
||||
|
||||
return $sslUrls;
|
||||
}
|
||||
}
|
36
vendor/symfony/asset/VersionStrategy/EmptyVersionStrategy.php
vendored
Normal file
36
vendor/symfony/asset/VersionStrategy/EmptyVersionStrategy.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\VersionStrategy;
|
||||
|
||||
/**
|
||||
* Disable version for all assets.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class EmptyVersionStrategy implements VersionStrategyInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVersion(string $path)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyVersion(string $path)
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
}
|
132
vendor/symfony/asset/VersionStrategy/JsonManifestVersionStrategy.php
vendored
Normal file
132
vendor/symfony/asset/VersionStrategy/JsonManifestVersionStrategy.php
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\VersionStrategy;
|
||||
|
||||
use Symfony\Component\Asset\Exception\AssetNotFoundException;
|
||||
use Symfony\Component\Asset\Exception\LogicException;
|
||||
use Symfony\Component\Asset\Exception\RuntimeException;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* Reads the versioned path of an asset from a JSON manifest file.
|
||||
*
|
||||
* For example, the manifest file might look like this:
|
||||
* {
|
||||
* "main.js": "main.abc123.js",
|
||||
* "css/styles.css": "css/styles.555abc.css"
|
||||
* }
|
||||
*
|
||||
* You could then ask for the version of "main.js" or "css/styles.css".
|
||||
*/
|
||||
class JsonManifestVersionStrategy implements VersionStrategyInterface
|
||||
{
|
||||
private $manifestPath;
|
||||
private $manifestData;
|
||||
private $httpClient;
|
||||
private $strictMode;
|
||||
|
||||
/**
|
||||
* @param string $manifestPath Absolute path to the manifest file
|
||||
* @param bool $strictMode Throws an exception for unknown paths
|
||||
*/
|
||||
public function __construct(string $manifestPath, HttpClientInterface $httpClient = null, $strictMode = false)
|
||||
{
|
||||
$this->manifestPath = $manifestPath;
|
||||
$this->httpClient = $httpClient;
|
||||
$this->strictMode = $strictMode;
|
||||
|
||||
if (null === $this->httpClient && ($scheme = parse_url($this->manifestPath, \PHP_URL_SCHEME)) && 0 === strpos($scheme, 'http')) {
|
||||
throw new LogicException(sprintf('The "%s" class needs an HTTP client to use a remote manifest. Try running "composer require symfony/http-client".', self::class));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* With a manifest, we don't really know or care about what
|
||||
* the version is. Instead, this returns the path to the
|
||||
* versioned file.
|
||||
*/
|
||||
public function getVersion(string $path)
|
||||
{
|
||||
return $this->applyVersion($path);
|
||||
}
|
||||
|
||||
public function applyVersion(string $path)
|
||||
{
|
||||
return $this->getManifestPath($path) ?: $path;
|
||||
}
|
||||
|
||||
private function getManifestPath(string $path): ?string
|
||||
{
|
||||
if (null === $this->manifestData) {
|
||||
if (null !== $this->httpClient && ($scheme = parse_url($this->manifestPath, \PHP_URL_SCHEME)) && 0 === strpos($scheme, 'http')) {
|
||||
try {
|
||||
$this->manifestData = $this->httpClient->request('GET', $this->manifestPath, [
|
||||
'headers' => ['accept' => 'application/json'],
|
||||
])->toArray();
|
||||
} catch (DecodingExceptionInterface $e) {
|
||||
throw new RuntimeException(sprintf('Error parsing JSON from asset manifest URL "%s".', $this->manifestPath), 0, $e);
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
throw new RuntimeException(sprintf('Error loading JSON from asset manifest URL "%s".', $this->manifestPath), 0, $e);
|
||||
}
|
||||
} else {
|
||||
if (!is_file($this->manifestPath)) {
|
||||
throw new RuntimeException(sprintf('Asset manifest file "%s" does not exist. Did you forget to build the assets with npm or yarn?', $this->manifestPath));
|
||||
}
|
||||
|
||||
$this->manifestData = json_decode(file_get_contents($this->manifestPath), true);
|
||||
if (0 < json_last_error()) {
|
||||
throw new RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": ', $this->manifestPath).json_last_error_msg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->manifestData[$path])) {
|
||||
return $this->manifestData[$path];
|
||||
}
|
||||
|
||||
if ($this->strictMode) {
|
||||
$message = sprintf('Asset "%s" not found in manifest "%s".', $path, $this->manifestPath);
|
||||
$alternatives = $this->findAlternatives($path, $this->manifestData);
|
||||
if (\count($alternatives) > 0) {
|
||||
$message .= sprintf(' Did you mean one of these? "%s".', implode('", "', $alternatives));
|
||||
}
|
||||
|
||||
throw new AssetNotFoundException($message, $alternatives);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findAlternatives(string $path, array $manifestData): array
|
||||
{
|
||||
$path = strtolower($path);
|
||||
$alternatives = [];
|
||||
|
||||
foreach ($manifestData as $key => $value) {
|
||||
$lev = levenshtein($path, strtolower($key));
|
||||
if ($lev <= \strlen($path) / 3 || false !== stripos($key, $path)) {
|
||||
$alternatives[$key] = isset($alternatives[$key]) ? min($lev, $alternatives[$key]) : $lev;
|
||||
}
|
||||
|
||||
$lev = levenshtein($path, strtolower($value));
|
||||
if ($lev <= \strlen($path) / 3 || false !== stripos($key, $path)) {
|
||||
$alternatives[$key] = isset($alternatives[$key]) ? min($lev, $alternatives[$key]) : $lev;
|
||||
}
|
||||
}
|
||||
|
||||
asort($alternatives);
|
||||
|
||||
return array_keys($alternatives);
|
||||
}
|
||||
}
|
66
vendor/symfony/asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php
vendored
Normal file
66
vendor/symfony/asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\VersionStrategy;
|
||||
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
trigger_deprecation('symfony/asset', '5.3', 'The "%s" class is deprecated, use "%s" instead.', RemoteJsonManifestVersionStrategy::class, JsonManifestVersionStrategy::class);
|
||||
|
||||
/**
|
||||
* Reads the versioned path of an asset from a remote JSON manifest file.
|
||||
*
|
||||
* For example, the manifest file might look like this:
|
||||
* {
|
||||
* "main.js": "main.abc123.js",
|
||||
* "css/styles.css": "css/styles.555abc.css"
|
||||
* }
|
||||
*
|
||||
* You could then ask for the version of "main.js" or "css/styles.css".
|
||||
*
|
||||
* @deprecated since Symfony 5.3, use JsonManifestVersionStrategy instead.
|
||||
*/
|
||||
class RemoteJsonManifestVersionStrategy implements VersionStrategyInterface
|
||||
{
|
||||
private $manifestData;
|
||||
private $manifestUrl;
|
||||
private $httpClient;
|
||||
|
||||
/**
|
||||
* @param string $manifestUrl Absolute URL to the manifest file
|
||||
*/
|
||||
public function __construct(string $manifestUrl, HttpClientInterface $httpClient)
|
||||
{
|
||||
$this->manifestUrl = $manifestUrl;
|
||||
$this->httpClient = $httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a manifest, we don't really know or care about what
|
||||
* the version is. Instead, this returns the path to the
|
||||
* versioned file.
|
||||
*/
|
||||
public function getVersion(string $path)
|
||||
{
|
||||
return $this->applyVersion($path);
|
||||
}
|
||||
|
||||
public function applyVersion(string $path)
|
||||
{
|
||||
if (null === $this->manifestData) {
|
||||
$this->manifestData = $this->httpClient->request('GET', $this->manifestUrl, [
|
||||
'headers' => ['accept' => 'application/json'],
|
||||
])->toArray();
|
||||
}
|
||||
|
||||
return $this->manifestData[$path] ?? $path;
|
||||
}
|
||||
}
|
55
vendor/symfony/asset/VersionStrategy/StaticVersionStrategy.php
vendored
Normal file
55
vendor/symfony/asset/VersionStrategy/StaticVersionStrategy.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\VersionStrategy;
|
||||
|
||||
/**
|
||||
* Returns the same version for all assets.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class StaticVersionStrategy implements VersionStrategyInterface
|
||||
{
|
||||
private $version;
|
||||
private $format;
|
||||
|
||||
/**
|
||||
* @param string $version Version number
|
||||
* @param string $format Url format
|
||||
*/
|
||||
public function __construct(string $version, string $format = null)
|
||||
{
|
||||
$this->version = $version;
|
||||
$this->format = $format ?: '%s?%s';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVersion(string $path)
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyVersion(string $path)
|
||||
{
|
||||
$versionized = sprintf($this->format, ltrim($path, '/'), $this->getVersion($path));
|
||||
|
||||
if ($path && '/' === $path[0]) {
|
||||
return '/'.$versionized;
|
||||
}
|
||||
|
||||
return $versionized;
|
||||
}
|
||||
}
|
34
vendor/symfony/asset/VersionStrategy/VersionStrategyInterface.php
vendored
Normal file
34
vendor/symfony/asset/VersionStrategy/VersionStrategyInterface.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\VersionStrategy;
|
||||
|
||||
/**
|
||||
* Asset version strategy interface.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface VersionStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Returns the asset version for an asset.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion(string $path);
|
||||
|
||||
/**
|
||||
* Applies version to the supplied path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function applyVersion(string $path);
|
||||
}
|
41
vendor/symfony/asset/composer.json
vendored
Normal file
41
vendor/symfony/asset/composer.json
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "symfony/asset",
|
||||
"type": "library",
|
||||
"description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files",
|
||||
"keywords": [],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/deprecation-contracts": "^2.1|^3",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/http-foundation": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-client": "^4.4|^5.0|^6.0",
|
||||
"symfony/http-foundation": "^5.3|^6.0",
|
||||
"symfony/http-kernel": "^4.4|^5.0|^6.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-foundation": "<5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Asset\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
715
vendor/symfony/browser-kit/AbstractBrowser.php
vendored
Normal file
715
vendor/symfony/browser-kit/AbstractBrowser.php
vendored
Normal file
@ -0,0 +1,715 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\BadMethodCallException;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\DomCrawler\Form;
|
||||
use Symfony\Component\DomCrawler\Link;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
|
||||
/**
|
||||
* Simulates a browser.
|
||||
*
|
||||
* To make the actual request, you need to implement the doRequest() method.
|
||||
*
|
||||
* If you want to be able to run requests in their own process (insulated flag),
|
||||
* you need to also implement the getScript() method.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class AbstractBrowser
|
||||
{
|
||||
protected $history;
|
||||
protected $cookieJar;
|
||||
protected $server = [];
|
||||
protected $internalRequest;
|
||||
protected $request;
|
||||
protected $internalResponse;
|
||||
protected $response;
|
||||
protected $crawler;
|
||||
protected $insulated = false;
|
||||
protected $redirect;
|
||||
protected $followRedirects = true;
|
||||
protected $followMetaRefresh = false;
|
||||
|
||||
private $maxRedirects = -1;
|
||||
private $redirectCount = 0;
|
||||
private $redirects = [];
|
||||
private $isMainRequest = true;
|
||||
|
||||
/**
|
||||
* @param array $server The server parameters (equivalent of $_SERVER)
|
||||
*/
|
||||
public function __construct(array $server = [], History $history = null, CookieJar $cookieJar = null)
|
||||
{
|
||||
$this->setServerParameters($server);
|
||||
$this->history = $history ?? new History();
|
||||
$this->cookieJar = $cookieJar ?? new CookieJar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to automatically follow redirects or not.
|
||||
*/
|
||||
public function followRedirects(bool $followRedirects = true)
|
||||
{
|
||||
$this->followRedirects = $followRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to automatically follow meta refresh redirects or not.
|
||||
*/
|
||||
public function followMetaRefresh(bool $followMetaRefresh = true)
|
||||
{
|
||||
$this->followMetaRefresh = $followMetaRefresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether client automatically follows redirects or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFollowingRedirects()
|
||||
{
|
||||
return $this->followRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of redirects that crawler can follow.
|
||||
*/
|
||||
public function setMaxRedirects(int $maxRedirects)
|
||||
{
|
||||
$this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects;
|
||||
$this->followRedirects = -1 != $this->maxRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of redirects that crawler can follow.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxRedirects()
|
||||
{
|
||||
return $this->maxRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insulated flag.
|
||||
*
|
||||
* @throws \RuntimeException When Symfony Process Component is not installed
|
||||
*/
|
||||
public function insulate(bool $insulated = true)
|
||||
{
|
||||
if ($insulated && !class_exists(\Symfony\Component\Process\Process::class)) {
|
||||
throw new \LogicException('Unable to isolate requests as the Symfony Process Component is not installed.');
|
||||
}
|
||||
|
||||
$this->insulated = $insulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets server parameters.
|
||||
*/
|
||||
public function setServerParameters(array $server)
|
||||
{
|
||||
$this->server = array_merge([
|
||||
'HTTP_USER_AGENT' => 'Symfony BrowserKit',
|
||||
], $server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets single server parameter.
|
||||
*/
|
||||
public function setServerParameter(string $key, string $value)
|
||||
{
|
||||
$this->server[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets single server parameter for specified key.
|
||||
*
|
||||
* @param mixed $default A default value when key is undefined
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getServerParameter(string $key, $default = '')
|
||||
{
|
||||
return $this->server[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function xmlHttpRequest(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], string $content = null, bool $changeHistory = true): Crawler
|
||||
{
|
||||
$this->setServerParameter('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest');
|
||||
|
||||
try {
|
||||
return $this->request($method, $uri, $parameters, $files, $server, $content, $changeHistory);
|
||||
} finally {
|
||||
unset($this->server['HTTP_X_REQUESTED_WITH']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the request parameters into a JSON string and uses it as request content.
|
||||
*/
|
||||
public function jsonRequest(string $method, string $uri, array $parameters = [], array $server = [], bool $changeHistory = true): Crawler
|
||||
{
|
||||
$content = json_encode($parameters);
|
||||
|
||||
$this->setServerParameter('CONTENT_TYPE', 'application/json');
|
||||
$this->setServerParameter('HTTP_ACCEPT', 'application/json');
|
||||
|
||||
try {
|
||||
return $this->request($method, $uri, [], [], $server, $content, $changeHistory);
|
||||
} finally {
|
||||
unset($this->server['CONTENT_TYPE']);
|
||||
unset($this->server['HTTP_ACCEPT']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the History instance.
|
||||
*
|
||||
* @return History
|
||||
*/
|
||||
public function getHistory()
|
||||
{
|
||||
return $this->history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CookieJar instance.
|
||||
*
|
||||
* @return CookieJar
|
||||
*/
|
||||
public function getCookieJar()
|
||||
{
|
||||
return $this->cookieJar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Crawler instance.
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
public function getCrawler()
|
||||
{
|
||||
if (null === $this->crawler) {
|
||||
throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
return $this->crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BrowserKit Response instance.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getInternalResponse()
|
||||
{
|
||||
if (null === $this->internalResponse) {
|
||||
throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
return $this->internalResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current origin response instance.
|
||||
*
|
||||
* The origin response is the response instance that is returned
|
||||
* by the code that handles requests.
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @see doRequest()
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
if (null === $this->response) {
|
||||
throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BrowserKit Request instance.
|
||||
*
|
||||
* @return Request
|
||||
*/
|
||||
public function getInternalRequest()
|
||||
{
|
||||
if (null === $this->internalRequest) {
|
||||
throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
return $this->internalRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current origin Request instance.
|
||||
*
|
||||
* The origin request is the request instance that is sent
|
||||
* to the code that handles requests.
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @see doRequest()
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
if (null === $this->request) {
|
||||
throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on a given link.
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
public function click(Link $link)
|
||||
{
|
||||
if ($link instanceof Form) {
|
||||
return $this->submit($link);
|
||||
}
|
||||
|
||||
return $this->request($link->getMethod(), $link->getUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the first link (or clickable image) that contains the given text.
|
||||
*
|
||||
* @param string $linkText The text of the link or the alt attribute of the clickable image
|
||||
*/
|
||||
public function clickLink(string $linkText): Crawler
|
||||
{
|
||||
if (null === $this->crawler) {
|
||||
throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
return $this->click($this->crawler->selectLink($linkText)->link());
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a form.
|
||||
*
|
||||
* @param array $values An array of form field values
|
||||
* @param array $serverParameters An array of server parameters
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
public function submit(Form $form, array $values = [], array $serverParameters = [])
|
||||
{
|
||||
$form->setValues($values);
|
||||
|
||||
return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles(), $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first form that contains a button with the given content and
|
||||
* uses it to submit the given form field values.
|
||||
*
|
||||
* @param string $button The text content, id, value or name of the form <button> or <input type="submit">
|
||||
* @param array $fieldValues Use this syntax: ['my_form[name]' => '...', 'my_form[email]' => '...']
|
||||
* @param string $method The HTTP method used to submit the form
|
||||
* @param array $serverParameters These values override the ones stored in $_SERVER (HTTP headers must include an HTTP_ prefix as PHP does)
|
||||
*/
|
||||
public function submitForm(string $button, array $fieldValues = [], string $method = 'POST', array $serverParameters = []): Crawler
|
||||
{
|
||||
if (null === $this->crawler) {
|
||||
throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
$buttonNode = $this->crawler->selectButton($button);
|
||||
$form = $buttonNode->form($fieldValues, $method);
|
||||
|
||||
return $this->submit($form, [], $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a URI.
|
||||
*
|
||||
* @param string $method The request method
|
||||
* @param string $uri The URI to fetch
|
||||
* @param array $parameters The Request parameters
|
||||
* @param array $files The files
|
||||
* @param array $server The server parameters (HTTP headers are referenced with an HTTP_ prefix as PHP does)
|
||||
* @param string $content The raw body data
|
||||
* @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
public function request(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], string $content = null, bool $changeHistory = true)
|
||||
{
|
||||
if ($this->isMainRequest) {
|
||||
$this->redirectCount = 0;
|
||||
} else {
|
||||
++$this->redirectCount;
|
||||
}
|
||||
|
||||
$originalUri = $uri;
|
||||
|
||||
$uri = $this->getAbsoluteUri($uri);
|
||||
|
||||
$server = array_merge($this->server, $server);
|
||||
|
||||
if (!empty($server['HTTP_HOST']) && null === parse_url($originalUri, \PHP_URL_HOST)) {
|
||||
$uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri);
|
||||
}
|
||||
|
||||
if (isset($server['HTTPS']) && null === parse_url($originalUri, \PHP_URL_SCHEME)) {
|
||||
$uri = preg_replace('{^'.parse_url($uri, \PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri);
|
||||
}
|
||||
|
||||
if (!isset($server['HTTP_REFERER']) && !$this->history->isEmpty()) {
|
||||
$server['HTTP_REFERER'] = $this->history->current()->getUri();
|
||||
}
|
||||
|
||||
if (empty($server['HTTP_HOST'])) {
|
||||
$server['HTTP_HOST'] = $this->extractHost($uri);
|
||||
}
|
||||
|
||||
$server['HTTPS'] = 'https' == parse_url($uri, \PHP_URL_SCHEME);
|
||||
|
||||
$this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
|
||||
|
||||
$this->request = $this->filterRequest($this->internalRequest);
|
||||
|
||||
if (true === $changeHistory) {
|
||||
$this->history->add($this->internalRequest);
|
||||
}
|
||||
|
||||
if ($this->insulated) {
|
||||
$this->response = $this->doRequestInProcess($this->request);
|
||||
} else {
|
||||
$this->response = $this->doRequest($this->request);
|
||||
}
|
||||
|
||||
$this->internalResponse = $this->filterResponse($this->response);
|
||||
|
||||
$this->cookieJar->updateFromResponse($this->internalResponse, $uri);
|
||||
|
||||
$status = $this->internalResponse->getStatusCode();
|
||||
|
||||
if ($status >= 300 && $status < 400) {
|
||||
$this->redirect = $this->internalResponse->getHeader('Location');
|
||||
} else {
|
||||
$this->redirect = null;
|
||||
}
|
||||
|
||||
if ($this->followRedirects && $this->redirect) {
|
||||
$this->redirects[serialize($this->history->current())] = true;
|
||||
|
||||
return $this->crawler = $this->followRedirect();
|
||||
}
|
||||
|
||||
$this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type') ?? '');
|
||||
|
||||
// Check for meta refresh redirect
|
||||
if ($this->followMetaRefresh && null !== $redirect = $this->getMetaRefreshUrl()) {
|
||||
$this->redirect = $redirect;
|
||||
$this->redirects[serialize($this->history->current())] = true;
|
||||
$this->crawler = $this->followRedirect();
|
||||
}
|
||||
|
||||
return $this->crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request in another process.
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @throws \RuntimeException When processing returns exit code
|
||||
*/
|
||||
protected function doRequestInProcess(object $request)
|
||||
{
|
||||
$deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec');
|
||||
putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$deprecationsFile);
|
||||
$_ENV['SYMFONY_DEPRECATIONS_SERIALIZE'] = $deprecationsFile;
|
||||
$process = new PhpProcess($this->getScript($request), null, null);
|
||||
$process->run();
|
||||
|
||||
if (file_exists($deprecationsFile)) {
|
||||
$deprecations = file_get_contents($deprecationsFile);
|
||||
unlink($deprecationsFile);
|
||||
foreach ($deprecations ? unserialize($deprecations) : [] as $deprecation) {
|
||||
if ($deprecation[0]) {
|
||||
// unsilenced on purpose
|
||||
trigger_error($deprecation[1], \E_USER_DEPRECATED);
|
||||
} else {
|
||||
@trigger_error($deprecation[1], \E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) {
|
||||
throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s.', $process->getOutput(), $process->getErrorOutput()));
|
||||
}
|
||||
|
||||
return unserialize($process->getOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
abstract protected function doRequest(object $request);
|
||||
|
||||
/**
|
||||
* Returns the script to execute when the request must be insulated.
|
||||
*
|
||||
* @param object $request An origin request instance
|
||||
*
|
||||
* @throws \LogicException When this abstract class is not implemented
|
||||
*/
|
||||
protected function getScript(object $request)
|
||||
{
|
||||
throw new \LogicException('To insulate requests, you need to override the getScript() method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the BrowserKit request to the origin one.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
protected function filterRequest(Request $request)
|
||||
{
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the origin response to the BrowserKit one.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function filterResponse(object $response)
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a crawler.
|
||||
*
|
||||
* This method returns null if the DomCrawler component is not available.
|
||||
*
|
||||
* @return Crawler|null
|
||||
*/
|
||||
protected function createCrawlerFromContent(string $uri, string $content, string $type)
|
||||
{
|
||||
if (!class_exists(Crawler::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$crawler = new Crawler(null, $uri);
|
||||
$crawler->addContent($content, $type);
|
||||
|
||||
return $crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in the browser history.
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
public function back()
|
||||
{
|
||||
do {
|
||||
$request = $this->history->back();
|
||||
} while (\array_key_exists(serialize($request), $this->redirects));
|
||||
|
||||
return $this->requestFromRequest($request, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes forward in the browser history.
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
public function forward()
|
||||
{
|
||||
do {
|
||||
$request = $this->history->forward();
|
||||
} while (\array_key_exists(serialize($request), $this->redirects));
|
||||
|
||||
return $this->requestFromRequest($request, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the current browser.
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
public function reload()
|
||||
{
|
||||
return $this->requestFromRequest($this->history->current(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow redirects?
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @throws \LogicException If request was not a redirect
|
||||
*/
|
||||
public function followRedirect()
|
||||
{
|
||||
if (empty($this->redirect)) {
|
||||
throw new \LogicException('The request was not redirected.');
|
||||
}
|
||||
|
||||
if (-1 !== $this->maxRedirects) {
|
||||
if ($this->redirectCount > $this->maxRedirects) {
|
||||
$this->redirectCount = 0;
|
||||
throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects));
|
||||
}
|
||||
}
|
||||
|
||||
$request = $this->internalRequest;
|
||||
|
||||
if (\in_array($this->internalResponse->getStatusCode(), [301, 302, 303])) {
|
||||
$method = 'GET';
|
||||
$files = [];
|
||||
$content = null;
|
||||
} else {
|
||||
$method = $request->getMethod();
|
||||
$files = $request->getFiles();
|
||||
$content = $request->getContent();
|
||||
}
|
||||
|
||||
if ('GET' === strtoupper($method)) {
|
||||
// Don't forward parameters for GET request as it should reach the redirection URI
|
||||
$parameters = [];
|
||||
} else {
|
||||
$parameters = $request->getParameters();
|
||||
}
|
||||
|
||||
$server = $request->getServer();
|
||||
$server = $this->updateServerFromUri($server, $this->redirect);
|
||||
|
||||
$this->isMainRequest = false;
|
||||
|
||||
$response = $this->request($method, $this->redirect, $parameters, $files, $server, $content);
|
||||
|
||||
$this->isMainRequest = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://dev.w3.org/html5/spec-preview/the-meta-element.html#attr-meta-http-equiv-refresh
|
||||
*/
|
||||
private function getMetaRefreshUrl(): ?string
|
||||
{
|
||||
$metaRefresh = $this->getCrawler()->filter('head meta[http-equiv="refresh"]');
|
||||
foreach ($metaRefresh->extract(['content']) as $content) {
|
||||
if (preg_match('/^\s*0\s*;\s*URL\s*=\s*(?|\'([^\']++)|"([^"]++)|([^\'"].*))/i', $content, $m)) {
|
||||
return str_replace("\t\r\n", '', rtrim($m[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the client.
|
||||
*
|
||||
* It flushes history and all cookies.
|
||||
*/
|
||||
public function restart()
|
||||
{
|
||||
$this->cookieJar->clear();
|
||||
$this->history->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a URI and converts it to absolute if it is not already absolute.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getAbsoluteUri(string $uri)
|
||||
{
|
||||
// already absolute?
|
||||
if (0 === strpos($uri, 'http://') || 0 === strpos($uri, 'https://')) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
if (!$this->history->isEmpty()) {
|
||||
$currentUri = $this->history->current()->getUri();
|
||||
} else {
|
||||
$currentUri = sprintf('http%s://%s/',
|
||||
isset($this->server['HTTPS']) ? 's' : '',
|
||||
$this->server['HTTP_HOST'] ?? 'localhost'
|
||||
);
|
||||
}
|
||||
|
||||
// protocol relative URL
|
||||
if ('' !== trim($uri, '/') && str_starts_with($uri, '//')) {
|
||||
return parse_url($currentUri, \PHP_URL_SCHEME).':'.$uri;
|
||||
}
|
||||
|
||||
// anchor or query string parameters?
|
||||
if (!$uri || '#' == $uri[0] || '?' == $uri[0]) {
|
||||
return preg_replace('/[#?].*?$/', '', $currentUri).$uri;
|
||||
}
|
||||
|
||||
if ('/' !== $uri[0]) {
|
||||
$path = parse_url($currentUri, \PHP_URL_PATH);
|
||||
|
||||
if ('/' !== substr($path, -1)) {
|
||||
$path = substr($path, 0, strrpos($path, '/') + 1);
|
||||
}
|
||||
|
||||
$uri = $path.$uri;
|
||||
}
|
||||
|
||||
return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request from a Request object directly.
|
||||
*
|
||||
* @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
protected function requestFromRequest(Request $request, bool $changeHistory = true)
|
||||
{
|
||||
return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
|
||||
}
|
||||
|
||||
private function updateServerFromUri(array $server, string $uri): array
|
||||
{
|
||||
$server['HTTP_HOST'] = $this->extractHost($uri);
|
||||
$scheme = parse_url($uri, \PHP_URL_SCHEME);
|
||||
$server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme;
|
||||
unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']);
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
private function extractHost(string $uri): ?string
|
||||
{
|
||||
$host = parse_url($uri, \PHP_URL_HOST);
|
||||
|
||||
if ($port = parse_url($uri, \PHP_URL_PORT)) {
|
||||
return $host.':'.$port;
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
}
|
63
vendor/symfony/browser-kit/CHANGELOG.md
vendored
Normal file
63
vendor/symfony/browser-kit/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Added `jsonRequest` method to `AbstractBrowser`
|
||||
* Allowed sending a body with GET requests when a content-type is defined
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] Request parameters are now casted to string in `Request::__construct()`.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* Added PHPUnit constraints: `BrowserCookieValueSame` and `BrowserHasCookie`
|
||||
* Added `HttpBrowser`, an implementation of a browser with the HttpClient component
|
||||
* Renamed `Client` to `AbstractBrowser`
|
||||
* Marked `Response` final.
|
||||
* Deprecated `Response::buildHeader()`
|
||||
* Deprecated `Response::getStatus()`, use `Response::getStatusCode()` instead
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* The method `Client::submit()` will have a new `$serverParameters` argument
|
||||
in version 5.0, not defining it is deprecated
|
||||
* Added ability to read the "samesite" attribute of cookies using `Cookie::getSameSite()`
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] Client will skip redirects during history navigation
|
||||
(back and forward calls) according to W3C Browsers recommendation
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] The request method is dropped from POST to GET when the response
|
||||
status code is 301.
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* Client HTTP user agent has been changed to 'Symfony BrowserKit'
|
||||
|
||||
2.3.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] `Client::followRedirect()` won't redirect responses with
|
||||
a non-3xx Status Code and `Location` header anymore, as per
|
||||
http://tools.ietf.org/html/rfc2616#section-14.30
|
||||
|
||||
* added `Client::getInternalRequest()` and `Client::getInternalResponse()` to
|
||||
have access to the BrowserKit internal request and response objects
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] The CookieJar internals have changed to allow cookies with the
|
||||
same name on different sub-domains/sub-paths
|
319
vendor/symfony/browser-kit/Cookie.php
vendored
Normal file
319
vendor/symfony/browser-kit/Cookie.php
vendored
Normal file
@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
/**
|
||||
* Cookie represents an HTTP cookie.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Cookie
|
||||
{
|
||||
/**
|
||||
* Handles dates as defined by RFC 2616 section 3.3.1, and also some other
|
||||
* non-standard, but common formats.
|
||||
*/
|
||||
private const DATE_FORMATS = [
|
||||
'D, d M Y H:i:s T',
|
||||
'D, d-M-y H:i:s T',
|
||||
'D, d-M-Y H:i:s T',
|
||||
'D, d-m-y H:i:s T',
|
||||
'D, d-m-Y H:i:s T',
|
||||
'D M j G:i:s Y',
|
||||
'D M d H:i:s Y T',
|
||||
];
|
||||
|
||||
protected $name;
|
||||
protected $value;
|
||||
protected $expires;
|
||||
protected $path;
|
||||
protected $domain;
|
||||
protected $secure;
|
||||
protected $httponly;
|
||||
protected $rawValue;
|
||||
private $samesite;
|
||||
|
||||
/**
|
||||
* Sets a cookie.
|
||||
*
|
||||
* @param string $name The cookie name
|
||||
* @param string|null $value The value of the cookie
|
||||
* @param string|null $expires The time the cookie expires
|
||||
* @param string|null $path The path on the server in which the cookie will be available on
|
||||
* @param string $domain The domain that the cookie is available
|
||||
* @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
|
||||
* @param bool $httponly The cookie httponly flag
|
||||
* @param bool $encodedValue Whether the value is encoded or not
|
||||
* @param string|null $samesite The cookie samesite attribute
|
||||
*/
|
||||
public function __construct(string $name, ?string $value, string $expires = null, string $path = null, string $domain = '', bool $secure = false, bool $httponly = true, bool $encodedValue = false, string $samesite = null)
|
||||
{
|
||||
if ($encodedValue) {
|
||||
$this->value = urldecode($value);
|
||||
$this->rawValue = $value;
|
||||
} else {
|
||||
$this->value = $value;
|
||||
$this->rawValue = rawurlencode($value ?? '');
|
||||
}
|
||||
$this->name = $name;
|
||||
$this->path = empty($path) ? '/' : $path;
|
||||
$this->domain = $domain;
|
||||
$this->secure = $secure;
|
||||
$this->httponly = $httponly;
|
||||
$this->samesite = $samesite;
|
||||
|
||||
if (null !== $expires) {
|
||||
$timestampAsDateTime = \DateTime::createFromFormat('U', $expires);
|
||||
if (false === $timestampAsDateTime) {
|
||||
throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.', $expires));
|
||||
}
|
||||
|
||||
$this->expires = $timestampAsDateTime->format('U');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP representation of the Cookie.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$cookie = sprintf('%s=%s', $this->name, $this->rawValue);
|
||||
|
||||
if (null !== $this->expires) {
|
||||
$dateTime = \DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT'));
|
||||
$cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::DATE_FORMATS[0]));
|
||||
}
|
||||
|
||||
if ('' !== $this->domain) {
|
||||
$cookie .= '; domain='.$this->domain;
|
||||
}
|
||||
|
||||
if ($this->path) {
|
||||
$cookie .= '; path='.$this->path;
|
||||
}
|
||||
|
||||
if ($this->secure) {
|
||||
$cookie .= '; secure';
|
||||
}
|
||||
|
||||
if ($this->httponly) {
|
||||
$cookie .= '; httponly';
|
||||
}
|
||||
|
||||
if (null !== $this->samesite) {
|
||||
$cookie .= '; samesite='.$this->samesite;
|
||||
}
|
||||
|
||||
return $cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Cookie instance from a Set-Cookie header value.
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function fromString(string $cookie, string $url = null)
|
||||
{
|
||||
$parts = explode(';', $cookie);
|
||||
|
||||
if (!str_contains($parts[0], '=')) {
|
||||
throw new \InvalidArgumentException(sprintf('The cookie string "%s" is not valid.', $parts[0]));
|
||||
}
|
||||
|
||||
[$name, $value] = explode('=', array_shift($parts), 2);
|
||||
|
||||
$values = [
|
||||
'name' => trim($name),
|
||||
'value' => trim($value),
|
||||
'expires' => null,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'httponly' => false,
|
||||
'passedRawValue' => true,
|
||||
'samesite' => null,
|
||||
];
|
||||
|
||||
if (null !== $url) {
|
||||
if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host'])) {
|
||||
throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url));
|
||||
}
|
||||
|
||||
$values['domain'] = $urlParts['host'];
|
||||
$values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : '';
|
||||
}
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$part = trim($part);
|
||||
|
||||
if ('secure' === strtolower($part)) {
|
||||
// Ignore the secure flag if the original URI is not given or is not HTTPS
|
||||
if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values['secure'] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('httponly' === strtolower($part)) {
|
||||
$values['httponly'] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (2 === \count($elements = explode('=', $part, 2))) {
|
||||
if ('expires' === strtolower($elements[0])) {
|
||||
$elements[1] = self::parseDate($elements[1]);
|
||||
}
|
||||
|
||||
$values[strtolower($elements[0])] = $elements[1];
|
||||
}
|
||||
}
|
||||
|
||||
return new static(
|
||||
$values['name'],
|
||||
$values['value'],
|
||||
$values['expires'],
|
||||
$values['path'],
|
||||
$values['domain'],
|
||||
$values['secure'],
|
||||
$values['httponly'],
|
||||
$values['passedRawValue'],
|
||||
$values['samesite']
|
||||
);
|
||||
}
|
||||
|
||||
private static function parseDate(string $dateValue): ?string
|
||||
{
|
||||
// trim single quotes around date if present
|
||||
if (($length = \strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length - 1]) {
|
||||
$dateValue = substr($dateValue, 1, -1);
|
||||
}
|
||||
|
||||
foreach (self::DATE_FORMATS as $dateFormat) {
|
||||
if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) {
|
||||
return $date->format('U');
|
||||
}
|
||||
}
|
||||
|
||||
// attempt a fallback for unusual formatting
|
||||
if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) {
|
||||
return $date->format('U');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the cookie.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the cookie.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw value of the cookie.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawValue()
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expires time of the cookie.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getExpiresTime()
|
||||
{
|
||||
return $this->expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the cookie.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the domain of the cookie.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secure flag of the cookie.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSecure()
|
||||
{
|
||||
return $this->secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the httponly flag of the cookie.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isHttpOnly()
|
||||
{
|
||||
return $this->httponly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cookie has expired.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired()
|
||||
{
|
||||
return null !== $this->expires && 0 != $this->expires && $this->expires <= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the samesite attribute of the cookie.
|
||||
*/
|
||||
public function getSameSite(): ?string
|
||||
{
|
||||
return $this->samesite;
|
||||
}
|
||||
}
|
224
vendor/symfony/browser-kit/CookieJar.php
vendored
Normal file
224
vendor/symfony/browser-kit/CookieJar.php
vendored
Normal file
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
/**
|
||||
* CookieJar.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class CookieJar
|
||||
{
|
||||
protected $cookieJar = [];
|
||||
|
||||
public function set(Cookie $cookie)
|
||||
{
|
||||
$this->cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cookie by name.
|
||||
*
|
||||
* You should never use an empty domain, but if you do so,
|
||||
* this method returns the first cookie for the given name/path
|
||||
* (this behavior ensures a BC behavior with previous versions of
|
||||
* Symfony).
|
||||
*
|
||||
* @return Cookie|null
|
||||
*/
|
||||
public function get(string $name, string $path = '/', string $domain = null)
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
foreach ($this->cookieJar as $cookieDomain => $pathCookies) {
|
||||
if ($cookieDomain && $domain) {
|
||||
$cookieDomain = '.'.ltrim($cookieDomain, '.');
|
||||
if (!str_ends_with('.'.$domain, $cookieDomain)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pathCookies as $cookiePath => $namedCookies) {
|
||||
if (!str_starts_with($path, $cookiePath)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($namedCookies[$name])) {
|
||||
return $namedCookies[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a cookie by name.
|
||||
*
|
||||
* You should never use an empty domain, but if you do so,
|
||||
* all cookies for the given name/path expire (this behavior
|
||||
* ensures a BC behavior with previous versions of Symfony).
|
||||
*/
|
||||
public function expire(string $name, ?string $path = '/', string $domain = null)
|
||||
{
|
||||
if (null === $path) {
|
||||
$path = '/';
|
||||
}
|
||||
|
||||
if (empty($domain)) {
|
||||
// an empty domain means any domain
|
||||
// this should never happen but it allows for a better BC
|
||||
$domains = array_keys($this->cookieJar);
|
||||
} else {
|
||||
$domains = [$domain];
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
unset($this->cookieJar[$domain][$path][$name]);
|
||||
|
||||
if (empty($this->cookieJar[$domain][$path])) {
|
||||
unset($this->cookieJar[$domain][$path]);
|
||||
|
||||
if (empty($this->cookieJar[$domain])) {
|
||||
unset($this->cookieJar[$domain]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the cookies from the jar.
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->cookieJar = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cookie jar from a response Set-Cookie headers.
|
||||
*
|
||||
* @param string[] $setCookies Set-Cookie headers from an HTTP response
|
||||
*/
|
||||
public function updateFromSetCookie(array $setCookies, string $uri = null)
|
||||
{
|
||||
$cookies = [];
|
||||
|
||||
foreach ($setCookies as $cookie) {
|
||||
foreach (explode(',', $cookie) as $i => $part) {
|
||||
if (0 === $i || preg_match('/^(?P<token>\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) {
|
||||
$cookies[] = ltrim($part);
|
||||
} else {
|
||||
$cookies[\count($cookies) - 1] .= ','.$part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
try {
|
||||
$this->set(Cookie::fromString($cookie, $uri));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// invalid cookies are just ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cookie jar from a Response object.
|
||||
*/
|
||||
public function updateFromResponse(Response $response, string $uri = null)
|
||||
{
|
||||
$this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns not yet expired cookies.
|
||||
*
|
||||
* @return Cookie[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
$flattenedCookies = [];
|
||||
foreach ($this->cookieJar as $path) {
|
||||
foreach ($path as $cookies) {
|
||||
foreach ($cookies as $cookie) {
|
||||
$flattenedCookies[] = $cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $flattenedCookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns not yet expired cookie values for the given URI.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function allValues(string $uri, bool $returnsRawValue = false)
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
$parts = array_replace(['path' => '/'], parse_url($uri));
|
||||
$cookies = [];
|
||||
foreach ($this->cookieJar as $domain => $pathCookies) {
|
||||
if ($domain) {
|
||||
$domain = '.'.ltrim($domain, '.');
|
||||
if ($domain != substr('.'.$parts['host'], -\strlen($domain))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pathCookies as $path => $namedCookies) {
|
||||
if ($path != substr($parts['path'], 0, \strlen($path))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($namedCookies as $cookie) {
|
||||
if ($cookie->isSecure() && 'https' != $parts['scheme']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns not yet expired raw cookie values for the given URI.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function allRawValues(string $uri)
|
||||
{
|
||||
return $this->allValues($uri, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all expired cookies.
|
||||
*/
|
||||
public function flushExpiredCookies()
|
||||
{
|
||||
foreach ($this->cookieJar as $domain => $pathCookies) {
|
||||
foreach ($pathCookies as $path => $namedCookies) {
|
||||
foreach ($namedCookies as $name => $cookie) {
|
||||
if ($cookie->isExpired()) {
|
||||
unset($this->cookieJar[$domain][$path][$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
vendor/symfony/browser-kit/Exception/BadMethodCallException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/BadMethodCallException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Exception;
|
||||
|
||||
class BadMethodCallException extends \BadMethodCallException
|
||||
{
|
||||
}
|
100
vendor/symfony/browser-kit/History.php
vendored
Normal file
100
vendor/symfony/browser-kit/History.php
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
/**
|
||||
* History.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class History
|
||||
{
|
||||
protected $stack = [];
|
||||
protected $position = -1;
|
||||
|
||||
/**
|
||||
* Clears the history.
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->stack = [];
|
||||
$this->position = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Request to the history.
|
||||
*/
|
||||
public function add(Request $request)
|
||||
{
|
||||
$this->stack = \array_slice($this->stack, 0, $this->position + 1);
|
||||
$this->stack[] = clone $request;
|
||||
$this->position = \count($this->stack) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the history is empty.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return 0 == \count($this->stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in the history.
|
||||
*
|
||||
* @return Request
|
||||
*
|
||||
* @throws \LogicException if the stack is already on the first page
|
||||
*/
|
||||
public function back()
|
||||
{
|
||||
if ($this->position < 1) {
|
||||
throw new \LogicException('You are already on the first page.');
|
||||
}
|
||||
|
||||
return clone $this->stack[--$this->position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes forward in the history.
|
||||
*
|
||||
* @return Request
|
||||
*
|
||||
* @throws \LogicException if the stack is already on the last page
|
||||
*/
|
||||
public function forward()
|
||||
{
|
||||
if ($this->position > \count($this->stack) - 2) {
|
||||
throw new \LogicException('You are already on the last page.');
|
||||
}
|
||||
|
||||
return clone $this->stack[++$this->position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current element in the history.
|
||||
*
|
||||
* @return Request
|
||||
*
|
||||
* @throws \LogicException if the stack is empty
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
if (-1 == $this->position) {
|
||||
throw new \LogicException('The page history is empty.');
|
||||
}
|
||||
|
||||
return clone $this->stack[$this->position];
|
||||
}
|
||||
}
|
153
vendor/symfony/browser-kit/HttpBrowser.php
vendored
Normal file
153
vendor/symfony/browser-kit/HttpBrowser.php
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* An implementation of a browser using the HttpClient component
|
||||
* to make real HTTP requests.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class HttpBrowser extends AbstractBrowser
|
||||
{
|
||||
private $client;
|
||||
|
||||
public function __construct(HttpClientInterface $client = null, History $history = null, CookieJar $cookieJar = null)
|
||||
{
|
||||
if (!$client && !class_exists(HttpClient::class)) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
|
||||
}
|
||||
|
||||
$this->client = $client ?? HttpClient::create();
|
||||
|
||||
parent::__construct([], $history, $cookieJar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function doRequest(object $request): Response
|
||||
{
|
||||
$headers = $this->getHeaders($request);
|
||||
[$body, $extraHeaders] = $this->getBodyAndExtraHeaders($request, $headers);
|
||||
|
||||
$response = $this->client->request($request->getMethod(), $request->getUri(), [
|
||||
'headers' => array_merge($headers, $extraHeaders),
|
||||
'body' => $body,
|
||||
'max_redirects' => 0,
|
||||
]);
|
||||
|
||||
return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [$body, $headers]
|
||||
*/
|
||||
private function getBodyAndExtraHeaders(Request $request, array $headers): array
|
||||
{
|
||||
if (\in_array($request->getMethod(), ['GET', 'HEAD']) && !isset($headers['content-type'])) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
if (!class_exists(AbstractPart::class)) {
|
||||
throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".');
|
||||
}
|
||||
|
||||
if (null !== $content = $request->getContent()) {
|
||||
if (isset($headers['content-type'])) {
|
||||
return [$content, []];
|
||||
}
|
||||
|
||||
$part = new TextPart($content, 'utf-8', 'plain', '8bit');
|
||||
|
||||
return [$part->bodyToString(), $part->getPreparedHeaders()->toArray()];
|
||||
}
|
||||
|
||||
$fields = $request->getParameters();
|
||||
|
||||
if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) {
|
||||
$part = new FormDataPart(array_replace_recursive($fields, $uploadedFiles));
|
||||
|
||||
return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()];
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
array_walk_recursive($fields, $caster = static function (&$v) use (&$caster) {
|
||||
if (\is_object($v)) {
|
||||
if ($vars = get_object_vars($v)) {
|
||||
array_walk_recursive($vars, $caster);
|
||||
$v = $vars;
|
||||
} elseif (method_exists($v, '__toString')) {
|
||||
$v = (string) $v;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [http_build_query($fields, '', '&'), ['Content-Type' => 'application/x-www-form-urlencoded']];
|
||||
}
|
||||
|
||||
protected function getHeaders(Request $request): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($request->getServer() as $key => $value) {
|
||||
$key = strtolower(str_replace('_', '-', $key));
|
||||
$contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true];
|
||||
if (str_starts_with($key, 'http-')) {
|
||||
$headers[substr($key, 5)] = $value;
|
||||
} elseif (isset($contentHeaders[$key])) {
|
||||
// CONTENT_* are not prefixed with HTTP_
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
$cookies = [];
|
||||
foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
|
||||
$cookies[] = $name.'='.$value;
|
||||
}
|
||||
if ($cookies) {
|
||||
$headers['cookie'] = implode('; ', $cookies);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively go through the list. If the file has a tmp_name, convert it to a DataPart.
|
||||
* Keep the original hierarchy.
|
||||
*/
|
||||
private function getUploadedFiles(array $files): array
|
||||
{
|
||||
$uploadedFiles = [];
|
||||
foreach ($files as $name => $file) {
|
||||
if (!\is_array($file)) {
|
||||
return $uploadedFiles;
|
||||
}
|
||||
if (!isset($file['tmp_name'])) {
|
||||
$uploadedFiles[$name] = $this->getUploadedFiles($file);
|
||||
}
|
||||
if (isset($file['tmp_name'])) {
|
||||
$uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
|
||||
}
|
||||
}
|
||||
|
||||
return $uploadedFiles;
|
||||
}
|
||||
}
|
19
vendor/symfony/browser-kit/LICENSE
vendored
Normal file
19
vendor/symfony/browser-kit/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2022 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.
|
17
vendor/symfony/browser-kit/README.md
vendored
Normal file
17
vendor/symfony/browser-kit/README.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
BrowserKit Component
|
||||
====================
|
||||
|
||||
The BrowserKit component simulates the behavior of a web browser, allowing you
|
||||
to make requests, click on links and submit forms programmatically.
|
||||
|
||||
The component comes with a concrete implementation that uses the HttpClient
|
||||
component to make real HTTP requests.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/browser_kit/introduction.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)
|
121
vendor/symfony/browser-kit/Request.php
vendored
Normal file
121
vendor/symfony/browser-kit/Request.php
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
protected $uri;
|
||||
protected $method;
|
||||
protected $parameters;
|
||||
protected $files;
|
||||
protected $cookies;
|
||||
protected $server;
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @param string $uri The request URI
|
||||
* @param string $method The HTTP method request
|
||||
* @param array $parameters The request parameters
|
||||
* @param array $files An array of uploaded files
|
||||
* @param array $cookies An array of cookies
|
||||
* @param array $server An array of server parameters
|
||||
* @param string $content The raw body data
|
||||
*/
|
||||
public function __construct(string $uri, string $method, array $parameters = [], array $files = [], array $cookies = [], array $server = [], string $content = null)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$this->method = $method;
|
||||
|
||||
array_walk_recursive($parameters, static function (&$value) {
|
||||
$value = (string) $value;
|
||||
});
|
||||
|
||||
$this->parameters = $parameters;
|
||||
$this->files = $files;
|
||||
$this->cookies = $cookies;
|
||||
$this->server = $server;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUri()
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request HTTP method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request server files.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFiles()
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request cookies.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCookies()
|
||||
{
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request server parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getServer()
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request raw body data.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
98
vendor/symfony/browser-kit/Response.php
vendored
Normal file
98
vendor/symfony/browser-kit/Response.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class Response
|
||||
{
|
||||
private $content;
|
||||
private $status;
|
||||
private $headers;
|
||||
|
||||
/**
|
||||
* The headers array is a set of key/value pairs. If a header is present multiple times
|
||||
* then the value is an array of all the values.
|
||||
*
|
||||
* @param string $content The content of the response
|
||||
* @param int $status The response status code
|
||||
* @param array $headers An array of headers
|
||||
*/
|
||||
public function __construct(string $content = '', int $status = 200, array $headers = [])
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->status = $status;
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the response object to string containing all headers and the response content.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$headers = '';
|
||||
foreach ($this->headers as $name => $value) {
|
||||
if (\is_string($value)) {
|
||||
$headers .= sprintf("%s: %s\n", $name, $value);
|
||||
} else {
|
||||
foreach ($value as $headerValue) {
|
||||
$headers .= sprintf("%s: %s\n", $name, $headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $headers."\n".$this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response content.
|
||||
*/
|
||||
public function getContent(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response headers.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a response header.
|
||||
*
|
||||
* @return string|array|null The first header value if $first is true, an array of values otherwise
|
||||
*/
|
||||
public function getHeader(string $header, bool $first = true)
|
||||
{
|
||||
$normalizedHeader = str_replace('-', '_', strtolower($header));
|
||||
foreach ($this->headers as $key => $value) {
|
||||
if (str_replace('-', '_', strtolower($key)) === $normalizedHeader) {
|
||||
if ($first) {
|
||||
return \is_array($value) ? (\count($value) ? $value[0] : '') : $value;
|
||||
}
|
||||
|
||||
return \is_array($value) ? $value : [$value];
|
||||
}
|
||||
}
|
||||
|
||||
return $first ? null : [];
|
||||
}
|
||||
}
|
75
vendor/symfony/browser-kit/Test/Constraint/BrowserCookieValueSame.php
vendored
Normal file
75
vendor/symfony/browser-kit/Test/Constraint/BrowserCookieValueSame.php
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
|
||||
final class BrowserCookieValueSame extends Constraint
|
||||
{
|
||||
private $name;
|
||||
private $value;
|
||||
private $raw;
|
||||
private $path;
|
||||
private $domain;
|
||||
|
||||
public function __construct(string $name, string $value, bool $raw = false, string $path = '/', string $domain = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->path = $path;
|
||||
$this->domain = $domain;
|
||||
$this->value = $value;
|
||||
$this->raw = $raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
$str = sprintf('has cookie "%s"', $this->name);
|
||||
if ('/' !== $this->path) {
|
||||
$str .= sprintf(' with path "%s"', $this->path);
|
||||
}
|
||||
if ($this->domain) {
|
||||
$str .= sprintf(' for domain "%s"', $this->domain);
|
||||
}
|
||||
$str .= sprintf(' with %svalue "%s"', $this->raw ? 'raw ' : '', $this->value);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function matches($browser): bool
|
||||
{
|
||||
$cookie = $browser->getCookieJar()->get($this->name, $this->path, $this->domain);
|
||||
if (!$cookie) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->value === ($this->raw ? $cookie->getRawValue() : $cookie->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function failureDescription($browser): string
|
||||
{
|
||||
return 'the Browser '.$this->toString();
|
||||
}
|
||||
}
|
65
vendor/symfony/browser-kit/Test/Constraint/BrowserHasCookie.php
vendored
Normal file
65
vendor/symfony/browser-kit/Test/Constraint/BrowserHasCookie.php
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
|
||||
final class BrowserHasCookie extends Constraint
|
||||
{
|
||||
private $name;
|
||||
private $path;
|
||||
private $domain;
|
||||
|
||||
public function __construct(string $name, string $path = '/', string $domain = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->path = $path;
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
$str = sprintf('has cookie "%s"', $this->name);
|
||||
if ('/' !== $this->path) {
|
||||
$str .= sprintf(' with path "%s"', $this->path);
|
||||
}
|
||||
if ($this->domain) {
|
||||
$str .= sprintf(' for domain "%s"', $this->domain);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function matches($browser): bool
|
||||
{
|
||||
return null !== $browser->getCookieJar()->get($this->name, $this->path, $this->domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function failureDescription($browser): string
|
||||
{
|
||||
return 'the Browser '.$this->toString();
|
||||
}
|
||||
}
|
39
vendor/symfony/browser-kit/composer.json
vendored
Normal file
39
vendor/symfony/browser-kit/composer.json
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "symfony/browser-kit",
|
||||
"type": "library",
|
||||
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
|
||||
"keywords": [],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/dom-crawler": "^4.4|^5.0|^6.0",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/css-selector": "^4.4|^5.0|^6.0",
|
||||
"symfony/http-client": "^4.4|^5.0|^6.0",
|
||||
"symfony/mime": "^4.4|^5.0|^6.0",
|
||||
"symfony/process": "^4.4|^5.0|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/process": ""
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\BrowserKit\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
3
vendor/symfony/cache-contracts/.gitignore
vendored
Normal file
3
vendor/symfony/cache-contracts/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
5
vendor/symfony/cache-contracts/CHANGELOG.md
vendored
Normal file
5
vendor/symfony/cache-contracts/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
The changelog is maintained for all Symfony contracts at the following URL:
|
||||
https://github.com/symfony/contracts/blob/main/CHANGELOG.md
|
57
vendor/symfony/cache-contracts/CacheInterface.php
vendored
Normal file
57
vendor/symfony/cache-contracts/CacheInterface.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <p@tchwork.com>
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @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;
|
||||
}
|
80
vendor/symfony/cache-contracts/CacheTrait.php
vendored
Normal file
80
vendor/symfony/cache-contracts/CacheTrait.php
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(InvalidArgumentException::class);
|
||||
|
||||
/**
|
||||
* An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
trait CacheTrait
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
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.', static::class, $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();
|
||||
}
|
||||
}
|
30
vendor/symfony/cache-contracts/CallbackInterface.php
vendored
Normal file
30
vendor/symfony/cache-contracts/CallbackInterface.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <p@tchwork.com>
|
||||
*/
|
||||
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);
|
||||
}
|
65
vendor/symfony/cache-contracts/ItemInterface.php
vendored
Normal file
65
vendor/symfony/cache-contracts/ItemInterface.php
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <p@tchwork.com>
|
||||
*/
|
||||
interface ItemInterface extends CacheItemInterface
|
||||
{
|
||||
/**
|
||||
* References the Unix timestamp stating when the item will expire.
|
||||
*/
|
||||
public const METADATA_EXPIRY = 'expiry';
|
||||
|
||||
/**
|
||||
* References the time the item took to be created, in milliseconds.
|
||||
*/
|
||||
public const METADATA_CTIME = 'ctime';
|
||||
|
||||
/**
|
||||
* References the list of tags that were assigned to the item, as string[].
|
||||
*/
|
||||
public const METADATA_TAGS = 'tags';
|
||||
|
||||
/**
|
||||
* Reserved characters that cannot be used in a key or tag.
|
||||
*/
|
||||
public 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;
|
||||
}
|
19
vendor/symfony/cache-contracts/LICENSE
vendored
Normal file
19
vendor/symfony/cache-contracts/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018-2022 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.
|
9
vendor/symfony/cache-contracts/README.md
vendored
Normal file
9
vendor/symfony/cache-contracts/README.md
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
Symfony Cache Contracts
|
||||
=======================
|
||||
|
||||
A set of abstractions extracted out of the Symfony components.
|
||||
|
||||
Can be used to build on semantics that the Symfony components proved useful - and
|
||||
that already have battle tested implementations.
|
||||
|
||||
See https://github.com/symfony/contracts/blob/main/README.md for more information.
|
38
vendor/symfony/cache-contracts/TagAwareCacheInterface.php
vendored
Normal file
38
vendor/symfony/cache-contracts/TagAwareCacheInterface.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <p@tchwork.com>
|
||||
*/
|
||||
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);
|
||||
}
|
38
vendor/symfony/cache-contracts/composer.json
vendored
Normal file
38
vendor/symfony/cache-contracts/composer.json
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"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.2.5",
|
||||
"psr/cache": "^1.0|^2.0|^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/cache-implementation": ""
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Contracts\\Cache\\": "" }
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.5-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
}
|
||||
}
|
||||
}
|
208
vendor/symfony/cache/Adapter/AbstractAdapter.php
vendored
Normal file
208
vendor/symfony/cache/Adapter/AbstractAdapter.php
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
|
||||
use Symfony\Component\Cache\Traits\ContractsTrait;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
|
||||
{
|
||||
use AbstractAdapterTrait;
|
||||
use ContractsTrait;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected const NS_SEPARATOR = ':';
|
||||
|
||||
private static $apcuSupported;
|
||||
private static $phpFilesSupported;
|
||||
|
||||
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
|
||||
{
|
||||
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
|
||||
$this->defaultLifetime = $defaultLifetime;
|
||||
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
|
||||
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
|
||||
}
|
||||
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
|
||||
static function ($key, $value, $isHit) {
|
||||
$item = new CacheItem();
|
||||
$item->key = $key;
|
||||
$item->value = $v = $value;
|
||||
$item->isHit = $isHit;
|
||||
// Detect wrapped values that encode for their expiry and creation duration
|
||||
// For compactness, these values are packed in the key of an array using
|
||||
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
|
||||
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
|
||||
$item->value = $v[$k];
|
||||
$v = unpack('Ve/Nc', substr($k, 1, -1));
|
||||
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
|
||||
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
|
||||
}
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind(
|
||||
static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) {
|
||||
$byLifetime = [];
|
||||
$now = microtime(true);
|
||||
$expiredIds = [];
|
||||
|
||||
foreach ($deferred as $key => $item) {
|
||||
$key = (string) $key;
|
||||
if (null === $item->expiry) {
|
||||
$ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
|
||||
} elseif (!$item->expiry) {
|
||||
$ttl = 0;
|
||||
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
|
||||
$expiredIds[] = $getId($key);
|
||||
continue;
|
||||
}
|
||||
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
|
||||
unset($metadata[CacheItem::METADATA_TAGS]);
|
||||
}
|
||||
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
|
||||
$byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
|
||||
}
|
||||
|
||||
return $byLifetime;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best possible adapter that your runtime supports.
|
||||
*
|
||||
* Using ApcuAdapter makes system caches compatible with read-only filesystems.
|
||||
*
|
||||
* @return AdapterInterface
|
||||
*/
|
||||
public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, LoggerInterface $logger = null)
|
||||
{
|
||||
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
|
||||
if (null !== $logger) {
|
||||
$opcache->setLogger($logger);
|
||||
}
|
||||
|
||||
if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
|
||||
return $opcache;
|
||||
}
|
||||
|
||||
if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
|
||||
return $opcache;
|
||||
}
|
||||
|
||||
$apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version);
|
||||
if (null !== $logger) {
|
||||
$apcu->setLogger($logger);
|
||||
}
|
||||
|
||||
return new ChainAdapter([$apcu, $opcache]);
|
||||
}
|
||||
|
||||
public static function createConnection(string $dsn, array $options = [])
|
||||
{
|
||||
if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) {
|
||||
return RedisAdapter::createConnection($dsn, $options);
|
||||
}
|
||||
if (str_starts_with($dsn, 'memcached:')) {
|
||||
return MemcachedAdapter::createConnection($dsn, $options);
|
||||
}
|
||||
if (0 === strpos($dsn, 'couchbase:')) {
|
||||
if (CouchbaseBucketAdapter::isSupported()) {
|
||||
return CouchbaseBucketAdapter::createConnection($dsn, $options);
|
||||
}
|
||||
|
||||
return CouchbaseCollectionAdapter::createConnection($dsn, $options);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
$ok = true;
|
||||
$byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, \Closure::fromCallable([$this, 'getId']), $this->defaultLifetime);
|
||||
$retry = $this->deferred = [];
|
||||
|
||||
if ($expiredIds) {
|
||||
try {
|
||||
$this->doDelete($expiredIds);
|
||||
} catch (\Exception $e) {
|
||||
$ok = false;
|
||||
CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
|
||||
}
|
||||
}
|
||||
foreach ($byLifetime as $lifetime => $values) {
|
||||
try {
|
||||
$e = $this->doSave($values, $lifetime);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
if (true === $e || [] === $e) {
|
||||
continue;
|
||||
}
|
||||
if (\is_array($e) || 1 === \count($values)) {
|
||||
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
|
||||
$ok = false;
|
||||
$v = $values[$id];
|
||||
$type = get_debug_type($v);
|
||||
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
|
||||
}
|
||||
} else {
|
||||
foreach ($values as $id => $v) {
|
||||
$retry[$lifetime][] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When bulk-save failed, retry each item individually
|
||||
foreach ($retry as $lifetime => $ids) {
|
||||
foreach ($ids as $id) {
|
||||
try {
|
||||
$v = $byLifetime[$lifetime][$id];
|
||||
$e = $this->doSave([$id => $v], $lifetime);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
if (true === $e || [] === $e) {
|
||||
continue;
|
||||
}
|
||||
$ok = false;
|
||||
$type = get_debug_type($v);
|
||||
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
|
||||
}
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
}
|
330
vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
vendored
Normal file
330
vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
vendored
Normal file
@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
|
||||
use Symfony\Component\Cache\Traits\ContractsTrait;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
/**
|
||||
* Abstract for native TagAware adapters.
|
||||
*
|
||||
* To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids
|
||||
* to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author André Rømcke <andre.romcke+symfony@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
|
||||
{
|
||||
use AbstractAdapterTrait;
|
||||
use ContractsTrait;
|
||||
|
||||
private const TAGS_PREFIX = "\0tags\0";
|
||||
|
||||
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
|
||||
{
|
||||
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
|
||||
$this->defaultLifetime = $defaultLifetime;
|
||||
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
|
||||
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
|
||||
}
|
||||
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
|
||||
static function ($key, $value, $isHit) {
|
||||
$item = new CacheItem();
|
||||
$item->key = $key;
|
||||
$item->isTaggable = true;
|
||||
// If structure does not match what we expect return item as is (no value and not a hit)
|
||||
if (!\is_array($value) || !\array_key_exists('value', $value)) {
|
||||
return $item;
|
||||
}
|
||||
$item->isHit = $isHit;
|
||||
// Extract value, tags and meta data from the cache value
|
||||
$item->value = $value['value'];
|
||||
$item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
|
||||
if (isset($value['meta'])) {
|
||||
// For compactness these values are packed, & expiry is offset to reduce size
|
||||
$v = unpack('Ve/Nc', $value['meta']);
|
||||
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
|
||||
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
|
||||
}
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind(
|
||||
static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) {
|
||||
$byLifetime = [];
|
||||
$now = microtime(true);
|
||||
$expiredIds = [];
|
||||
|
||||
foreach ($deferred as $key => $item) {
|
||||
$key = (string) $key;
|
||||
if (null === $item->expiry) {
|
||||
$ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
|
||||
} elseif (!$item->expiry) {
|
||||
$ttl = 0;
|
||||
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
|
||||
$expiredIds[] = $getId($key);
|
||||
continue;
|
||||
}
|
||||
// Store Value and Tags on the cache value
|
||||
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
|
||||
$value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
|
||||
unset($metadata[CacheItem::METADATA_TAGS]);
|
||||
} else {
|
||||
$value = ['value' => $item->value, 'tags' => []];
|
||||
}
|
||||
|
||||
if ($metadata) {
|
||||
// For compactness, expiry and creation duration are packed, using magic numbers as separators
|
||||
$value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
|
||||
}
|
||||
|
||||
// Extract tag changes, these should be removed from values in doSave()
|
||||
$value['tag-operations'] = ['add' => [], 'remove' => []];
|
||||
$oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
|
||||
foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
|
||||
$value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
|
||||
}
|
||||
foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
|
||||
$value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
|
||||
}
|
||||
|
||||
$byLifetime[$ttl][$getId($key)] = $value;
|
||||
$item->metadata = $item->newMetadata;
|
||||
}
|
||||
|
||||
return $byLifetime;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists several cache items immediately.
|
||||
*
|
||||
* @param array $values The values to cache, indexed by their cache identifier
|
||||
* @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
|
||||
* @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag
|
||||
* @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
|
||||
*
|
||||
* @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
|
||||
*/
|
||||
abstract protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array;
|
||||
|
||||
/**
|
||||
* Removes multiple items from the pool and their corresponding tags.
|
||||
*
|
||||
* @param array $ids An array of identifiers that should be removed from the pool
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function doDelete(array $ids);
|
||||
|
||||
/**
|
||||
* Removes relations between tags and deleted items.
|
||||
*
|
||||
* @param array $tagData Array of tag => key identifiers that should be removed from the pool
|
||||
*/
|
||||
abstract protected function doDeleteTagRelations(array $tagData): bool;
|
||||
|
||||
/**
|
||||
* Invalidates cached items using tags.
|
||||
*
|
||||
* @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
|
||||
*/
|
||||
abstract protected function doInvalidate(array $tagIds): bool;
|
||||
|
||||
/**
|
||||
* Delete items and yields the tags they were bound to.
|
||||
*/
|
||||
protected function doDeleteYieldTags(array $ids): iterable
|
||||
{
|
||||
foreach ($this->doFetch($ids) as $id => $value) {
|
||||
yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : [];
|
||||
}
|
||||
|
||||
$this->doDelete($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit(): bool
|
||||
{
|
||||
$ok = true;
|
||||
$byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, \Closure::fromCallable([$this, 'getId']), self::TAGS_PREFIX, $this->defaultLifetime);
|
||||
$retry = $this->deferred = [];
|
||||
|
||||
if ($expiredIds) {
|
||||
// Tags are not cleaned up in this case, however that is done on invalidateTags().
|
||||
try {
|
||||
$this->doDelete($expiredIds);
|
||||
} catch (\Exception $e) {
|
||||
$ok = false;
|
||||
CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
|
||||
}
|
||||
}
|
||||
foreach ($byLifetime as $lifetime => $values) {
|
||||
try {
|
||||
$values = $this->extractTagData($values, $addTagData, $removeTagData);
|
||||
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
if (true === $e || [] === $e) {
|
||||
continue;
|
||||
}
|
||||
if (\is_array($e) || 1 === \count($values)) {
|
||||
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
|
||||
$ok = false;
|
||||
$v = $values[$id];
|
||||
$type = get_debug_type($v);
|
||||
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
|
||||
}
|
||||
} else {
|
||||
foreach ($values as $id => $v) {
|
||||
$retry[$lifetime][] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When bulk-save failed, retry each item individually
|
||||
foreach ($retry as $lifetime => $ids) {
|
||||
foreach ($ids as $id) {
|
||||
try {
|
||||
$v = $byLifetime[$lifetime][$id];
|
||||
$values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
|
||||
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
if (true === $e || [] === $e) {
|
||||
continue;
|
||||
}
|
||||
$ok = false;
|
||||
$type = get_debug_type($v);
|
||||
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
|
||||
}
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems(array $keys): bool
|
||||
{
|
||||
if (!$keys) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ok = true;
|
||||
$ids = [];
|
||||
$tagData = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$ids[$key] = $this->getId($key);
|
||||
unset($this->deferred[$key]);
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) {
|
||||
foreach ($tags as $tag) {
|
||||
$tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$ok = false;
|
||||
}
|
||||
|
||||
try {
|
||||
if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) {
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
// When bulk-delete failed, retry each item individually
|
||||
foreach ($ids as $key => $id) {
|
||||
try {
|
||||
$e = null;
|
||||
if ($this->doDelete([$id])) {
|
||||
continue;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
$message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
|
||||
$ok = false;
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function invalidateTags(array $tags)
|
||||
{
|
||||
if (empty($tags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tagIds = [];
|
||||
foreach (array_unique($tags) as $tag) {
|
||||
$tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->doInvalidate($tagIds)) {
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to invalidate tags: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
|
||||
*/
|
||||
private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
|
||||
{
|
||||
$addTagData = $removeTagData = [];
|
||||
foreach ($values as $id => $value) {
|
||||
foreach ($value['tag-operations']['add'] as $tag => $tagId) {
|
||||
$addTagData[$tagId][] = $id;
|
||||
}
|
||||
|
||||
foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
|
||||
$removeTagData[$tagId][] = $id;
|
||||
}
|
||||
|
||||
unset($values[$id]['tag-operations']);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
47
vendor/symfony/cache/Adapter/AdapterInterface.php
vendored
Normal file
47
vendor/symfony/cache/Adapter/AdapterInterface.php
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(CacheItem::class);
|
||||
|
||||
/**
|
||||
* Interface for adapters managing instances of Symfony's CacheItem.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
interface AdapterInterface extends CacheItemPoolInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return CacheItem
|
||||
*/
|
||||
public function getItem($key);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return \Traversable<string, CacheItem>
|
||||
*/
|
||||
public function getItems(array $keys = []);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '');
|
||||
}
|
138
vendor/symfony/cache/Adapter/ApcuAdapter.php
vendored
Normal file
138
vendor/symfony/cache/Adapter/ApcuAdapter.php
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ApcuAdapter extends AbstractAdapter
|
||||
{
|
||||
private $marshaller;
|
||||
|
||||
/**
|
||||
* @throws CacheException if APCu is not enabled
|
||||
*/
|
||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('APCu is not enabled.');
|
||||
}
|
||||
if ('cli' === \PHP_SAPI) {
|
||||
ini_set('apc.use_request_time', 0);
|
||||
}
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
|
||||
if (null !== $version) {
|
||||
CacheItem::validateKey($version);
|
||||
|
||||
if (!apcu_exists($version.'@'.$namespace)) {
|
||||
$this->doClear($namespace);
|
||||
apcu_add($version.'@'.$namespace, null);
|
||||
}
|
||||
}
|
||||
|
||||
$this->marshaller = $marshaller;
|
||||
}
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
||||
try {
|
||||
$values = [];
|
||||
$ids = array_flip($ids);
|
||||
foreach (apcu_fetch(array_keys($ids), $ok) ?: [] as $k => $v) {
|
||||
if (!isset($ids[$k])) {
|
||||
// work around https://github.com/krakjoe/apcu/issues/247
|
||||
$k = key($ids);
|
||||
}
|
||||
unset($ids[$k]);
|
||||
|
||||
if (null !== $v || $ok) {
|
||||
$values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id)
|
||||
{
|
||||
return apcu_exists($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace)
|
||||
{
|
||||
return isset($namespace[0]) && class_exists(\APCUIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))
|
||||
? apcu_delete(new \APCUIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY))
|
||||
: apcu_clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
apcu_delete($id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
if (null !== $this->marshaller && (!$values = $this->marshaller->marshall($values, $failed))) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
try {
|
||||
if (false === $failures = apcu_store($values, null, $lifetime)) {
|
||||
$failures = $values;
|
||||
}
|
||||
|
||||
return array_keys($failures);
|
||||
} catch (\Throwable $e) {
|
||||
if (1 === \count($values)) {
|
||||
// Workaround https://github.com/krakjoe/apcu/issues/170
|
||||
apcu_delete(array_key_first($values));
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
407
vendor/symfony/cache/Adapter/ArrayAdapter.php
vendored
Normal file
407
vendor/symfony/cache/Adapter/ArrayAdapter.php
vendored
Normal file
@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
* An in-memory cache storage.
|
||||
*
|
||||
* Acts as a least-recently-used (LRU) storage when configured with a maximum number of items.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $storeSerialized;
|
||||
private $values = [];
|
||||
private $expiries = [];
|
||||
private $defaultLifetime;
|
||||
private $maxLifetime;
|
||||
private $maxItems;
|
||||
|
||||
private static $createCacheItem;
|
||||
|
||||
/**
|
||||
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
|
||||
*/
|
||||
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true, float $maxLifetime = 0, int $maxItems = 0)
|
||||
{
|
||||
if (0 > $maxLifetime) {
|
||||
throw new InvalidArgumentException(sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime));
|
||||
}
|
||||
|
||||
if (0 > $maxItems) {
|
||||
throw new InvalidArgumentException(sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems));
|
||||
}
|
||||
|
||||
$this->defaultLifetime = $defaultLifetime;
|
||||
$this->storeSerialized = $storeSerialized;
|
||||
$this->maxLifetime = $maxLifetime;
|
||||
$this->maxItems = $maxItems;
|
||||
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
|
||||
static function ($key, $value, $isHit) {
|
||||
$item = new CacheItem();
|
||||
$item->key = $key;
|
||||
$item->value = $value;
|
||||
$item->isHit = $isHit;
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
|
||||
{
|
||||
$item = $this->getItem($key);
|
||||
$metadata = $item->getMetadata();
|
||||
|
||||
// ArrayAdapter works in memory, we don't care about stampede protection
|
||||
if (\INF === $beta || !$item->isHit()) {
|
||||
$save = true;
|
||||
$item->set($callback($item, $save));
|
||||
if ($save) {
|
||||
$this->save($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $item->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
return $this->deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
|
||||
if ($this->maxItems) {
|
||||
// Move the item last in the storage
|
||||
$value = $this->values[$key];
|
||||
unset($this->values[$key]);
|
||||
$this->values[$key] = $value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
\assert('' !== CacheItem::validateKey($key));
|
||||
|
||||
return isset($this->expiries[$key]) && !$this->deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
if (!$isHit = $this->hasItem($key)) {
|
||||
$value = null;
|
||||
|
||||
if (!$this->maxItems) {
|
||||
// Track misses in non-LRU mode only
|
||||
$this->values[$key] = null;
|
||||
}
|
||||
} else {
|
||||
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
|
||||
}
|
||||
|
||||
return (self::$createCacheItem)($key, $value, $isHit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
\assert(self::validateKeys($keys));
|
||||
|
||||
return $this->generateItems($keys, microtime(true), self::$createCacheItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
\assert('' !== CacheItem::validateKey($key));
|
||||
unset($this->values[$key], $this->expiries[$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
$this->deleteItem($key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
if (!$item instanceof CacheItem) {
|
||||
return false;
|
||||
}
|
||||
$item = (array) $item;
|
||||
$key = $item["\0*\0key"];
|
||||
$value = $item["\0*\0value"];
|
||||
$expiry = $item["\0*\0expiry"];
|
||||
|
||||
$now = microtime(true);
|
||||
|
||||
if (null !== $expiry) {
|
||||
if (!$expiry) {
|
||||
$expiry = \PHP_INT_MAX;
|
||||
} elseif ($expiry <= $now) {
|
||||
$this->deleteItem($key);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
|
||||
return false;
|
||||
}
|
||||
if (null === $expiry && 0 < $this->defaultLifetime) {
|
||||
$expiry = $this->defaultLifetime;
|
||||
$expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry);
|
||||
} elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) {
|
||||
$expiry = $now + $this->maxLifetime;
|
||||
}
|
||||
|
||||
if ($this->maxItems) {
|
||||
unset($this->values[$key]);
|
||||
|
||||
// Iterate items and vacuum expired ones while we are at it
|
||||
foreach ($this->values as $k => $v) {
|
||||
if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) {
|
||||
break;
|
||||
}
|
||||
|
||||
unset($this->values[$k], $this->expiries[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->values[$key] = $value;
|
||||
$this->expiries[$key] = $expiry ?? \PHP_INT_MAX;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
return $this->save($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
if ('' !== $prefix) {
|
||||
$now = microtime(true);
|
||||
|
||||
foreach ($this->values as $key => $value) {
|
||||
if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || 0 === strpos($key, $prefix)) {
|
||||
unset($this->values[$key], $this->expiries[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->values) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->values = $this->expiries = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all cached values, with cache miss as null.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
if (!$this->storeSerialized) {
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
$values = $this->values;
|
||||
foreach ($values as $k => $v) {
|
||||
if (null === $v || 'N;' === $v) {
|
||||
continue;
|
||||
}
|
||||
if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
|
||||
$values[$k] = serialize($v);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->clear();
|
||||
}
|
||||
|
||||
private function generateItems(array $keys, float $now, \Closure $f): \Generator
|
||||
{
|
||||
foreach ($keys as $i => $key) {
|
||||
if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
|
||||
$value = null;
|
||||
|
||||
if (!$this->maxItems) {
|
||||
// Track misses in non-LRU mode only
|
||||
$this->values[$key] = null;
|
||||
}
|
||||
} else {
|
||||
if ($this->maxItems) {
|
||||
// Move the item last in the storage
|
||||
$value = $this->values[$key];
|
||||
unset($this->values[$key]);
|
||||
$this->values[$key] = $value;
|
||||
}
|
||||
|
||||
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
|
||||
}
|
||||
unset($keys[$i]);
|
||||
|
||||
yield $key => $f($key, $value, $isHit);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
yield $key => $f($key, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
private function freeze($value, string $key)
|
||||
{
|
||||
if (null === $value) {
|
||||
return 'N;';
|
||||
}
|
||||
if (\is_string($value)) {
|
||||
// Serialize strings if they could be confused with serialized objects or arrays
|
||||
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
|
||||
return serialize($value);
|
||||
}
|
||||
} elseif (!\is_scalar($value)) {
|
||||
try {
|
||||
$serialized = serialize($value);
|
||||
} catch (\Exception $e) {
|
||||
unset($this->values[$key]);
|
||||
$type = get_debug_type($value);
|
||||
$message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
|
||||
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
|
||||
|
||||
return;
|
||||
}
|
||||
// Keep value serialized if it contains any objects or any internal references
|
||||
if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function unfreeze(string $key, bool &$isHit)
|
||||
{
|
||||
if ('N;' === $value = $this->values[$key]) {
|
||||
return null;
|
||||
}
|
||||
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
||||
try {
|
||||
$value = unserialize($value);
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
|
||||
$value = false;
|
||||
}
|
||||
if (false === $value) {
|
||||
$value = null;
|
||||
$isHit = false;
|
||||
|
||||
if (!$this->maxItems) {
|
||||
$this->values[$key] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function validateKeys(array $keys): bool
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (!\is_string($key) || !isset($this->expiries[$key])) {
|
||||
CacheItem::validateKey($key);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
342
vendor/symfony/cache/Adapter/ChainAdapter.php
vendored
Normal file
342
vendor/symfony/cache/Adapter/ChainAdapter.php
vendored
Normal file
@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\ContractsTrait;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Chains several adapters together.
|
||||
*
|
||||
* Cached items are fetched from the first adapter having them in its data store.
|
||||
* They are saved and deleted in all adapters at once.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
|
||||
{
|
||||
use ContractsTrait;
|
||||
|
||||
private $adapters = [];
|
||||
private $adapterCount;
|
||||
private $defaultLifetime;
|
||||
|
||||
private static $syncItem;
|
||||
|
||||
/**
|
||||
* @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items
|
||||
* @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
|
||||
*/
|
||||
public function __construct(array $adapters, int $defaultLifetime = 0)
|
||||
{
|
||||
if (!$adapters) {
|
||||
throw new InvalidArgumentException('At least one adapter must be specified.');
|
||||
}
|
||||
|
||||
foreach ($adapters as $adapter) {
|
||||
if (!$adapter instanceof CacheItemPoolInterface) {
|
||||
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class));
|
||||
}
|
||||
if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
|
||||
continue; // skip putting APCu in the chain when the backend is disabled
|
||||
}
|
||||
|
||||
if ($adapter instanceof AdapterInterface) {
|
||||
$this->adapters[] = $adapter;
|
||||
} else {
|
||||
$this->adapters[] = new ProxyAdapter($adapter);
|
||||
}
|
||||
}
|
||||
$this->adapterCount = \count($this->adapters);
|
||||
$this->defaultLifetime = $defaultLifetime;
|
||||
|
||||
self::$syncItem ?? self::$syncItem = \Closure::bind(
|
||||
static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) {
|
||||
$sourceItem->isTaggable = false;
|
||||
$sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;
|
||||
unset($sourceMetadata[CacheItem::METADATA_TAGS]);
|
||||
|
||||
$item->value = $sourceItem->value;
|
||||
$item->isHit = $sourceItem->isHit;
|
||||
$item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
|
||||
|
||||
if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) {
|
||||
$item->expiresAt(\DateTime::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY])));
|
||||
} elseif (0 < $defaultLifetime) {
|
||||
$item->expiresAfter($defaultLifetime);
|
||||
}
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
|
||||
{
|
||||
$doSave = true;
|
||||
$callback = static function (CacheItem $item, bool &$save) use ($callback, &$doSave) {
|
||||
$value = $callback($item, $save);
|
||||
$doSave = $save;
|
||||
|
||||
return $value;
|
||||
};
|
||||
|
||||
$lastItem = null;
|
||||
$i = 0;
|
||||
$wrap = function (CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$i, &$doSave, &$lastItem, &$metadata) {
|
||||
$adapter = $this->adapters[$i];
|
||||
if (isset($this->adapters[++$i])) {
|
||||
$callback = $wrap;
|
||||
$beta = \INF === $beta ? \INF : 0;
|
||||
}
|
||||
if ($adapter instanceof CacheInterface) {
|
||||
$value = $adapter->get($key, $callback, $beta, $metadata);
|
||||
} else {
|
||||
$value = $this->doGet($adapter, $key, $callback, $beta, $metadata);
|
||||
}
|
||||
if (null !== $item) {
|
||||
(self::$syncItem)($lastItem = $lastItem ?? $item, $item, $this->defaultLifetime, $metadata);
|
||||
}
|
||||
$save = $doSave;
|
||||
|
||||
return $value;
|
||||
};
|
||||
|
||||
return $wrap();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
$syncItem = self::$syncItem;
|
||||
$misses = [];
|
||||
|
||||
foreach ($this->adapters as $i => $adapter) {
|
||||
$item = $adapter->getItem($key);
|
||||
|
||||
if ($item->isHit()) {
|
||||
while (0 <= --$i) {
|
||||
$this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime));
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
$misses[$i] = $item;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
return $this->generateItems($this->adapters[0]->getItems($keys), 0);
|
||||
}
|
||||
|
||||
private function generateItems(iterable $items, int $adapterIndex): \Generator
|
||||
{
|
||||
$missing = [];
|
||||
$misses = [];
|
||||
$nextAdapterIndex = $adapterIndex + 1;
|
||||
$nextAdapter = $this->adapters[$nextAdapterIndex] ?? null;
|
||||
|
||||
foreach ($items as $k => $item) {
|
||||
if (!$nextAdapter || $item->isHit()) {
|
||||
yield $k => $item;
|
||||
} else {
|
||||
$missing[] = $k;
|
||||
$misses[$k] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing) {
|
||||
$syncItem = self::$syncItem;
|
||||
$adapter = $this->adapters[$adapterIndex];
|
||||
$items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
|
||||
|
||||
foreach ($items as $k => $item) {
|
||||
if ($item->isHit()) {
|
||||
$adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime));
|
||||
}
|
||||
|
||||
yield $k => $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
foreach ($this->adapters as $adapter) {
|
||||
if ($adapter->hasItem($key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
$cleared = true;
|
||||
$i = $this->adapterCount;
|
||||
|
||||
while ($i--) {
|
||||
if ($this->adapters[$i] instanceof AdapterInterface) {
|
||||
$cleared = $this->adapters[$i]->clear($prefix) && $cleared;
|
||||
} else {
|
||||
$cleared = $this->adapters[$i]->clear() && $cleared;
|
||||
}
|
||||
}
|
||||
|
||||
return $cleared;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
$deleted = true;
|
||||
$i = $this->adapterCount;
|
||||
|
||||
while ($i--) {
|
||||
$deleted = $this->adapters[$i]->deleteItem($key) && $deleted;
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
$deleted = true;
|
||||
$i = $this->adapterCount;
|
||||
|
||||
while ($i--) {
|
||||
$deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
$saved = true;
|
||||
$i = $this->adapterCount;
|
||||
|
||||
while ($i--) {
|
||||
$saved = $this->adapters[$i]->save($item) && $saved;
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
$saved = true;
|
||||
$i = $this->adapterCount;
|
||||
|
||||
while ($i--) {
|
||||
$saved = $this->adapters[$i]->saveDeferred($item) && $saved;
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
$committed = true;
|
||||
$i = $this->adapterCount;
|
||||
|
||||
while ($i--) {
|
||||
$committed = $this->adapters[$i]->commit() && $committed;
|
||||
}
|
||||
|
||||
return $committed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$pruned = true;
|
||||
|
||||
foreach ($this->adapters as $adapter) {
|
||||
if ($adapter instanceof PruneableInterface) {
|
||||
$pruned = $adapter->prune() && $pruned;
|
||||
}
|
||||
}
|
||||
|
||||
return $pruned;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
foreach ($this->adapters as $adapter) {
|
||||
if ($adapter instanceof ResetInterface) {
|
||||
$adapter->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
252
vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php
vendored
Normal file
252
vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
|
||||
/**
|
||||
* @author Antonio Jose Cerezo Aranda <aj.cerezo@gmail.com>
|
||||
*/
|
||||
class CouchbaseBucketAdapter extends AbstractAdapter
|
||||
{
|
||||
private const THIRTY_DAYS_IN_SECONDS = 2592000;
|
||||
private const MAX_KEY_LENGTH = 250;
|
||||
private const KEY_NOT_FOUND = 13;
|
||||
private const VALID_DSN_OPTIONS = [
|
||||
'operationTimeout',
|
||||
'configTimeout',
|
||||
'configNodeTimeout',
|
||||
'n1qlTimeout',
|
||||
'httpTimeout',
|
||||
'configDelay',
|
||||
'htconfigIdleTimeout',
|
||||
'durabilityInterval',
|
||||
'durabilityTimeout',
|
||||
];
|
||||
|
||||
private $bucket;
|
||||
private $marshaller;
|
||||
|
||||
public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
|
||||
}
|
||||
|
||||
$this->maxIdLength = static::MAX_KEY_LENGTH;
|
||||
|
||||
$this->bucket = $bucket;
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
$this->enableVersioning();
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $servers
|
||||
*/
|
||||
public static function createConnection($servers, array $options = []): \CouchbaseBucket
|
||||
{
|
||||
if (\is_string($servers)) {
|
||||
$servers = [$servers];
|
||||
} elseif (!\is_array($servers)) {
|
||||
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($servers)));
|
||||
}
|
||||
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
|
||||
}
|
||||
|
||||
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
|
||||
|
||||
$dsnPattern = '/^(?<protocol>couchbase(?:s)?)\:\/\/(?:(?<username>[^\:]+)\:(?<password>[^\@]{6,})@)?'
|
||||
.'(?<host>[^\:]+(?:\:\d+)?)(?:\/(?<bucketName>[^\?]+))(?:\?(?<options>.*))?$/i';
|
||||
|
||||
$newServers = [];
|
||||
$protocol = 'couchbase';
|
||||
try {
|
||||
$options = self::initOptions($options);
|
||||
$username = $options['username'];
|
||||
$password = $options['password'];
|
||||
|
||||
foreach ($servers as $dsn) {
|
||||
if (0 !== strpos($dsn, 'couchbase:')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $dsn));
|
||||
}
|
||||
|
||||
preg_match($dsnPattern, $dsn, $matches);
|
||||
|
||||
$username = $matches['username'] ?: $username;
|
||||
$password = $matches['password'] ?: $password;
|
||||
$protocol = $matches['protocol'] ?: $protocol;
|
||||
|
||||
if (isset($matches['options'])) {
|
||||
$optionsInDsn = self::getOptions($matches['options']);
|
||||
|
||||
foreach ($optionsInDsn as $parameter => $value) {
|
||||
$options[$parameter] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$newServers[] = $matches['host'];
|
||||
}
|
||||
|
||||
$connectionString = $protocol.'://'.implode(',', $newServers);
|
||||
|
||||
$client = new \CouchbaseCluster($connectionString);
|
||||
$client->authenticateAs($username, $password);
|
||||
|
||||
$bucket = $client->openBucket($matches['bucketName']);
|
||||
|
||||
unset($options['username'], $options['password']);
|
||||
foreach ($options as $option => $value) {
|
||||
if (!empty($value)) {
|
||||
$bucket->$option = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $bucket;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
public static function isSupported(): bool
|
||||
{
|
||||
return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<');
|
||||
}
|
||||
|
||||
private static function getOptions(string $options): array
|
||||
{
|
||||
$results = [];
|
||||
$optionsInArray = explode('&', $options);
|
||||
|
||||
foreach ($optionsInArray as $option) {
|
||||
[$key, $value] = explode('=', $option);
|
||||
|
||||
if (\in_array($key, static::VALID_DSN_OPTIONS, true)) {
|
||||
$results[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private static function initOptions(array $options): array
|
||||
{
|
||||
$options['username'] = $options['username'] ?? '';
|
||||
$options['password'] = $options['password'] ?? '';
|
||||
$options['operationTimeout'] = $options['operationTimeout'] ?? 0;
|
||||
$options['configTimeout'] = $options['configTimeout'] ?? 0;
|
||||
$options['configNodeTimeout'] = $options['configNodeTimeout'] ?? 0;
|
||||
$options['n1qlTimeout'] = $options['n1qlTimeout'] ?? 0;
|
||||
$options['httpTimeout'] = $options['httpTimeout'] ?? 0;
|
||||
$options['configDelay'] = $options['configDelay'] ?? 0;
|
||||
$options['htconfigIdleTimeout'] = $options['htconfigIdleTimeout'] ?? 0;
|
||||
$options['durabilityInterval'] = $options['durabilityInterval'] ?? 0;
|
||||
$options['durabilityTimeout'] = $options['durabilityTimeout'] ?? 0;
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$resultsCouchbase = $this->bucket->get($ids);
|
||||
|
||||
$results = [];
|
||||
foreach ($resultsCouchbase as $key => $value) {
|
||||
if (null !== $value->error) {
|
||||
continue;
|
||||
}
|
||||
$results[$key] = $this->marshaller->unmarshall($value->value);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id): bool
|
||||
{
|
||||
return false !== $this->bucket->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace): bool
|
||||
{
|
||||
if ('' === $namespace) {
|
||||
$this->bucket->manager()->flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids): bool
|
||||
{
|
||||
$results = $this->bucket->remove(array_values($ids));
|
||||
|
||||
foreach ($results as $key => $result) {
|
||||
if (null !== $result->error && static::KEY_NOT_FOUND !== $result->error->getCode()) {
|
||||
continue;
|
||||
}
|
||||
unset($results[$key]);
|
||||
}
|
||||
|
||||
return 0 === \count($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
$lifetime = $this->normalizeExpiry($lifetime);
|
||||
|
||||
$ko = [];
|
||||
foreach ($values as $key => $value) {
|
||||
$result = $this->bucket->upsert($key, $value, ['expiry' => $lifetime]);
|
||||
|
||||
if (null !== $result->error) {
|
||||
$ko[$key] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
return [] === $ko ? true : $ko;
|
||||
}
|
||||
|
||||
private function normalizeExpiry(int $expiry): int
|
||||
{
|
||||
if ($expiry && $expiry > static::THIRTY_DAYS_IN_SECONDS) {
|
||||
$expiry += time();
|
||||
}
|
||||
|
||||
return $expiry;
|
||||
}
|
||||
}
|
222
vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php
vendored
Normal file
222
vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Couchbase\Bucket;
|
||||
use Couchbase\Cluster;
|
||||
use Couchbase\ClusterOptions;
|
||||
use Couchbase\Collection;
|
||||
use Couchbase\DocumentNotFoundException;
|
||||
use Couchbase\UpsertOptions;
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
|
||||
/**
|
||||
* @author Antonio Jose Cerezo Aranda <aj.cerezo@gmail.com>
|
||||
*/
|
||||
class CouchbaseCollectionAdapter extends AbstractAdapter
|
||||
{
|
||||
private const MAX_KEY_LENGTH = 250;
|
||||
|
||||
/** @var Collection */
|
||||
private $connection;
|
||||
private $marshaller;
|
||||
|
||||
public function __construct(Collection $connection, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.');
|
||||
}
|
||||
|
||||
$this->maxIdLength = static::MAX_KEY_LENGTH;
|
||||
|
||||
$this->connection = $connection;
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
$this->enableVersioning();
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $dsn
|
||||
*
|
||||
* @return Bucket|Collection
|
||||
*/
|
||||
public static function createConnection($dsn, array $options = [])
|
||||
{
|
||||
if (\is_string($dsn)) {
|
||||
$dsn = [$dsn];
|
||||
} elseif (!\is_array($dsn)) {
|
||||
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($dsn)));
|
||||
}
|
||||
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.');
|
||||
}
|
||||
|
||||
set_error_handler(function ($type, $msg, $file, $line): bool { throw new \ErrorException($msg, 0, $type, $file, $line); });
|
||||
|
||||
$dsnPattern = '/^(?<protocol>couchbase(?:s)?)\:\/\/(?:(?<username>[^\:]+)\:(?<password>[^\@]{6,})@)?'
|
||||
.'(?<host>[^\:]+(?:\:\d+)?)(?:\/(?<bucketName>[^\/\?]+))(?:(?:\/(?<scopeName>[^\/]+))'
|
||||
.'(?:\/(?<collectionName>[^\/\?]+)))?(?:\/)?(?:\?(?<options>.*))?$/i';
|
||||
|
||||
$newServers = [];
|
||||
$protocol = 'couchbase';
|
||||
try {
|
||||
$username = $options['username'] ?? '';
|
||||
$password = $options['password'] ?? '';
|
||||
|
||||
foreach ($dsn as $server) {
|
||||
if (0 !== strpos($server, 'couchbase:')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $server));
|
||||
}
|
||||
|
||||
preg_match($dsnPattern, $server, $matches);
|
||||
|
||||
$username = $matches['username'] ?: $username;
|
||||
$password = $matches['password'] ?: $password;
|
||||
$protocol = $matches['protocol'] ?: $protocol;
|
||||
|
||||
if (isset($matches['options'])) {
|
||||
$optionsInDsn = self::getOptions($matches['options']);
|
||||
|
||||
foreach ($optionsInDsn as $parameter => $value) {
|
||||
$options[$parameter] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$newServers[] = $matches['host'];
|
||||
}
|
||||
|
||||
$option = isset($matches['options']) ? '?'.$matches['options'] : '';
|
||||
$connectionString = $protocol.'://'.implode(',', $newServers).$option;
|
||||
|
||||
$clusterOptions = new ClusterOptions();
|
||||
$clusterOptions->credentials($username, $password);
|
||||
|
||||
$client = new Cluster($connectionString, $clusterOptions);
|
||||
|
||||
$bucket = $client->bucket($matches['bucketName']);
|
||||
$collection = $bucket->defaultCollection();
|
||||
if (!empty($matches['scopeName'])) {
|
||||
$scope = $bucket->scope($matches['scopeName']);
|
||||
$collection = $scope->collection($matches['collectionName']);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
public static function isSupported(): bool
|
||||
{
|
||||
return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '3.0.5', '>=') && version_compare(phpversion('couchbase'), '4.0', '<');
|
||||
}
|
||||
|
||||
private static function getOptions(string $options): array
|
||||
{
|
||||
$results = [];
|
||||
$optionsInArray = explode('&', $options);
|
||||
|
||||
foreach ($optionsInArray as $option) {
|
||||
[$key, $value] = explode('=', $option);
|
||||
|
||||
$results[$key] = $value;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids): array
|
||||
{
|
||||
$results = [];
|
||||
foreach ($ids as $id) {
|
||||
try {
|
||||
$resultCouchbase = $this->connection->get($id);
|
||||
} catch (DocumentNotFoundException $exception) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $resultCouchbase->value ?? $resultCouchbase->content();
|
||||
|
||||
$results[$id] = $this->marshaller->unmarshall($content);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id): bool
|
||||
{
|
||||
return $this->connection->exists($id)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids): bool
|
||||
{
|
||||
$idsErrors = [];
|
||||
foreach ($ids as $id) {
|
||||
try {
|
||||
$result = $this->connection->remove($id);
|
||||
|
||||
if (null === $result->mutationToken()) {
|
||||
$idsErrors[] = $id;
|
||||
}
|
||||
} catch (DocumentNotFoundException $exception) {
|
||||
}
|
||||
}
|
||||
|
||||
return 0 === \count($idsErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
$upsertOptions = new UpsertOptions();
|
||||
$upsertOptions->expiry($lifetime);
|
||||
|
||||
$ko = [];
|
||||
foreach ($values as $key => $value) {
|
||||
try {
|
||||
$this->connection->upsert($key, $value, $upsertOptions);
|
||||
} catch (\Exception $exception) {
|
||||
$ko[$key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return [] === $ko ? true : $ko;
|
||||
}
|
||||
}
|
110
vendor/symfony/cache/Adapter/DoctrineAdapter.php
vendored
Normal file
110
vendor/symfony/cache/Adapter/DoctrineAdapter.php
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Doctrine\Common\Cache\CacheProvider;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @deprecated Since Symfony 5.4, use Doctrine\Common\Cache\Psr6\CacheAdapter instead
|
||||
*/
|
||||
class DoctrineAdapter extends AbstractAdapter
|
||||
{
|
||||
private $provider;
|
||||
|
||||
public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
|
||||
{
|
||||
trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "%s" instead.', __CLASS__, CacheAdapter::class);
|
||||
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->provider = $provider;
|
||||
$provider->setNamespace($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
$this->provider->setNamespace($this->provider->getNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback');
|
||||
try {
|
||||
return $this->provider->fetchMultiple($ids);
|
||||
} catch (\Error $e) {
|
||||
$trace = $e->getTrace();
|
||||
|
||||
if (isset($trace[0]['function']) && !isset($trace[0]['class'])) {
|
||||
switch ($trace[0]['function']) {
|
||||
case 'unserialize':
|
||||
case 'apcu_fetch':
|
||||
case 'apc_fetch':
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id)
|
||||
{
|
||||
return $this->provider->contains($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace)
|
||||
{
|
||||
$namespace = $this->provider->getNamespace();
|
||||
|
||||
return isset($namespace[0])
|
||||
? $this->provider->deleteAll()
|
||||
: $this->provider->flushAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$ok = true;
|
||||
foreach ($ids as $id) {
|
||||
$ok = $this->provider->delete($id) && $ok;
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
return $this->provider->saveMultiple($values, $lifetime);
|
||||
}
|
||||
}
|
397
vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php
vendored
Normal file
397
vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php
vendored
Normal file
@ -0,0 +1,397 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Exception\TableNotFoundException;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
|
||||
class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface
|
||||
{
|
||||
protected $maxIdLength = 255;
|
||||
|
||||
private $marshaller;
|
||||
private $conn;
|
||||
private $platformName;
|
||||
private $serverVersion;
|
||||
private $table = 'cache_items';
|
||||
private $idCol = 'item_id';
|
||||
private $dataCol = 'item_data';
|
||||
private $lifetimeCol = 'item_lifetime';
|
||||
private $timeCol = 'item_time';
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* You can either pass an existing database Doctrine DBAL Connection or
|
||||
* a DSN string that will be used to connect to the database.
|
||||
*
|
||||
* The cache table is created automatically when possible.
|
||||
* Otherwise, use the createTable() method.
|
||||
*
|
||||
* List of available options:
|
||||
* * db_table: The name of the table [default: cache_items]
|
||||
* * db_id_col: The column where to store the cache id [default: item_id]
|
||||
* * db_data_col: The column where to store the cache data [default: item_data]
|
||||
* * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
|
||||
* * db_time_col: The column where to store the timestamp [default: item_time]
|
||||
*
|
||||
* @param Connection|string $connOrDsn
|
||||
*
|
||||
* @throws InvalidArgumentException When namespace contains invalid characters
|
||||
*/
|
||||
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
|
||||
{
|
||||
if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
|
||||
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
|
||||
}
|
||||
|
||||
if ($connOrDsn instanceof Connection) {
|
||||
$this->conn = $connOrDsn;
|
||||
} elseif (\is_string($connOrDsn)) {
|
||||
if (!class_exists(DriverManager::class)) {
|
||||
throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connOrDsn));
|
||||
}
|
||||
$this->conn = DriverManager::getConnection(['url' => $connOrDsn]);
|
||||
} else {
|
||||
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be "%s" or string, "%s" given.', __METHOD__, Connection::class, get_debug_type($connOrDsn)));
|
||||
}
|
||||
|
||||
$this->table = $options['db_table'] ?? $this->table;
|
||||
$this->idCol = $options['db_id_col'] ?? $this->idCol;
|
||||
$this->dataCol = $options['db_data_col'] ?? $this->dataCol;
|
||||
$this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol;
|
||||
$this->timeCol = $options['db_time_col'] ?? $this->timeCol;
|
||||
$this->namespace = $namespace;
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the table to store cache items which can be called once for setup.
|
||||
*
|
||||
* Cache ID are saved in a column of maximum length 255. Cache data is
|
||||
* saved in a BLOB.
|
||||
*
|
||||
* @throws DBALException When the table already exists
|
||||
*/
|
||||
public function createTable()
|
||||
{
|
||||
$schema = new Schema();
|
||||
$this->addTableToSchema($schema);
|
||||
|
||||
foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) {
|
||||
$this->conn->executeStatement($sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureSchema(Schema $schema, Connection $forConnection): void
|
||||
{
|
||||
// only update the schema for this connection
|
||||
if ($forConnection !== $this->conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($schema->hasTable($this->table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addTableToSchema($schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune(): bool
|
||||
{
|
||||
$deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ?";
|
||||
$params = [time()];
|
||||
$paramTypes = [ParameterType::INTEGER];
|
||||
|
||||
if ('' !== $this->namespace) {
|
||||
$deleteSql .= " AND $this->idCol LIKE ?";
|
||||
$params[] = sprintf('%s%%', $this->namespace);
|
||||
$paramTypes[] = ParameterType::STRING;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->conn->executeStatement($deleteSql, $params, $paramTypes);
|
||||
} catch (TableNotFoundException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids): iterable
|
||||
{
|
||||
$now = time();
|
||||
$expired = [];
|
||||
|
||||
$sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN (?)";
|
||||
$result = $this->conn->executeQuery($sql, [
|
||||
$now,
|
||||
$ids,
|
||||
], [
|
||||
ParameterType::INTEGER,
|
||||
Connection::PARAM_STR_ARRAY,
|
||||
])->iterateNumeric();
|
||||
|
||||
foreach ($result as $row) {
|
||||
if (null === $row[1]) {
|
||||
$expired[] = $row[0];
|
||||
} else {
|
||||
yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($expired) {
|
||||
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN (?)";
|
||||
$this->conn->executeStatement($sql, [
|
||||
$now,
|
||||
$expired,
|
||||
], [
|
||||
ParameterType::INTEGER,
|
||||
Connection::PARAM_STR_ARRAY,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id): bool
|
||||
{
|
||||
$sql = "SELECT 1 FROM $this->table WHERE $this->idCol = ? AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ?)";
|
||||
$result = $this->conn->executeQuery($sql, [
|
||||
$id,
|
||||
time(),
|
||||
], [
|
||||
ParameterType::STRING,
|
||||
ParameterType::INTEGER,
|
||||
]);
|
||||
|
||||
return (bool) $result->fetchOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace): bool
|
||||
{
|
||||
if ('' === $namespace) {
|
||||
if ('sqlite' === $this->getPlatformName()) {
|
||||
$sql = "DELETE FROM $this->table";
|
||||
} else {
|
||||
$sql = "TRUNCATE TABLE $this->table";
|
||||
}
|
||||
} else {
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
|
||||
}
|
||||
|
||||
try {
|
||||
$this->conn->executeStatement($sql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids): bool
|
||||
{
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol IN (?)";
|
||||
try {
|
||||
$this->conn->executeStatement($sql, [array_values($ids)], [Connection::PARAM_STR_ARRAY]);
|
||||
} catch (TableNotFoundException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
$platformName = $this->getPlatformName();
|
||||
$insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?)";
|
||||
|
||||
switch (true) {
|
||||
case 'mysql' === $platformName:
|
||||
$sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
|
||||
break;
|
||||
case 'oci' === $platformName:
|
||||
// DUAL is Oracle specific dummy table
|
||||
$sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
|
||||
break;
|
||||
case 'sqlsrv' === $platformName && version_compare($this->getServerVersion(), '10', '>='):
|
||||
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
|
||||
// It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
|
||||
$sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
|
||||
break;
|
||||
case 'sqlite' === $platformName:
|
||||
$sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
|
||||
break;
|
||||
case 'pgsql' === $platformName && version_compare($this->getServerVersion(), '9.5', '>='):
|
||||
$sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
|
||||
break;
|
||||
default:
|
||||
$platformName = null;
|
||||
$sql = "UPDATE $this->table SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ? WHERE $this->idCol = ?";
|
||||
break;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$lifetime = $lifetime ?: null;
|
||||
try {
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
}
|
||||
|
||||
// $id and $data are defined later in the loop. Binding is done by reference, values are read on execution.
|
||||
if ('sqlsrv' === $platformName || 'oci' === $platformName) {
|
||||
$stmt->bindParam(1, $id);
|
||||
$stmt->bindParam(2, $id);
|
||||
$stmt->bindParam(3, $data, ParameterType::LARGE_OBJECT);
|
||||
$stmt->bindValue(4, $lifetime, ParameterType::INTEGER);
|
||||
$stmt->bindValue(5, $now, ParameterType::INTEGER);
|
||||
$stmt->bindParam(6, $data, ParameterType::LARGE_OBJECT);
|
||||
$stmt->bindValue(7, $lifetime, ParameterType::INTEGER);
|
||||
$stmt->bindValue(8, $now, ParameterType::INTEGER);
|
||||
} elseif (null !== $platformName) {
|
||||
$stmt->bindParam(1, $id);
|
||||
$stmt->bindParam(2, $data, ParameterType::LARGE_OBJECT);
|
||||
$stmt->bindValue(3, $lifetime, ParameterType::INTEGER);
|
||||
$stmt->bindValue(4, $now, ParameterType::INTEGER);
|
||||
} else {
|
||||
$stmt->bindParam(1, $data, ParameterType::LARGE_OBJECT);
|
||||
$stmt->bindValue(2, $lifetime, ParameterType::INTEGER);
|
||||
$stmt->bindValue(3, $now, ParameterType::INTEGER);
|
||||
$stmt->bindParam(4, $id);
|
||||
|
||||
$insertStmt = $this->conn->prepare($insertSql);
|
||||
$insertStmt->bindParam(1, $id);
|
||||
$insertStmt->bindParam(2, $data, ParameterType::LARGE_OBJECT);
|
||||
$insertStmt->bindValue(3, $lifetime, ParameterType::INTEGER);
|
||||
$insertStmt->bindValue(4, $now, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
foreach ($values as $id => $data) {
|
||||
try {
|
||||
$rowCount = $stmt->executeStatement();
|
||||
} catch (TableNotFoundException $e) {
|
||||
if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$rowCount = $stmt->executeStatement();
|
||||
}
|
||||
if (null === $platformName && 0 === $rowCount) {
|
||||
try {
|
||||
$insertStmt->executeStatement();
|
||||
} catch (DBALException $e) {
|
||||
// A concurrent write won, let it be
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $failed;
|
||||
}
|
||||
|
||||
private function getPlatformName(): string
|
||||
{
|
||||
if (isset($this->platformName)) {
|
||||
return $this->platformName;
|
||||
}
|
||||
|
||||
$platform = $this->conn->getDatabasePlatform();
|
||||
|
||||
switch (true) {
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\MySQLPlatform:
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\MySQL57Platform:
|
||||
return $this->platformName = 'mysql';
|
||||
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\SqlitePlatform:
|
||||
return $this->platformName = 'sqlite';
|
||||
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform:
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQL94Platform:
|
||||
return $this->platformName = 'pgsql';
|
||||
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform:
|
||||
return $this->platformName = 'oci';
|
||||
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform:
|
||||
case $platform instanceof \Doctrine\DBAL\Platforms\SQLServer2012Platform:
|
||||
return $this->platformName = 'sqlsrv';
|
||||
|
||||
default:
|
||||
return $this->platformName = \get_class($platform);
|
||||
}
|
||||
}
|
||||
|
||||
private function getServerVersion(): string
|
||||
{
|
||||
if (isset($this->serverVersion)) {
|
||||
return $this->serverVersion;
|
||||
}
|
||||
|
||||
$conn = $this->conn->getWrappedConnection();
|
||||
if ($conn instanceof ServerInfoAwareConnection) {
|
||||
return $this->serverVersion = $conn->getServerVersion();
|
||||
}
|
||||
|
||||
return $this->serverVersion = '0';
|
||||
}
|
||||
|
||||
private function addTableToSchema(Schema $schema): void
|
||||
{
|
||||
$types = [
|
||||
'mysql' => 'binary',
|
||||
'sqlite' => 'text',
|
||||
];
|
||||
|
||||
$table = $schema->createTable($this->table);
|
||||
$table->addColumn($this->idCol, $types[$this->getPlatformName()] ?? 'string', ['length' => 255]);
|
||||
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
|
||||
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
|
||||
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
|
||||
$table->setPrimaryKey([$this->idCol]);
|
||||
}
|
||||
}
|
29
vendor/symfony/cache/Adapter/FilesystemAdapter.php
vendored
Normal file
29
vendor/symfony/cache/Adapter/FilesystemAdapter.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
||||
|
||||
class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
|
||||
{
|
||||
use FilesystemTrait;
|
||||
|
||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->init($namespace, $directory);
|
||||
}
|
||||
}
|
239
vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
vendored
Normal file
239
vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
||||
|
||||
/**
|
||||
* Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author André Rømcke <andre.romcke+symfony@gmail.com>
|
||||
*/
|
||||
class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
|
||||
{
|
||||
use FilesystemTrait {
|
||||
doClear as private doClearCache;
|
||||
doSave as private doSaveCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Folder used for tag symlinks.
|
||||
*/
|
||||
private const TAG_FOLDER = 'tags';
|
||||
|
||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->marshaller = new TagAwareMarshaller($marshaller);
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->init($namespace, $directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace)
|
||||
{
|
||||
$ok = $this->doClearCache($namespace);
|
||||
|
||||
if ('' !== $namespace) {
|
||||
return $ok;
|
||||
}
|
||||
|
||||
set_error_handler(static function () {});
|
||||
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
try {
|
||||
foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
|
||||
if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) {
|
||||
$dir = $renamed.\DIRECTORY_SEPARATOR;
|
||||
} else {
|
||||
$dir .= \DIRECTORY_SEPARATOR;
|
||||
$renamed = null;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < 38; ++$i) {
|
||||
if (!is_dir($dir.$chars[$i])) {
|
||||
continue;
|
||||
}
|
||||
for ($j = 0; $j < 38; ++$j) {
|
||||
if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
|
||||
continue;
|
||||
}
|
||||
foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) {
|
||||
if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) {
|
||||
unlink($d.\DIRECTORY_SEPARATOR.$link);
|
||||
}
|
||||
}
|
||||
null === $renamed ?: rmdir($d);
|
||||
}
|
||||
null === $renamed ?: rmdir($dir.$chars[$i]);
|
||||
}
|
||||
null === $renamed ?: rmdir($renamed);
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array
|
||||
{
|
||||
$failed = $this->doSaveCache($values, $lifetime);
|
||||
|
||||
// Add Tags as symlinks
|
||||
foreach ($addTagData as $tagId => $ids) {
|
||||
$tagFolder = $this->getTagFolder($tagId);
|
||||
foreach ($ids as $id) {
|
||||
if ($failed && \in_array($id, $failed, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = $this->getFile($id);
|
||||
|
||||
if (!@symlink($file, $tagLink = $this->getFile($id, true, $tagFolder)) && !is_link($tagLink)) {
|
||||
@unlink($file);
|
||||
$failed[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unlink removed Tags
|
||||
foreach ($removeTagData as $tagId => $ids) {
|
||||
$tagFolder = $this->getTagFolder($tagId);
|
||||
foreach ($ids as $id) {
|
||||
if ($failed && \in_array($id, $failed, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@unlink($this->getFile($id, false, $tagFolder));
|
||||
}
|
||||
}
|
||||
|
||||
return $failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteYieldTags(array $ids): iterable
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
$file = $this->getFile($id);
|
||||
if (!is_file($file) || !$h = @fopen($file, 'r')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) {
|
||||
fclose($h);
|
||||
continue;
|
||||
}
|
||||
|
||||
$meta = explode("\n", fread($h, 4096), 3)[2] ?? '';
|
||||
|
||||
// detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
|
||||
if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) {
|
||||
$meta[9] = "\0";
|
||||
$tagLen = unpack('Nlen', $meta, 9)['len'];
|
||||
$meta = substr($meta, 13, $tagLen);
|
||||
|
||||
if (0 < $tagLen -= \strlen($meta)) {
|
||||
$meta .= fread($h, $tagLen);
|
||||
}
|
||||
|
||||
try {
|
||||
yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta);
|
||||
} catch (\Exception $e) {
|
||||
yield $id => [];
|
||||
}
|
||||
}
|
||||
|
||||
fclose($h);
|
||||
|
||||
if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteTagRelations(array $tagData): bool
|
||||
{
|
||||
foreach ($tagData as $tagId => $idList) {
|
||||
$tagFolder = $this->getTagFolder($tagId);
|
||||
foreach ($idList as $id) {
|
||||
@unlink($this->getFile($id, false, $tagFolder));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doInvalidate(array $tagIds): bool
|
||||
{
|
||||
foreach ($tagIds as $tagId) {
|
||||
if (!is_dir($tagFolder = $this->getTagFolder($tagId))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
set_error_handler(static function () {});
|
||||
|
||||
try {
|
||||
if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) {
|
||||
$tagFolder = $renamed.\DIRECTORY_SEPARATOR;
|
||||
} else {
|
||||
$renamed = null;
|
||||
}
|
||||
|
||||
foreach ($this->scanHashDir($tagFolder) as $itemLink) {
|
||||
unlink(realpath($itemLink) ?: $itemLink);
|
||||
unlink($itemLink);
|
||||
}
|
||||
|
||||
if (null === $renamed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
for ($i = 0; $i < 38; ++$i) {
|
||||
for ($j = 0; $j < 38; ++$j) {
|
||||
rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
|
||||
}
|
||||
rmdir($tagFolder.$chars[$i]);
|
||||
}
|
||||
rmdir($renamed);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getTagFolder(string $tagId): string
|
||||
{
|
||||
return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
353
vendor/symfony/cache/Adapter/MemcachedAdapter.php
vendored
Normal file
353
vendor/symfony/cache/Adapter/MemcachedAdapter.php
vendored
Normal file
@ -0,0 +1,353 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
|
||||
/**
|
||||
* @author Rob Frawley 2nd <rmf@src.run>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class MemcachedAdapter extends AbstractAdapter
|
||||
{
|
||||
/**
|
||||
* We are replacing characters that are illegal in Memcached keys with reserved characters from
|
||||
* {@see \Symfony\Contracts\Cache\ItemInterface::RESERVED_CHARACTERS} that are legal in Memcached.
|
||||
* Note: don’t use {@see \Symfony\Component\Cache\Adapter\AbstractAdapter::NS_SEPARATOR}.
|
||||
*/
|
||||
private const RESERVED_MEMCACHED = " \n\r\t\v\f\0";
|
||||
private const RESERVED_PSR6 = '@()\{}/';
|
||||
|
||||
protected $maxIdLength = 250;
|
||||
|
||||
private const DEFAULT_CLIENT_OPTIONS = [
|
||||
'persistent_id' => null,
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
\Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
|
||||
];
|
||||
|
||||
private $marshaller;
|
||||
private $client;
|
||||
private $lazyClient;
|
||||
|
||||
/**
|
||||
* Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged.
|
||||
* Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that:
|
||||
* - the Memcached::OPT_BINARY_PROTOCOL must be enabled
|
||||
* (that's the default when using MemcachedAdapter::createConnection());
|
||||
* - tags eviction by Memcached's LRU algorithm will break by-tags invalidation;
|
||||
* your Memcached memory should be large enough to never trigger LRU.
|
||||
*
|
||||
* Using a MemcachedAdapter as a pure items store is fine.
|
||||
*/
|
||||
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.');
|
||||
}
|
||||
if ('Memcached' === \get_class($client)) {
|
||||
$opt = $client->getOption(\Memcached::OPT_SERIALIZER);
|
||||
if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
|
||||
throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
|
||||
}
|
||||
$this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
|
||||
$this->client = $client;
|
||||
} else {
|
||||
$this->lazyClient = $client;
|
||||
}
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
$this->enableVersioning();
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
}
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
return \extension_loaded('memcached') && version_compare(phpversion('memcached'), \PHP_VERSION_ID >= 80100 ? '3.1.6' : '2.2.0', '>=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Memcached instance.
|
||||
*
|
||||
* By default, the binary protocol, no block, and libketama compatible options are enabled.
|
||||
*
|
||||
* Examples for servers:
|
||||
* - 'memcached://user:pass@localhost?weight=33'
|
||||
* - [['localhost', 11211, 33]]
|
||||
*
|
||||
* @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
|
||||
*
|
||||
* @return \Memcached
|
||||
*
|
||||
* @throws \ErrorException When invalid options or servers are provided
|
||||
*/
|
||||
public static function createConnection($servers, array $options = [])
|
||||
{
|
||||
if (\is_string($servers)) {
|
||||
$servers = [$servers];
|
||||
} elseif (!\is_array($servers)) {
|
||||
throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', get_debug_type($servers)));
|
||||
}
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.');
|
||||
}
|
||||
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
|
||||
try {
|
||||
$options += static::DEFAULT_CLIENT_OPTIONS;
|
||||
$client = new \Memcached($options['persistent_id']);
|
||||
$username = $options['username'];
|
||||
$password = $options['password'];
|
||||
|
||||
// parse any DSN in $servers
|
||||
foreach ($servers as $i => $dsn) {
|
||||
if (\is_array($dsn)) {
|
||||
continue;
|
||||
}
|
||||
if (!str_starts_with($dsn, 'memcached:')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached:".', $dsn));
|
||||
}
|
||||
$params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
|
||||
if (!empty($m[2])) {
|
||||
[$username, $password] = explode(':', $m[2], 2) + [1 => null];
|
||||
}
|
||||
|
||||
return 'file:'.($m[1] ?? '');
|
||||
}, $dsn);
|
||||
if (false === $params = parse_url($params)) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
|
||||
}
|
||||
$query = $hosts = [];
|
||||
if (isset($params['query'])) {
|
||||
parse_str($params['query'], $query);
|
||||
|
||||
if (isset($query['host'])) {
|
||||
if (!\is_array($hosts = $query['host'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
|
||||
}
|
||||
foreach ($hosts as $host => $weight) {
|
||||
if (false === $port = strrpos($host, ':')) {
|
||||
$hosts[$host] = [$host, 11211, (int) $weight];
|
||||
} else {
|
||||
$hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight];
|
||||
}
|
||||
}
|
||||
$hosts = array_values($hosts);
|
||||
unset($query['host']);
|
||||
}
|
||||
if ($hosts && !isset($params['host']) && !isset($params['path'])) {
|
||||
unset($servers[$i]);
|
||||
$servers = array_merge($servers, $hosts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isset($params['host']) && !isset($params['path'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
|
||||
}
|
||||
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
|
||||
$params['weight'] = $m[1];
|
||||
$params['path'] = substr($params['path'], 0, -\strlen($m[0]));
|
||||
}
|
||||
$params += [
|
||||
'host' => $params['host'] ?? $params['path'],
|
||||
'port' => isset($params['host']) ? 11211 : null,
|
||||
'weight' => 0,
|
||||
];
|
||||
if ($query) {
|
||||
$params += $query;
|
||||
$options = $query + $options;
|
||||
}
|
||||
|
||||
$servers[$i] = [$params['host'], $params['port'], $params['weight']];
|
||||
|
||||
if ($hosts) {
|
||||
$servers = array_merge($servers, $hosts);
|
||||
}
|
||||
}
|
||||
|
||||
// set client's options
|
||||
unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']);
|
||||
$options = array_change_key_case($options, \CASE_UPPER);
|
||||
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
|
||||
$client->setOption(\Memcached::OPT_NO_BLOCK, true);
|
||||
$client->setOption(\Memcached::OPT_TCP_NODELAY, true);
|
||||
if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
|
||||
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
|
||||
}
|
||||
foreach ($options as $name => $value) {
|
||||
if (\is_int($name)) {
|
||||
continue;
|
||||
}
|
||||
if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
|
||||
$value = \constant('Memcached::'.$name.'_'.strtoupper($value));
|
||||
}
|
||||
unset($options[$name]);
|
||||
|
||||
if (\defined('Memcached::OPT_'.$name)) {
|
||||
$options[\constant('Memcached::OPT_'.$name)] = $value;
|
||||
}
|
||||
}
|
||||
$client->setOptions($options);
|
||||
|
||||
// set client's servers, taking care of persistent connections
|
||||
if (!$client->isPristine()) {
|
||||
$oldServers = [];
|
||||
foreach ($client->getServerList() as $server) {
|
||||
$oldServers[] = [$server['host'], $server['port']];
|
||||
}
|
||||
|
||||
$newServers = [];
|
||||
foreach ($servers as $server) {
|
||||
if (1 < \count($server)) {
|
||||
$server = array_values($server);
|
||||
unset($server[2]);
|
||||
$server[1] = (int) $server[1];
|
||||
}
|
||||
$newServers[] = $server;
|
||||
}
|
||||
|
||||
if ($oldServers !== $newServers) {
|
||||
$client->resetServerList();
|
||||
$client->addServers($servers);
|
||||
}
|
||||
} else {
|
||||
$client->addServers($servers);
|
||||
}
|
||||
|
||||
if (null !== $username || null !== $password) {
|
||||
if (!method_exists($client, 'setSaslAuthData')) {
|
||||
trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
|
||||
}
|
||||
$client->setSaslAuthData($username, $password);
|
||||
}
|
||||
|
||||
return $client;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
if ($lifetime && $lifetime > 30 * 86400) {
|
||||
$lifetime += time();
|
||||
}
|
||||
|
||||
$encodedValues = [];
|
||||
foreach ($values as $key => $value) {
|
||||
$encodedValues[self::encodeKey($key)] = $value;
|
||||
}
|
||||
|
||||
return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
try {
|
||||
$encodedIds = array_map([__CLASS__, 'encodeKey'], $ids);
|
||||
|
||||
$encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
|
||||
|
||||
$result = [];
|
||||
foreach ($encodedResult as $key => $value) {
|
||||
$result[self::decodeKey($key)] = $this->marshaller->unmarshall($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id)
|
||||
{
|
||||
return false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$ok = true;
|
||||
$encodedIds = array_map([__CLASS__, 'encodeKey'], $ids);
|
||||
foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) {
|
||||
if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace)
|
||||
{
|
||||
return '' === $namespace && $this->getClient()->flush();
|
||||
}
|
||||
|
||||
private function checkResultCode($result)
|
||||
{
|
||||
$code = $this->client->getResultCode();
|
||||
|
||||
if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage()));
|
||||
}
|
||||
|
||||
private function getClient(): \Memcached
|
||||
{
|
||||
if ($this->client) {
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
$opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER);
|
||||
if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
|
||||
throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
|
||||
}
|
||||
if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) {
|
||||
throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix));
|
||||
}
|
||||
|
||||
return $this->client = $this->lazyClient;
|
||||
}
|
||||
|
||||
private static function encodeKey(string $key): string
|
||||
{
|
||||
return strtr($key, self::RESERVED_MEMCACHED, self::RESERVED_PSR6);
|
||||
}
|
||||
|
||||
private static function decodeKey(string $key): string
|
||||
{
|
||||
return strtr($key, self::RESERVED_PSR6, self::RESERVED_MEMCACHED);
|
||||
}
|
||||
}
|
152
vendor/symfony/cache/Adapter/NullAdapter.php
vendored
Normal file
152
vendor/symfony/cache/Adapter/NullAdapter.php
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
* @author Titouan Galopin <galopintitouan@gmail.com>
|
||||
*/
|
||||
class NullAdapter implements AdapterInterface, CacheInterface
|
||||
{
|
||||
private static $createCacheItem;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
|
||||
static function ($key) {
|
||||
$item = new CacheItem();
|
||||
$item->key = $key;
|
||||
$item->isHit = false;
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
|
||||
{
|
||||
$save = true;
|
||||
|
||||
return $callback((self::$createCacheItem)($key), $save);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
return (self::$createCacheItem)($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
return $this->generateItems($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
return $this->deleteItem($key);
|
||||
}
|
||||
|
||||
private function generateItems(array $keys): \Generator
|
||||
{
|
||||
$f = self::$createCacheItem;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
yield $key => $f($key);
|
||||
}
|
||||
}
|
||||
}
|
35
vendor/symfony/cache/Adapter/ParameterNormalizer.php
vendored
Normal file
35
vendor/symfony/cache/Adapter/ParameterNormalizer.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
/**
|
||||
* @author Lars Strojny <lars@strojny.net>
|
||||
*/
|
||||
final class ParameterNormalizer
|
||||
{
|
||||
public static function normalizeDuration(string $duration): int
|
||||
{
|
||||
if (is_numeric($duration)) {
|
||||
return $duration;
|
||||
}
|
||||
|
||||
if (false !== $time = strtotime($duration, 0)) {
|
||||
return $time;
|
||||
}
|
||||
|
||||
try {
|
||||
return \DateTime::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp();
|
||||
} catch (\Exception $e) {
|
||||
throw new \InvalidArgumentException(sprintf('Cannot parse date interval "%s".', $duration), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
583
vendor/symfony/cache/Adapter/PdoAdapter.php
vendored
Normal file
583
vendor/symfony/cache/Adapter/PdoAdapter.php
vendored
Normal file
@ -0,0 +1,583 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
|
||||
class PdoAdapter extends AbstractAdapter implements PruneableInterface
|
||||
{
|
||||
protected $maxIdLength = 255;
|
||||
|
||||
private $marshaller;
|
||||
private $conn;
|
||||
private $dsn;
|
||||
private $driver;
|
||||
private $serverVersion;
|
||||
private $table = 'cache_items';
|
||||
private $idCol = 'item_id';
|
||||
private $dataCol = 'item_data';
|
||||
private $lifetimeCol = 'item_lifetime';
|
||||
private $timeCol = 'item_time';
|
||||
private $username = '';
|
||||
private $password = '';
|
||||
private $connectionOptions = [];
|
||||
private $namespace;
|
||||
|
||||
private $dbalAdapter;
|
||||
|
||||
/**
|
||||
* You can either pass an existing database connection as PDO instance or
|
||||
* a DSN string that will be used to lazy-connect to the database when the
|
||||
* cache is actually used.
|
||||
*
|
||||
* List of available options:
|
||||
* * db_table: The name of the table [default: cache_items]
|
||||
* * db_id_col: The column where to store the cache id [default: item_id]
|
||||
* * db_data_col: The column where to store the cache data [default: item_data]
|
||||
* * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
|
||||
* * db_time_col: The column where to store the timestamp [default: item_time]
|
||||
* * db_username: The username when lazy-connect [default: '']
|
||||
* * db_password: The password when lazy-connect [default: '']
|
||||
* * db_connection_options: An array of driver-specific connection options [default: []]
|
||||
*
|
||||
* @param \PDO|string $connOrDsn
|
||||
*
|
||||
* @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
|
||||
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
|
||||
* @throws InvalidArgumentException When namespace contains invalid characters
|
||||
*/
|
||||
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
|
||||
{
|
||||
if ($connOrDsn instanceof Connection || (\is_string($connOrDsn) && str_contains($connOrDsn, '://'))) {
|
||||
trigger_deprecation('symfony/cache', '5.4', 'Usage of a DBAL Connection with "%s" is deprecated and will be removed in symfony 6.0. Use "%s" instead.', __CLASS__, DoctrineDbalAdapter::class);
|
||||
$this->dbalAdapter = new DoctrineDbalAdapter($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
|
||||
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
|
||||
}
|
||||
|
||||
if ($connOrDsn instanceof \PDO) {
|
||||
if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
|
||||
throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__));
|
||||
}
|
||||
|
||||
$this->conn = $connOrDsn;
|
||||
} elseif (\is_string($connOrDsn)) {
|
||||
$this->dsn = $connOrDsn;
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, get_debug_type($connOrDsn)));
|
||||
}
|
||||
|
||||
$this->table = $options['db_table'] ?? $this->table;
|
||||
$this->idCol = $options['db_id_col'] ?? $this->idCol;
|
||||
$this->dataCol = $options['db_data_col'] ?? $this->dataCol;
|
||||
$this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol;
|
||||
$this->timeCol = $options['db_time_col'] ?? $this->timeCol;
|
||||
$this->username = $options['db_username'] ?? $this->username;
|
||||
$this->password = $options['db_password'] ?? $this->password;
|
||||
$this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions;
|
||||
$this->namespace = $namespace;
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->getItem($key);
|
||||
}
|
||||
|
||||
return parent::getItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->getItems($keys);
|
||||
}
|
||||
|
||||
return parent::getItems($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->hasItem($key);
|
||||
}
|
||||
|
||||
return parent::hasItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->deleteItem($key);
|
||||
}
|
||||
|
||||
return parent::deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->deleteItems($keys);
|
||||
}
|
||||
|
||||
return parent::deleteItems($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->clear($prefix);
|
||||
}
|
||||
|
||||
return parent::clear($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->get($key, $callback, $beta, $metadata);
|
||||
}
|
||||
|
||||
return parent::get($key, $callback, $beta, $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->delete($key);
|
||||
}
|
||||
|
||||
return parent::delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->save($item);
|
||||
}
|
||||
|
||||
return parent::save($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->saveDeferred($item);
|
||||
}
|
||||
|
||||
return parent::saveDeferred($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
$this->dbalAdapter->setLogger($logger);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
parent::setLogger($logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->commit();
|
||||
}
|
||||
|
||||
return parent::commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
$this->dbalAdapter->reset();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
parent::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the table to store cache items which can be called once for setup.
|
||||
*
|
||||
* Cache ID are saved in a column of maximum length 255. Cache data is
|
||||
* saved in a BLOB.
|
||||
*
|
||||
* @throws \PDOException When the table already exists
|
||||
* @throws \DomainException When an unsupported PDO driver is used
|
||||
*/
|
||||
public function createTable()
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
$this->dbalAdapter->createTable();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// connect if we are not yet
|
||||
$conn = $this->getConnection();
|
||||
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
// We use varbinary for the ID column because it prevents unwanted conversions:
|
||||
// - character set conversions between server and client
|
||||
// - trailing space removal
|
||||
// - case-insensitivity
|
||||
// - language processing like é == e
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB";
|
||||
break;
|
||||
case 'sqlite':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'pgsql':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'oci':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'sqlsrv':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
default:
|
||||
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
|
||||
}
|
||||
|
||||
$conn->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Table to the Schema if the adapter uses this Connection.
|
||||
*
|
||||
* @deprecated since symfony/cache 5.4 use DoctrineDbalAdapter instead
|
||||
*/
|
||||
public function configureSchema(Schema $schema, Connection $forConnection): void
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
$this->dbalAdapter->configureSchema($schema, $forConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
if (isset($this->dbalAdapter)) {
|
||||
return $this->dbalAdapter->prune();
|
||||
}
|
||||
|
||||
$deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time";
|
||||
|
||||
if ('' !== $this->namespace) {
|
||||
$deleteSql .= " AND $this->idCol LIKE :namespace";
|
||||
}
|
||||
|
||||
$connection = $this->getConnection();
|
||||
|
||||
try {
|
||||
$delete = $connection->prepare($deleteSql);
|
||||
} catch (\PDOException $e) {
|
||||
return true;
|
||||
}
|
||||
$delete->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
|
||||
if ('' !== $this->namespace) {
|
||||
$delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
|
||||
}
|
||||
try {
|
||||
return $delete->execute();
|
||||
} catch (\PDOException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
|
||||
$now = time();
|
||||
$expired = [];
|
||||
|
||||
$sql = str_pad('', (\count($ids) << 1) - 1, '?,');
|
||||
$sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)";
|
||||
$stmt = $connection->prepare($sql);
|
||||
$stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
|
||||
foreach ($ids as $id) {
|
||||
$stmt->bindValue(++$i, $id);
|
||||
}
|
||||
$result = $stmt->execute();
|
||||
|
||||
if (\is_object($result)) {
|
||||
$result = $result->iterateNumeric();
|
||||
} else {
|
||||
$stmt->setFetchMode(\PDO::FETCH_NUM);
|
||||
$result = $stmt;
|
||||
}
|
||||
|
||||
foreach ($result as $row) {
|
||||
if (null === $row[1]) {
|
||||
$expired[] = $row[0];
|
||||
} else {
|
||||
yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($expired) {
|
||||
$sql = str_pad('', (\count($expired) << 1) - 1, '?,');
|
||||
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)";
|
||||
$stmt = $connection->prepare($sql);
|
||||
$stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
|
||||
foreach ($expired as $id) {
|
||||
$stmt->bindValue(++$i, $id);
|
||||
}
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id)
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
|
||||
$sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
|
||||
$stmt = $connection->prepare($sql);
|
||||
|
||||
$stmt->bindValue(':id', $id);
|
||||
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
return (bool) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace)
|
||||
{
|
||||
$conn = $this->getConnection();
|
||||
|
||||
if ('' === $namespace) {
|
||||
if ('sqlite' === $this->driver) {
|
||||
$sql = "DELETE FROM $this->table";
|
||||
} else {
|
||||
$sql = "TRUNCATE TABLE $this->table";
|
||||
}
|
||||
} else {
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
|
||||
}
|
||||
|
||||
try {
|
||||
$conn->exec($sql);
|
||||
} catch (\PDOException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$sql = str_pad('', (\count($ids) << 1) - 1, '?,');
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)";
|
||||
try {
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->execute(array_values($ids));
|
||||
} catch (\PDOException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
$conn = $this->getConnection();
|
||||
|
||||
$driver = $this->driver;
|
||||
$insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
|
||||
|
||||
switch (true) {
|
||||
case 'mysql' === $driver:
|
||||
$sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
|
||||
break;
|
||||
case 'oci' === $driver:
|
||||
// DUAL is Oracle specific dummy table
|
||||
$sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
|
||||
break;
|
||||
case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='):
|
||||
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
|
||||
// It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
|
||||
$sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
|
||||
break;
|
||||
case 'sqlite' === $driver:
|
||||
$sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
|
||||
break;
|
||||
case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='):
|
||||
$sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
|
||||
break;
|
||||
default:
|
||||
$driver = null;
|
||||
$sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
|
||||
break;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$lifetime = $lifetime ?: null;
|
||||
try {
|
||||
$stmt = $conn->prepare($sql);
|
||||
} catch (\PDOException $e) {
|
||||
if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$stmt = $conn->prepare($sql);
|
||||
}
|
||||
|
||||
// $id and $data are defined later in the loop. Binding is done by reference, values are read on execution.
|
||||
if ('sqlsrv' === $driver || 'oci' === $driver) {
|
||||
$stmt->bindParam(1, $id);
|
||||
$stmt->bindParam(2, $id);
|
||||
$stmt->bindParam(3, $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(4, $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(5, $now, \PDO::PARAM_INT);
|
||||
$stmt->bindParam(6, $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(7, $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(8, $now, \PDO::PARAM_INT);
|
||||
} else {
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
||||
}
|
||||
if (null === $driver) {
|
||||
$insertStmt = $conn->prepare($insertSql);
|
||||
|
||||
$insertStmt->bindParam(':id', $id);
|
||||
$insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||
$insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
|
||||
$insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
foreach ($values as $id => $data) {
|
||||
try {
|
||||
$stmt->execute();
|
||||
} catch (\PDOException $e) {
|
||||
if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$stmt->execute();
|
||||
}
|
||||
if (null === $driver && !$stmt->rowCount()) {
|
||||
try {
|
||||
$insertStmt->execute();
|
||||
} catch (\PDOException $e) {
|
||||
// A concurrent write won, let it be
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $failed;
|
||||
}
|
||||
|
||||
private function getConnection(): \PDO
|
||||
{
|
||||
if (null === $this->conn) {
|
||||
$this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
|
||||
$this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
if (null === $this->driver) {
|
||||
$this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||
}
|
||||
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
private function getServerVersion(): string
|
||||
{
|
||||
if (null === $this->serverVersion) {
|
||||
$this->serverVersion = $this->conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
|
||||
return $this->serverVersion;
|
||||
}
|
||||
}
|
435
vendor/symfony/cache/Adapter/PhpArrayAdapter.php
vendored
Normal file
435
vendor/symfony/cache/Adapter/PhpArrayAdapter.php
vendored
Normal file
@ -0,0 +1,435 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\ContractsTrait;
|
||||
use Symfony\Component\Cache\Traits\ProxyTrait;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
|
||||
* Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
|
||||
*
|
||||
* @author Titouan Galopin <galopintitouan@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
|
||||
{
|
||||
use ContractsTrait;
|
||||
use ProxyTrait;
|
||||
|
||||
private $file;
|
||||
private $keys;
|
||||
private $values;
|
||||
|
||||
private static $createCacheItem;
|
||||
private static $valuesCache = [];
|
||||
|
||||
/**
|
||||
* @param string $file The PHP file were values are cached
|
||||
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
|
||||
*/
|
||||
public function __construct(string $file, AdapterInterface $fallbackPool)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->pool = $fallbackPool;
|
||||
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
|
||||
static function ($key, $value, $isHit) {
|
||||
$item = new CacheItem();
|
||||
$item->key = $key;
|
||||
$item->value = $value;
|
||||
$item->isHit = $isHit;
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This adapter takes advantage of how PHP stores arrays in its latest versions.
|
||||
*
|
||||
* @param string $file The PHP file were values are cached
|
||||
* @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
|
||||
*
|
||||
* @return CacheItemPoolInterface
|
||||
*/
|
||||
public static function create(string $file, CacheItemPoolInterface $fallbackPool)
|
||||
{
|
||||
if (!$fallbackPool instanceof AdapterInterface) {
|
||||
$fallbackPool = new ProxyAdapter($fallbackPool);
|
||||
}
|
||||
|
||||
return new static($file, $fallbackPool);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
|
||||
{
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
if (!isset($this->keys[$key])) {
|
||||
get_from_pool:
|
||||
if ($this->pool instanceof CacheInterface) {
|
||||
return $this->pool->get($key, $callback, $beta, $metadata);
|
||||
}
|
||||
|
||||
return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
|
||||
}
|
||||
$value = $this->values[$this->keys[$key]];
|
||||
|
||||
if ('N;' === $value) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if ($value instanceof \Closure) {
|
||||
return $value();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
unset($this->keys[$key]);
|
||||
goto get_from_pool;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
if (!isset($this->keys[$key])) {
|
||||
return $this->pool->getItem($key);
|
||||
}
|
||||
|
||||
$value = $this->values[$this->keys[$key]];
|
||||
$isHit = true;
|
||||
|
||||
if ('N;' === $value) {
|
||||
$value = null;
|
||||
} elseif ($value instanceof \Closure) {
|
||||
try {
|
||||
$value = $value();
|
||||
} catch (\Throwable $e) {
|
||||
$value = null;
|
||||
$isHit = false;
|
||||
}
|
||||
}
|
||||
|
||||
return (self::$createCacheItem)($key, $value, $isHit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
|
||||
}
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return $this->generateItems($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return isset($this->keys[$key]) || $this->pool->hasItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
$deleted = true;
|
||||
$fallbackKeys = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
|
||||
}
|
||||
|
||||
if (isset($this->keys[$key])) {
|
||||
$deleted = false;
|
||||
} else {
|
||||
$fallbackKeys[] = $key;
|
||||
}
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if ($fallbackKeys) {
|
||||
$deleted = $this->pool->deleteItems($fallbackKeys) && $deleted;
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
return $this->pool->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
$this->keys = $this->values = [];
|
||||
|
||||
$cleared = @unlink($this->file) || !file_exists($this->file);
|
||||
unset(self::$valuesCache[$this->file]);
|
||||
|
||||
if ($this->pool instanceof AdapterInterface) {
|
||||
return $this->pool->clear($prefix) && $cleared;
|
||||
}
|
||||
|
||||
return $this->pool->clear() && $cleared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an array of cached values.
|
||||
*
|
||||
* @param array $values The cached values
|
||||
*
|
||||
* @return string[] A list of classes to preload on PHP 7.4+
|
||||
*/
|
||||
public function warmUp(array $values)
|
||||
{
|
||||
if (file_exists($this->file)) {
|
||||
if (!is_file($this->file)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".', $this->file));
|
||||
}
|
||||
|
||||
if (!is_writable($this->file)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".', $this->file));
|
||||
}
|
||||
} else {
|
||||
$directory = \dirname($this->file);
|
||||
|
||||
if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".', $directory));
|
||||
}
|
||||
|
||||
if (!is_writable($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".', $directory));
|
||||
}
|
||||
}
|
||||
|
||||
$preload = [];
|
||||
$dumpedValues = '';
|
||||
$dumpedMap = [];
|
||||
$dump = <<<'EOF'
|
||||
<?php
|
||||
|
||||
// This file has been auto-generated by the Symfony Cache Component.
|
||||
|
||||
return [[
|
||||
|
||||
|
||||
EOF;
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
|
||||
$isStaticValue = true;
|
||||
|
||||
if (null === $value) {
|
||||
$value = "'N;'";
|
||||
} elseif (\is_object($value) || \is_array($value)) {
|
||||
try {
|
||||
$value = VarExporter::export($value, $isStaticValue, $preload);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e);
|
||||
}
|
||||
} elseif (\is_string($value)) {
|
||||
// Wrap "N;" in a closure to not confuse it with an encoded `null`
|
||||
if ('N;' === $value) {
|
||||
$isStaticValue = false;
|
||||
}
|
||||
$value = var_export($value, true);
|
||||
} elseif (!\is_scalar($value)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)));
|
||||
} else {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
|
||||
if (!$isStaticValue) {
|
||||
$value = str_replace("\n", "\n ", $value);
|
||||
$value = "static function () {\n return {$value};\n}";
|
||||
}
|
||||
$hash = hash('md5', $value);
|
||||
|
||||
if (null === $id = $dumpedMap[$hash] ?? null) {
|
||||
$id = $dumpedMap[$hash] = \count($dumpedMap);
|
||||
$dumpedValues .= "{$id} => {$value},\n";
|
||||
}
|
||||
|
||||
$dump .= var_export($key, true)." => {$id},\n";
|
||||
}
|
||||
|
||||
$dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
|
||||
|
||||
$tmpFile = uniqid($this->file, true);
|
||||
|
||||
file_put_contents($tmpFile, $dump);
|
||||
@chmod($tmpFile, 0666 & ~umask());
|
||||
unset($serialized, $value, $dump);
|
||||
|
||||
@rename($tmpFile, $this->file);
|
||||
unset(self::$valuesCache[$this->file]);
|
||||
|
||||
$this->initialize();
|
||||
|
||||
return $preload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the cache file.
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if (isset(self::$valuesCache[$this->file])) {
|
||||
$values = self::$valuesCache[$this->file];
|
||||
} elseif (!is_file($this->file)) {
|
||||
$this->keys = $this->values = [];
|
||||
|
||||
return;
|
||||
} else {
|
||||
$values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
|
||||
}
|
||||
|
||||
if (2 !== \count($values) || !isset($values[0], $values[1])) {
|
||||
$this->keys = $this->values = [];
|
||||
} else {
|
||||
[$this->keys, $this->values] = $values;
|
||||
}
|
||||
}
|
||||
|
||||
private function generateItems(array $keys): \Generator
|
||||
{
|
||||
$f = self::$createCacheItem;
|
||||
$fallbackKeys = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (isset($this->keys[$key])) {
|
||||
$value = $this->values[$this->keys[$key]];
|
||||
|
||||
if ('N;' === $value) {
|
||||
yield $key => $f($key, null, true);
|
||||
} elseif ($value instanceof \Closure) {
|
||||
try {
|
||||
yield $key => $f($key, $value(), true);
|
||||
} catch (\Throwable $e) {
|
||||
yield $key => $f($key, null, false);
|
||||
}
|
||||
} else {
|
||||
yield $key => $f($key, $value, true);
|
||||
}
|
||||
} else {
|
||||
$fallbackKeys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fallbackKeys) {
|
||||
yield from $this->pool->getItems($fallbackKeys);
|
||||
}
|
||||
}
|
||||
}
|
330
vendor/symfony/cache/Adapter/PhpFilesAdapter.php
vendored
Normal file
330
vendor/symfony/cache/Adapter/PhpFilesAdapter.php
vendored
Normal file
@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\Traits\FilesystemCommonTrait;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
|
||||
/**
|
||||
* @author Piotr Stankowski <git@trakos.pl>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author Rob Frawley 2nd <rmf@src.run>
|
||||
*/
|
||||
class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
|
||||
{
|
||||
use FilesystemCommonTrait {
|
||||
doClear as private doCommonClear;
|
||||
doDelete as private doCommonDelete;
|
||||
}
|
||||
|
||||
private $includeHandler;
|
||||
private $appendOnly;
|
||||
private $values = [];
|
||||
private $files = [];
|
||||
|
||||
private static $startTime;
|
||||
private static $valuesCache = [];
|
||||
|
||||
/**
|
||||
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
|
||||
* Doing so is encouraged because it fits perfectly OPcache's memory model.
|
||||
*
|
||||
* @throws CacheException if OPcache is not enabled
|
||||
*/
|
||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
|
||||
{
|
||||
$this->appendOnly = $appendOnly;
|
||||
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->init($namespace, $directory);
|
||||
$this->includeHandler = static function ($type, $msg, $file, $line) {
|
||||
throw new \ErrorException($msg, 0, $type, $file, $line);
|
||||
};
|
||||
}
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
|
||||
|
||||
return \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$time = time();
|
||||
$pruned = true;
|
||||
$getExpiry = true;
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
foreach ($this->scanHashDir($this->directory) as $file) {
|
||||
try {
|
||||
if (\is_array($expiresAt = include $file)) {
|
||||
$expiresAt = $expiresAt[0];
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
$expiresAt = $time;
|
||||
}
|
||||
|
||||
if ($time >= $expiresAt) {
|
||||
$pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return $pruned;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
if ($this->appendOnly) {
|
||||
$now = 0;
|
||||
$missingIds = [];
|
||||
} else {
|
||||
$now = time();
|
||||
$missingIds = $ids;
|
||||
$ids = [];
|
||||
}
|
||||
$values = [];
|
||||
|
||||
begin:
|
||||
$getExpiry = false;
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if (null === $value = $this->values[$id] ?? null) {
|
||||
$missingIds[] = $id;
|
||||
} elseif ('N;' === $value) {
|
||||
$values[$id] = null;
|
||||
} elseif (!\is_object($value)) {
|
||||
$values[$id] = $value;
|
||||
} elseif (!$value instanceof LazyValue) {
|
||||
$values[$id] = $value();
|
||||
} elseif (false === $values[$id] = include $value->file) {
|
||||
unset($values[$id], $this->values[$id]);
|
||||
$missingIds[] = $id;
|
||||
}
|
||||
if (!$this->appendOnly) {
|
||||
unset($this->values[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$missingIds) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
$getExpiry = true;
|
||||
|
||||
foreach ($missingIds as $k => $id) {
|
||||
try {
|
||||
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
|
||||
|
||||
if (isset(self::$valuesCache[$file])) {
|
||||
[$expiresAt, $this->values[$id]] = self::$valuesCache[$file];
|
||||
} elseif (\is_array($expiresAt = include $file)) {
|
||||
if ($this->appendOnly) {
|
||||
self::$valuesCache[$file] = $expiresAt;
|
||||
}
|
||||
|
||||
[$expiresAt, $this->values[$id]] = $expiresAt;
|
||||
} elseif ($now < $expiresAt) {
|
||||
$this->values[$id] = new LazyValue($file);
|
||||
}
|
||||
|
||||
if ($now >= $expiresAt) {
|
||||
unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
unset($missingIds[$k]);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$ids = $missingIds;
|
||||
$missingIds = [];
|
||||
goto begin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id)
|
||||
{
|
||||
if ($this->appendOnly && isset($this->values[$id])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
|
||||
$getExpiry = true;
|
||||
|
||||
if (isset(self::$valuesCache[$file])) {
|
||||
[$expiresAt, $value] = self::$valuesCache[$file];
|
||||
} elseif (\is_array($expiresAt = include $file)) {
|
||||
if ($this->appendOnly) {
|
||||
self::$valuesCache[$file] = $expiresAt;
|
||||
}
|
||||
|
||||
[$expiresAt, $value] = $expiresAt;
|
||||
} elseif ($this->appendOnly) {
|
||||
$value = new LazyValue($file);
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
return false;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
if ($this->appendOnly) {
|
||||
$now = 0;
|
||||
$this->values[$id] = $value;
|
||||
} else {
|
||||
$now = time();
|
||||
}
|
||||
|
||||
return $now < $expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
$ok = true;
|
||||
$expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
|
||||
$allowCompile = self::isSupported();
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
unset($this->values[$key]);
|
||||
$isStaticValue = true;
|
||||
if (null === $value) {
|
||||
$value = "'N;'";
|
||||
} elseif (\is_object($value) || \is_array($value)) {
|
||||
try {
|
||||
$value = VarExporter::export($value, $isStaticValue);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e);
|
||||
}
|
||||
} elseif (\is_string($value)) {
|
||||
// Wrap "N;" in a closure to not confuse it with an encoded `null`
|
||||
if ('N;' === $value) {
|
||||
$isStaticValue = false;
|
||||
}
|
||||
$value = var_export($value, true);
|
||||
} elseif (!\is_scalar($value)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)));
|
||||
} else {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
|
||||
$encodedKey = rawurlencode($key);
|
||||
|
||||
if ($isStaticValue) {
|
||||
$value = "return [{$expiry}, {$value}];";
|
||||
} elseif ($this->appendOnly) {
|
||||
$value = "return [{$expiry}, static function () { return {$value}; }];";
|
||||
} else {
|
||||
// We cannot use a closure here because of https://bugs.php.net/76982
|
||||
$value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
|
||||
$value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};";
|
||||
}
|
||||
|
||||
$file = $this->files[$key] = $this->getFile($key, true);
|
||||
// Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
|
||||
$ok = $this->write($file, "<?php //{$encodedKey}\n\n{$value}\n", self::$startTime - 10) && $ok;
|
||||
|
||||
if ($allowCompile) {
|
||||
@opcache_invalidate($file, true);
|
||||
@opcache_compile_file($file);
|
||||
}
|
||||
unset(self::$valuesCache[$file]);
|
||||
}
|
||||
|
||||
if (!$ok && !is_writable($this->directory)) {
|
||||
throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory));
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace)
|
||||
{
|
||||
$this->values = [];
|
||||
|
||||
return $this->doCommonClear($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
unset($this->values[$id]);
|
||||
}
|
||||
|
||||
return $this->doCommonDelete($ids);
|
||||
}
|
||||
|
||||
protected function doUnlink(string $file)
|
||||
{
|
||||
unset(self::$valuesCache[$file]);
|
||||
|
||||
if (self::isSupported()) {
|
||||
@opcache_invalidate($file, true);
|
||||
}
|
||||
|
||||
return @unlink($file);
|
||||
}
|
||||
|
||||
private function getFileKey(string $file): string
|
||||
{
|
||||
if (!$h = @fopen($file, 'r')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$encodedKey = substr(fgets($h), 8);
|
||||
fclose($h);
|
||||
|
||||
return rawurldecode(rtrim($encodedKey));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class LazyValue
|
||||
{
|
||||
public $file;
|
||||
|
||||
public function __construct(string $file)
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
}
|
268
vendor/symfony/cache/Adapter/ProxyAdapter.php
vendored
Normal file
268
vendor/symfony/cache/Adapter/ProxyAdapter.php
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\ContractsTrait;
|
||||
use Symfony\Component\Cache\Traits\ProxyTrait;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
|
||||
{
|
||||
use ContractsTrait;
|
||||
use ProxyTrait;
|
||||
|
||||
private $namespace = '';
|
||||
private $namespaceLen;
|
||||
private $poolHash;
|
||||
private $defaultLifetime;
|
||||
|
||||
private static $createCacheItem;
|
||||
private static $setInnerItem;
|
||||
|
||||
public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
$this->poolHash = $poolHash = spl_object_hash($pool);
|
||||
if ('' !== $namespace) {
|
||||
\assert('' !== CacheItem::validateKey($namespace));
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
$this->namespaceLen = \strlen($namespace);
|
||||
$this->defaultLifetime = $defaultLifetime;
|
||||
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
|
||||
static function ($key, $innerItem, $poolHash) {
|
||||
$item = new CacheItem();
|
||||
$item->key = $key;
|
||||
|
||||
if (null === $innerItem) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$item->value = $v = $innerItem->get();
|
||||
$item->isHit = $innerItem->isHit();
|
||||
$item->innerItem = $innerItem;
|
||||
$item->poolHash = $poolHash;
|
||||
|
||||
// Detect wrapped values that encode for their expiry and creation duration
|
||||
// For compactness, these values are packed in the key of an array using
|
||||
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
|
||||
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
|
||||
$item->value = $v[$k];
|
||||
$v = unpack('Ve/Nc', substr($k, 1, -1));
|
||||
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
|
||||
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
|
||||
} elseif ($innerItem instanceof CacheItem) {
|
||||
$item->metadata = $innerItem->metadata;
|
||||
}
|
||||
$innerItem->set(null);
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
self::$setInnerItem ?? self::$setInnerItem = \Closure::bind(
|
||||
/**
|
||||
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
|
||||
*/
|
||||
static function (CacheItemInterface $innerItem, array $item) {
|
||||
// Tags are stored separately, no need to account for them when considering this item's newly set metadata
|
||||
if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
|
||||
unset($metadata[CacheItem::METADATA_TAGS]);
|
||||
}
|
||||
if ($metadata) {
|
||||
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
|
||||
$item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
|
||||
}
|
||||
$innerItem->set($item["\0*\0value"]);
|
||||
$innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $item["\0*\0expiry"])) : null);
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
|
||||
{
|
||||
if (!$this->pool instanceof CacheInterface) {
|
||||
return $this->doGet($this, $key, $callback, $beta, $metadata);
|
||||
}
|
||||
|
||||
return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
|
||||
$item = (self::$createCacheItem)($key, $innerItem, $this->poolHash);
|
||||
$item->set($value = $callback($item, $save));
|
||||
(self::$setInnerItem)($innerItem, (array) $item);
|
||||
|
||||
return $value;
|
||||
}, $beta, $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
$item = $this->pool->getItem($this->getId($key));
|
||||
|
||||
return (self::$createCacheItem)($key, $item, $this->poolHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
if ($this->namespaceLen) {
|
||||
foreach ($keys as $i => $key) {
|
||||
$keys[$i] = $this->getId($key);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->generateItems($this->pool->getItems($keys));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
return $this->pool->hasItem($this->getId($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
if ($this->pool instanceof AdapterInterface) {
|
||||
return $this->pool->clear($this->namespace.$prefix);
|
||||
}
|
||||
|
||||
return $this->pool->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
return $this->pool->deleteItem($this->getId($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
if ($this->namespaceLen) {
|
||||
foreach ($keys as $i => $key) {
|
||||
$keys[$i] = $this->getId($key);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->pool->deleteItems($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
return $this->doSave($item, __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
return $this->doSave($item, __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
return $this->pool->commit();
|
||||
}
|
||||
|
||||
private function doSave(CacheItemInterface $item, string $method)
|
||||
{
|
||||
if (!$item instanceof CacheItem) {
|
||||
return false;
|
||||
}
|
||||
$item = (array) $item;
|
||||
if (null === $item["\0*\0expiry"] && 0 < $this->defaultLifetime) {
|
||||
$item["\0*\0expiry"] = microtime(true) + $this->defaultLifetime;
|
||||
}
|
||||
|
||||
if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
|
||||
$innerItem = $item["\0*\0innerItem"];
|
||||
} elseif ($this->pool instanceof AdapterInterface) {
|
||||
// this is an optimization specific for AdapterInterface implementations
|
||||
// so we can save a round-trip to the backend by just creating a new item
|
||||
$innerItem = (self::$createCacheItem)($this->namespace.$item["\0*\0key"], null, $this->poolHash);
|
||||
} else {
|
||||
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
|
||||
}
|
||||
|
||||
(self::$setInnerItem)($innerItem, $item);
|
||||
|
||||
return $this->pool->$method($innerItem);
|
||||
}
|
||||
|
||||
private function generateItems(iterable $items): \Generator
|
||||
{
|
||||
$f = self::$createCacheItem;
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
if ($this->namespaceLen) {
|
||||
$key = substr($key, $this->namespaceLen);
|
||||
}
|
||||
|
||||
yield $key => $f($key, $item, $this->poolHash);
|
||||
}
|
||||
}
|
||||
|
||||
private function getId($key): string
|
||||
{
|
||||
\assert('' !== CacheItem::validateKey($key));
|
||||
|
||||
return $this->namespace.$key;
|
||||
}
|
||||
}
|
86
vendor/symfony/cache/Adapter/Psr16Adapter.php
vendored
Normal file
86
vendor/symfony/cache/Adapter/Psr16Adapter.php
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\ProxyTrait;
|
||||
|
||||
/**
|
||||
* Turns a PSR-16 cache into a PSR-6 one.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
|
||||
{
|
||||
use ProxyTrait;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected const NS_SEPARATOR = '_';
|
||||
|
||||
private $miss;
|
||||
|
||||
public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
|
||||
{
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
|
||||
$this->pool = $pool;
|
||||
$this->miss = new \stdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
|
||||
if ($this->miss !== $value) {
|
||||
yield $key => $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave(string $id)
|
||||
{
|
||||
return $this->pool->has($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear(string $namespace)
|
||||
{
|
||||
return $this->pool->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
return $this->pool->deleteMultiple($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime)
|
||||
{
|
||||
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
|
||||
}
|
||||
}
|
32
vendor/symfony/cache/Adapter/RedisAdapter.php
vendored
Normal file
32
vendor/symfony/cache/Adapter/RedisAdapter.php
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\Traits\RedisClusterProxy;
|
||||
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||
use Symfony\Component\Cache\Traits\RedisTrait;
|
||||
|
||||
class RedisAdapter extends AbstractAdapter
|
||||
{
|
||||
use RedisTrait;
|
||||
|
||||
/**
|
||||
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client
|
||||
* @param string $namespace The default namespace
|
||||
* @param int $defaultLifetime The default lifetime
|
||||
*/
|
||||
public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->init($redis, $namespace, $defaultLifetime, $marshaller);
|
||||
}
|
||||
}
|
325
vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
vendored
Normal file
325
vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Predis\Connection\Aggregate\ClusterInterface;
|
||||
use Predis\Connection\Aggregate\PredisCluster;
|
||||
use Predis\Connection\Aggregate\ReplicationInterface;
|
||||
use Predis\Response\ErrorInterface;
|
||||
use Predis\Response\Status;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Exception\LogicException;
|
||||
use Symfony\Component\Cache\Marshaller\DeflateMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
|
||||
use Symfony\Component\Cache\Traits\RedisClusterProxy;
|
||||
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||
use Symfony\Component\Cache\Traits\RedisTrait;
|
||||
|
||||
/**
|
||||
* Stores tag id <> cache id relationship as a Redis Set.
|
||||
*
|
||||
* Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
|
||||
* if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
|
||||
* relationship survives eviction (cache cleanup when Redis runs out of memory).
|
||||
*
|
||||
* Redis server 2.8+ with any `volatile-*` eviction policy, OR `noeviction` if you're sure memory will NEVER fill up
|
||||
*
|
||||
* Design limitations:
|
||||
* - Max 4 billion cache keys per cache tag as limited by Redis Set datatype.
|
||||
* E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also.
|
||||
*
|
||||
* @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
|
||||
* @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author André Rømcke <andre.romcke+symfony@gmail.com>
|
||||
*/
|
||||
class RedisTagAwareAdapter extends AbstractTagAwareAdapter
|
||||
{
|
||||
use RedisTrait;
|
||||
|
||||
/**
|
||||
* On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
|
||||
* preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
|
||||
*/
|
||||
private const DEFAULT_CACHE_TTL = 8640000;
|
||||
|
||||
/**
|
||||
* @var string|null detected eviction policy used on Redis server
|
||||
*/
|
||||
private $redisEvictionPolicy;
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client
|
||||
* @param string $namespace The default namespace
|
||||
* @param int $defaultLifetime The default lifetime
|
||||
*/
|
||||
public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection())));
|
||||
}
|
||||
|
||||
if (\defined('Redis::OPT_COMPRESSION') && ($redis instanceof \Redis || $redis instanceof \RedisArray || $redis instanceof \RedisCluster)) {
|
||||
$compression = $redis->getOption(\Redis::OPT_COMPRESSION);
|
||||
|
||||
foreach (\is_array($compression) ? $compression : [$compression] as $c) {
|
||||
if (\Redis::COMPRESSION_NONE !== $c) {
|
||||
throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller));
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, int $lifetime, array $addTagData = [], array $delTagData = []): array
|
||||
{
|
||||
$eviction = $this->getRedisEvictionPolicy();
|
||||
if ('noeviction' !== $eviction && !str_starts_with($eviction, 'volatile-')) {
|
||||
throw new LogicException(sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction));
|
||||
}
|
||||
|
||||
// serialize values
|
||||
if (!$serialized = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
// While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
|
||||
$results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) {
|
||||
// Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
|
||||
foreach ($serialized as $id => $value) {
|
||||
yield 'setEx' => [
|
||||
$id,
|
||||
0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime,
|
||||
$value,
|
||||
];
|
||||
}
|
||||
|
||||
// Add and Remove Tags
|
||||
foreach ($addTagData as $tagId => $ids) {
|
||||
if (!$failed || $ids = array_diff($ids, $failed)) {
|
||||
yield 'sAdd' => array_merge([$tagId], $ids);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($delTagData as $tagId => $ids) {
|
||||
if (!$failed || $ids = array_diff($ids, $failed)) {
|
||||
yield 'sRem' => array_merge([$tagId], $ids);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($results as $id => $result) {
|
||||
// Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not
|
||||
if (is_numeric($result)) {
|
||||
continue;
|
||||
}
|
||||
// setEx results
|
||||
if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
|
||||
$failed[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteYieldTags(array $ids): iterable
|
||||
{
|
||||
$lua = <<<'EOLUA'
|
||||
local v = redis.call('GET', KEYS[1])
|
||||
local e = redis.pcall('UNLINK', KEYS[1])
|
||||
|
||||
if type(e) ~= 'number' then
|
||||
redis.call('DEL', KEYS[1])
|
||||
end
|
||||
|
||||
if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then
|
||||
return ''
|
||||
end
|
||||
|
||||
return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536)
|
||||
EOLUA;
|
||||
|
||||
$results = $this->pipeline(function () use ($ids, $lua) {
|
||||
foreach ($ids as $id) {
|
||||
yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id] : [$lua, [$id], 1];
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($results as $id => $result) {
|
||||
if ($result instanceof \RedisException || $result instanceof ErrorInterface) {
|
||||
CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result);
|
||||
} catch (\Exception $e) {
|
||||
yield $id => [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteTagRelations(array $tagData): bool
|
||||
{
|
||||
$results = $this->pipeline(static function () use ($tagData) {
|
||||
foreach ($tagData as $tagId => $idList) {
|
||||
array_unshift($idList, $tagId);
|
||||
yield 'sRem' => $idList;
|
||||
}
|
||||
});
|
||||
foreach ($results as $result) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doInvalidate(array $tagIds): bool
|
||||
{
|
||||
// This script scans the set of items linked to tag: it empties the set
|
||||
// and removes the linked items. When the set is still not empty after
|
||||
// the scan, it means we're in cluster mode and that the linked items
|
||||
// are on other nodes: we move the links to a temporary set and we
|
||||
// garbage collect that set from the client side.
|
||||
|
||||
$lua = <<<'EOLUA'
|
||||
redis.replicate_commands()
|
||||
|
||||
local cursor = '0'
|
||||
local id = KEYS[1]
|
||||
repeat
|
||||
local result = redis.call('SSCAN', id, cursor, 'COUNT', 5000);
|
||||
cursor = result[1];
|
||||
local rems = {}
|
||||
|
||||
for _, v in ipairs(result[2]) do
|
||||
local ok, _ = pcall(redis.call, 'DEL', ARGV[1]..v)
|
||||
if ok then
|
||||
table.insert(rems, v)
|
||||
end
|
||||
end
|
||||
if 0 < #rems then
|
||||
redis.call('SREM', id, unpack(rems))
|
||||
end
|
||||
until '0' == cursor;
|
||||
|
||||
redis.call('SUNIONSTORE', '{'..id..'}'..id, id)
|
||||
redis.call('DEL', id)
|
||||
|
||||
return redis.call('SSCAN', '{'..id..'}'..id, '0', 'COUNT', 5000)
|
||||
EOLUA;
|
||||
|
||||
$results = $this->pipeline(function () use ($tagIds, $lua) {
|
||||
if ($this->redis instanceof \Predis\ClientInterface) {
|
||||
$prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : '';
|
||||
} elseif (\is_array($prefix = $this->redis->getOption(\Redis::OPT_PREFIX) ?? '')) {
|
||||
$prefix = current($prefix);
|
||||
}
|
||||
|
||||
foreach ($tagIds as $id) {
|
||||
yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id, $prefix] : [$lua, [$id, $prefix], 1];
|
||||
}
|
||||
});
|
||||
|
||||
$lua = <<<'EOLUA'
|
||||
redis.replicate_commands()
|
||||
|
||||
local id = KEYS[1]
|
||||
local cursor = table.remove(ARGV)
|
||||
redis.call('SREM', '{'..id..'}'..id, unpack(ARGV))
|
||||
|
||||
return redis.call('SSCAN', '{'..id..'}'..id, cursor, 'COUNT', 5000)
|
||||
EOLUA;
|
||||
|
||||
$success = true;
|
||||
foreach ($results as $id => $values) {
|
||||
if ($values instanceof \RedisException || $values instanceof ErrorInterface) {
|
||||
CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]);
|
||||
$success = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
[$cursor, $ids] = $values;
|
||||
|
||||
while ($ids || '0' !== $cursor) {
|
||||
$this->doDelete($ids);
|
||||
|
||||
$evalArgs = [$id, $cursor];
|
||||
array_splice($evalArgs, 1, 0, $ids);
|
||||
|
||||
if ($this->redis instanceof \Predis\ClientInterface) {
|
||||
array_unshift($evalArgs, $lua, 1);
|
||||
} else {
|
||||
$evalArgs = [$lua, $evalArgs, 1];
|
||||
}
|
||||
|
||||
$results = $this->pipeline(function () use ($evalArgs) {
|
||||
yield 'eval' => $evalArgs;
|
||||
});
|
||||
|
||||
foreach ($results as [$cursor, $ids]) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
private function getRedisEvictionPolicy(): string
|
||||
{
|
||||
if (null !== $this->redisEvictionPolicy) {
|
||||
return $this->redisEvictionPolicy;
|
||||
}
|
||||
|
||||
$hosts = $this->getHosts();
|
||||
$host = reset($hosts);
|
||||
if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) {
|
||||
// Predis supports info command only on the master in replication environments
|
||||
$hosts = [$host->getClientFor('master')];
|
||||
}
|
||||
|
||||
foreach ($hosts as $host) {
|
||||
$info = $host->info('Memory');
|
||||
|
||||
if ($info instanceof ErrorInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$info = $info['Memory'] ?? $info;
|
||||
|
||||
return $this->redisEvictionPolicy = $info['maxmemory_policy'];
|
||||
}
|
||||
|
||||
return $this->redisEvictionPolicy = '';
|
||||
}
|
||||
}
|
428
vendor/symfony/cache/Adapter/TagAwareAdapter.php
vendored
Normal file
428
vendor/symfony/cache/Adapter/TagAwareAdapter.php
vendored
Normal file
@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\ContractsTrait;
|
||||
use Symfony\Component\Cache\Traits\ProxyTrait;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface
|
||||
{
|
||||
use ContractsTrait;
|
||||
use LoggerAwareTrait;
|
||||
use ProxyTrait;
|
||||
|
||||
public const TAGS_PREFIX = "\0tags\0";
|
||||
|
||||
private $deferred = [];
|
||||
private $tags;
|
||||
private $knownTagVersions = [];
|
||||
private $knownTagVersionsTtl;
|
||||
|
||||
private static $createCacheItem;
|
||||
private static $setCacheItemTags;
|
||||
private static $getTagsByKey;
|
||||
private static $saveTags;
|
||||
|
||||
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15)
|
||||
{
|
||||
$this->pool = $itemsPool;
|
||||
$this->tags = $tagsPool ?: $itemsPool;
|
||||
$this->knownTagVersionsTtl = $knownTagVersionsTtl;
|
||||
self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
|
||||
static function ($key, $value, CacheItem $protoItem) {
|
||||
$item = new CacheItem();
|
||||
$item->key = $key;
|
||||
$item->value = $value;
|
||||
$item->expiry = $protoItem->expiry;
|
||||
$item->poolHash = $protoItem->poolHash;
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
self::$setCacheItemTags ?? self::$setCacheItemTags = \Closure::bind(
|
||||
static function (CacheItem $item, $key, array &$itemTags) {
|
||||
$item->isTaggable = true;
|
||||
if (!$item->isHit) {
|
||||
return $item;
|
||||
}
|
||||
if (isset($itemTags[$key])) {
|
||||
foreach ($itemTags[$key] as $tag => $version) {
|
||||
$item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
|
||||
}
|
||||
unset($itemTags[$key]);
|
||||
} else {
|
||||
$item->value = null;
|
||||
$item->isHit = false;
|
||||
}
|
||||
|
||||
return $item;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
self::$getTagsByKey ?? self::$getTagsByKey = \Closure::bind(
|
||||
static function ($deferred) {
|
||||
$tagsByKey = [];
|
||||
foreach ($deferred as $key => $item) {
|
||||
$tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
|
||||
$item->metadata = $item->newMetadata;
|
||||
}
|
||||
|
||||
return $tagsByKey;
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
self::$saveTags ?? self::$saveTags = \Closure::bind(
|
||||
static function (AdapterInterface $tagsAdapter, array $tags) {
|
||||
ksort($tags);
|
||||
|
||||
foreach ($tags as $v) {
|
||||
$v->expiry = 0;
|
||||
$tagsAdapter->saveDeferred($v);
|
||||
}
|
||||
|
||||
return $tagsAdapter->commit();
|
||||
},
|
||||
null,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function invalidateTags(array $tags)
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($tags as $tag) {
|
||||
\assert('' !== CacheItem::validateKey($tag));
|
||||
unset($this->knownTagVersions[$tag]);
|
||||
$ids[] = $tag.static::TAGS_PREFIX;
|
||||
}
|
||||
|
||||
return !$tags || $this->tags->deleteItems($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
if (\is_string($key) && isset($this->deferred[$key])) {
|
||||
$this->commit();
|
||||
}
|
||||
|
||||
if (!$this->pool->hasItem($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key);
|
||||
|
||||
if (!$itemTags->isHit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$itemTags = $itemTags->get()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
|
||||
if ($itemTags[$tag] !== $version) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
foreach ($this->getItems([$key]) as $item) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
$tagKeys = [];
|
||||
$commit = false;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if ('' !== $key && \is_string($key)) {
|
||||
$commit = $commit || isset($this->deferred[$key]);
|
||||
$key = static::TAGS_PREFIX.$key;
|
||||
$tagKeys[$key] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
}
|
||||
|
||||
try {
|
||||
$items = $this->pool->getItems($tagKeys + $keys);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->pool->getItems($keys); // Should throw an exception
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->generateItems($items, $tagKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
if ('' !== $prefix) {
|
||||
foreach ($this->deferred as $key => $item) {
|
||||
if (str_starts_with($key, $prefix)) {
|
||||
unset($this->deferred[$key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->deferred = [];
|
||||
}
|
||||
|
||||
if ($this->pool instanceof AdapterInterface) {
|
||||
return $this->pool->clear($prefix);
|
||||
}
|
||||
|
||||
return $this->pool->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
return $this->deleteItems([$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if ('' !== $key && \is_string($key)) {
|
||||
$keys[] = static::TAGS_PREFIX.$key;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->pool->deleteItems($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
if (!$item instanceof CacheItem) {
|
||||
return false;
|
||||
}
|
||||
$this->deferred[$item->getKey()] = $item;
|
||||
|
||||
return $this->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
if (!$item instanceof CacheItem) {
|
||||
return false;
|
||||
}
|
||||
$this->deferred[$item->getKey()] = $item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
if (!$this->deferred) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ok = true;
|
||||
foreach ($this->deferred as $key => $item) {
|
||||
if (!$this->pool->saveDeferred($item)) {
|
||||
unset($this->deferred[$key]);
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
$items = $this->deferred;
|
||||
$tagsByKey = (self::$getTagsByKey)($items);
|
||||
$this->deferred = [];
|
||||
|
||||
$tagVersions = $this->getTagVersions($tagsByKey);
|
||||
$f = self::$createCacheItem;
|
||||
|
||||
foreach ($tagsByKey as $key => $tags) {
|
||||
$this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
|
||||
}
|
||||
|
||||
return $this->pool->commit() && $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->commit();
|
||||
}
|
||||
|
||||
private function generateItems(iterable $items, array $tagKeys): \Generator
|
||||
{
|
||||
$bufferedItems = $itemTags = [];
|
||||
$f = self::$setCacheItemTags;
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
if (!$tagKeys) {
|
||||
yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
|
||||
continue;
|
||||
}
|
||||
if (!isset($tagKeys[$key])) {
|
||||
$bufferedItems[$key] = $item;
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($tagKeys[$key]);
|
||||
|
||||
if ($item->isHit()) {
|
||||
$itemTags[$key] = $item->get() ?: [];
|
||||
}
|
||||
|
||||
if (!$tagKeys) {
|
||||
$tagVersions = $this->getTagVersions($itemTags);
|
||||
|
||||
foreach ($itemTags as $key => $tags) {
|
||||
foreach ($tags as $tag => $version) {
|
||||
if ($tagVersions[$tag] !== $version) {
|
||||
unset($itemTags[$key]);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
$tagVersions = $tagKeys = null;
|
||||
|
||||
foreach ($bufferedItems as $key => $item) {
|
||||
yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
|
||||
}
|
||||
$bufferedItems = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getTagVersions(array $tagsByKey)
|
||||
{
|
||||
$tagVersions = [];
|
||||
$fetchTagVersions = false;
|
||||
|
||||
foreach ($tagsByKey as $tags) {
|
||||
$tagVersions += $tags;
|
||||
|
||||
foreach ($tags as $tag => $version) {
|
||||
if ($tagVersions[$tag] !== $version) {
|
||||
unset($this->knownTagVersions[$tag]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$tagVersions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$now = microtime(true);
|
||||
$tags = [];
|
||||
foreach ($tagVersions as $tag => $version) {
|
||||
$tags[$tag.static::TAGS_PREFIX] = $tag;
|
||||
if ($fetchTagVersions || ($this->knownTagVersions[$tag][1] ?? null) !== $version || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
|
||||
// reuse previously fetched tag versions up to the ttl
|
||||
$fetchTagVersions = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$fetchTagVersions) {
|
||||
return $tagVersions;
|
||||
}
|
||||
|
||||
$newTags = [];
|
||||
$newVersion = null;
|
||||
foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
|
||||
if (!$version->isHit()) {
|
||||
$newTags[$tag] = $version->set($newVersion ?? $newVersion = random_int(\PHP_INT_MIN, \PHP_INT_MAX));
|
||||
}
|
||||
$tagVersions[$tag = $tags[$tag]] = $version->get();
|
||||
$this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]];
|
||||
}
|
||||
|
||||
if ($newTags) {
|
||||
(self::$saveTags)($this->tags, $newTags);
|
||||
}
|
||||
|
||||
return $tagVersions;
|
||||
}
|
||||
}
|
33
vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php
vendored
Normal file
33
vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Interface for invalidating cached items using tags.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface TagAwareAdapterInterface extends AdapterInterface
|
||||
{
|
||||
/**
|
||||
* Invalidates cached items using tags.
|
||||
*
|
||||
* @param string[] $tags An array of tags to invalidate
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws InvalidArgumentException When $tags is not valid
|
||||
*/
|
||||
public function invalidateTags(array $tags);
|
||||
}
|
295
vendor/symfony/cache/Adapter/TraceableAdapter.php
vendored
Normal file
295
vendor/symfony/cache/Adapter/TraceableAdapter.php
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* An adapter that collects data about all cache calls.
|
||||
*
|
||||
* @author Aaron Scherer <aequasi@gmail.com>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
|
||||
{
|
||||
protected $pool;
|
||||
private $calls = [];
|
||||
|
||||
public function __construct(AdapterInterface $pool)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
|
||||
{
|
||||
if (!$this->pool instanceof CacheInterface) {
|
||||
throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class));
|
||||
}
|
||||
|
||||
$isHit = true;
|
||||
$callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) {
|
||||
$isHit = $item->isHit();
|
||||
|
||||
return $callback($item, $save);
|
||||
};
|
||||
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
$value = $this->pool->get($key, $callback, $beta, $metadata);
|
||||
$event->result[$key] = get_debug_type($value);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
if ($isHit) {
|
||||
++$event->hits;
|
||||
} else {
|
||||
++$event->misses;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
$item = $this->pool->getItem($key);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
if ($event->result[$key] = $item->isHit()) {
|
||||
++$event->hits;
|
||||
} else {
|
||||
++$event->misses;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$key] = $this->pool->hasItem($key);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$key] = $this->pool->deleteItem($key);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save(CacheItemInterface $item)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$item->getKey()] = $this->pool->save($item);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$item->getKey()] = $this->pool->saveDeferred($item);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(array $keys = [])
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
$result = $this->pool->getItems($keys);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
$f = function () use ($result, $event) {
|
||||
$event->result = [];
|
||||
foreach ($result as $key => $item) {
|
||||
if ($event->result[$key] = $item->isHit()) {
|
||||
++$event->hits;
|
||||
} else {
|
||||
++$event->misses;
|
||||
}
|
||||
yield $key => $item;
|
||||
}
|
||||
};
|
||||
|
||||
return $f();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(string $prefix = '')
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
if ($this->pool instanceof AdapterInterface) {
|
||||
return $event->result = $this->pool->clear($prefix);
|
||||
}
|
||||
|
||||
return $event->result = $this->pool->clear();
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
$event->result['keys'] = $keys;
|
||||
try {
|
||||
return $event->result['result'] = $this->pool->deleteItems($keys);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result = $this->pool->commit();
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
if (!$this->pool instanceof PruneableInterface) {
|
||||
return false;
|
||||
}
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result = $this->pool->prune();
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
if ($this->pool instanceof ResetInterface) {
|
||||
$this->pool->reset();
|
||||
}
|
||||
|
||||
$this->clearCalls();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$key] = $this->pool->deleteItem($key);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCalls()
|
||||
{
|
||||
return $this->calls;
|
||||
}
|
||||
|
||||
public function clearCalls()
|
||||
{
|
||||
$this->calls = [];
|
||||
}
|
||||
|
||||
protected function start(string $name)
|
||||
{
|
||||
$this->calls[] = $event = new TraceableAdapterEvent();
|
||||
$event->name = $name;
|
||||
$event->start = microtime(true);
|
||||
|
||||
return $event;
|
||||
}
|
||||
}
|
||||
|
||||
class TraceableAdapterEvent
|
||||
{
|
||||
public $name;
|
||||
public $start;
|
||||
public $end;
|
||||
public $result;
|
||||
public $hits = 0;
|
||||
public $misses = 0;
|
||||
}
|
38
vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php
vendored
Normal file
38
vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
|
||||
{
|
||||
public function __construct(TagAwareAdapterInterface $pool)
|
||||
{
|
||||
parent::__construct($pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function invalidateTags(array $tags)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result = $this->pool->invalidateTags($tags);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
}
|
108
vendor/symfony/cache/CHANGELOG.md
vendored
Normal file
108
vendor/symfony/cache/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package
|
||||
* Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL
|
||||
* Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* added support for connecting to Redis Sentinel clusters when using the Redis PHP extension
|
||||
* add support for a custom serializer to the `ApcuAdapter` class
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* added integration with Messenger to allow computing cached values in a worker
|
||||
* allow ISO 8601 time intervals to specify default lifetime
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* added max-items + LRU + max-lifetime capabilities to `ArrayCache`
|
||||
* added `CouchbaseBucketAdapter`
|
||||
* added context `cache-adapter` to log messages
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* removed all PSR-16 implementations in the `Simple` namespace
|
||||
* removed `SimpleCacheAdapter`
|
||||
* removed `AbstractAdapter::unserialize()`
|
||||
* removed `CacheItem::getPreviousTags()`
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* added support for connecting to Redis Sentinel clusters
|
||||
* added argument `$prefix` to `AdapterInterface::clear()`
|
||||
* improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag
|
||||
* added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter`
|
||||
* added `DeflateMarshaller` to compress serialized values
|
||||
* removed support for phpredis 4 `compression`
|
||||
* [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead
|
||||
* Marked the `CacheDataCollector` class as `@final`.
|
||||
* added `SodiumMarshaller` to encrypt/decrypt values using libsodium
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
|
||||
* deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
|
||||
* deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* added support for connecting to Redis clusters via DSN
|
||||
* added support for configuring multiple Memcached servers via DSN
|
||||
* added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
|
||||
* implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
|
||||
* added sub-second expiry accuracy for backends that support it
|
||||
* added support for phpredis 4 `compression` and `tcp_keepalive` options
|
||||
* added automatic table creation when using Doctrine DBAL with PDO-based backends
|
||||
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
|
||||
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
|
||||
* deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
|
||||
* added `CacheCollectorPass` (originally in `FrameworkBundle`)
|
||||
* added `CachePoolClearerPass` (originally in `FrameworkBundle`)
|
||||
* added `CachePoolPass` (originally in `FrameworkBundle`)
|
||||
* added `CachePoolPrunerPass` (originally in `FrameworkBundle`)
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* added using options from Memcached DSN
|
||||
* added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning
|
||||
* added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait
|
||||
* now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and
|
||||
ChainCache implement PruneableInterface and support manual stale cache pruning
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
|
||||
* added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
|
||||
* added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
|
||||
* added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)
|
||||
* added TraceableAdapter (PSR-6) and TraceableCache (PSR-16)
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* added TagAwareAdapter for tags-based invalidation
|
||||
* added PdoAdapter with PDO and Doctrine DBAL support
|
||||
* added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only)
|
||||
* added NullAdapter
|
||||
|
||||
3.1.0
|
||||
-----
|
||||
|
||||
* added the component with strict PSR-6 implementations
|
||||
* added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter
|
||||
* added AbstractAdapter, ChainAdapter and ProxyAdapter
|
||||
* added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache
|
192
vendor/symfony/cache/CacheItem.php
vendored
Normal file
192
vendor/symfony/cache/CacheItem.php
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Exception\LogicException;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class CacheItem implements ItemInterface
|
||||
{
|
||||
private const METADATA_EXPIRY_OFFSET = 1527506807;
|
||||
|
||||
protected $key;
|
||||
protected $value;
|
||||
protected $isHit = false;
|
||||
protected $expiry;
|
||||
protected $metadata = [];
|
||||
protected $newMetadata = [];
|
||||
protected $innerItem;
|
||||
protected $poolHash;
|
||||
protected $isTaggable = false;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isHit(): bool
|
||||
{
|
||||
return $this->isHit;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set($value): self
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function expiresAt($expiration): self
|
||||
{
|
||||
if (null === $expiration) {
|
||||
$this->expiry = null;
|
||||
} elseif ($expiration instanceof \DateTimeInterface) {
|
||||
$this->expiry = (float) $expiration->format('U.u');
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', get_debug_type($expiration)));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function expiresAfter($time): self
|
||||
{
|
||||
if (null === $time) {
|
||||
$this->expiry = null;
|
||||
} elseif ($time instanceof \DateInterval) {
|
||||
$this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
|
||||
} elseif (\is_int($time)) {
|
||||
$this->expiry = $time + microtime(true);
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', get_debug_type($time)));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function tag($tags): ItemInterface
|
||||
{
|
||||
if (!$this->isTaggable) {
|
||||
throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key));
|
||||
}
|
||||
if (!is_iterable($tags)) {
|
||||
$tags = [$tags];
|
||||
}
|
||||
foreach ($tags as $tag) {
|
||||
if (!\is_string($tag) && !(\is_object($tag) && method_exists($tag, '__toString'))) {
|
||||
throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
|
||||
}
|
||||
$tag = (string) $tag;
|
||||
if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) {
|
||||
continue;
|
||||
}
|
||||
if ('' === $tag) {
|
||||
throw new InvalidArgumentException('Cache tag length must be greater than zero.');
|
||||
}
|
||||
if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS));
|
||||
}
|
||||
$this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetadata(): array
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a cache key according to PSR-6.
|
||||
*
|
||||
* @param mixed $key The key to validate
|
||||
*
|
||||
* @throws InvalidArgumentException When $key is not valid
|
||||
*/
|
||||
public static function validateKey($key): string
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
|
||||
}
|
||||
if ('' === $key) {
|
||||
throw new InvalidArgumentException('Cache key length must be greater than zero.');
|
||||
}
|
||||
if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS));
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal logging helper.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function log(?LoggerInterface $logger, string $message, array $context = [])
|
||||
{
|
||||
if ($logger) {
|
||||
$logger->warning($message, $context);
|
||||
} else {
|
||||
$replace = [];
|
||||
foreach ($context as $k => $v) {
|
||||
if (\is_scalar($v)) {
|
||||
$replace['{'.$k.'}'] = $v;
|
||||
}
|
||||
}
|
||||
@trigger_error(strtr($message, $replace), \E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
185
vendor/symfony/cache/DataCollector/CacheDataCollector.php
vendored
Normal file
185
vendor/symfony/cache/DataCollector/CacheDataCollector.php
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\DataCollector;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\TraceableAdapter;
|
||||
use Symfony\Component\Cache\Adapter\TraceableAdapterEvent;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
|
||||
/**
|
||||
* @author Aaron Scherer <aequasi@gmail.com>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class CacheDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
/**
|
||||
* @var TraceableAdapter[]
|
||||
*/
|
||||
private $instances = [];
|
||||
|
||||
public function addInstance(string $name, TraceableAdapter $instance)
|
||||
{
|
||||
$this->instances[$name] = $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null)
|
||||
{
|
||||
$empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []];
|
||||
$this->data = ['instances' => $empty, 'total' => $empty];
|
||||
foreach ($this->instances as $name => $instance) {
|
||||
$this->data['instances']['calls'][$name] = $instance->getCalls();
|
||||
}
|
||||
|
||||
$this->data['instances']['statistics'] = $this->calculateStatistics();
|
||||
$this->data['total']['statistics'] = $this->calculateTotalStatistics();
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->data = [];
|
||||
foreach ($this->instances as $instance) {
|
||||
$instance->clearCalls();
|
||||
}
|
||||
}
|
||||
|
||||
public function lateCollect()
|
||||
{
|
||||
$this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'cache';
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns amount of logged Cache reads: "get" calls.
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return $this->data['instances']['statistics'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns the statistic totals.
|
||||
*/
|
||||
public function getTotals(): array
|
||||
{
|
||||
return $this->data['total']['statistics'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns all logged Cache call objects.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCalls()
|
||||
{
|
||||
return $this->data['instances']['calls'];
|
||||
}
|
||||
|
||||
private function calculateStatistics(): array
|
||||
{
|
||||
$statistics = [];
|
||||
foreach ($this->data['instances']['calls'] as $name => $calls) {
|
||||
$statistics[$name] = [
|
||||
'calls' => 0,
|
||||
'time' => 0,
|
||||
'reads' => 0,
|
||||
'writes' => 0,
|
||||
'deletes' => 0,
|
||||
'hits' => 0,
|
||||
'misses' => 0,
|
||||
];
|
||||
/** @var TraceableAdapterEvent $call */
|
||||
foreach ($calls as $call) {
|
||||
++$statistics[$name]['calls'];
|
||||
$statistics[$name]['time'] += $call->end - $call->start;
|
||||
if ('get' === $call->name) {
|
||||
++$statistics[$name]['reads'];
|
||||
if ($call->hits) {
|
||||
++$statistics[$name]['hits'];
|
||||
} else {
|
||||
++$statistics[$name]['misses'];
|
||||
++$statistics[$name]['writes'];
|
||||
}
|
||||
} elseif ('getItem' === $call->name) {
|
||||
++$statistics[$name]['reads'];
|
||||
if ($call->hits) {
|
||||
++$statistics[$name]['hits'];
|
||||
} else {
|
||||
++$statistics[$name]['misses'];
|
||||
}
|
||||
} elseif ('getItems' === $call->name) {
|
||||
$statistics[$name]['reads'] += $call->hits + $call->misses;
|
||||
$statistics[$name]['hits'] += $call->hits;
|
||||
$statistics[$name]['misses'] += $call->misses;
|
||||
} elseif ('hasItem' === $call->name) {
|
||||
++$statistics[$name]['reads'];
|
||||
if (false === $call->result) {
|
||||
++$statistics[$name]['misses'];
|
||||
} else {
|
||||
++$statistics[$name]['hits'];
|
||||
}
|
||||
} elseif ('save' === $call->name) {
|
||||
++$statistics[$name]['writes'];
|
||||
} elseif ('deleteItem' === $call->name) {
|
||||
++$statistics[$name]['deletes'];
|
||||
}
|
||||
}
|
||||
if ($statistics[$name]['reads']) {
|
||||
$statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2);
|
||||
} else {
|
||||
$statistics[$name]['hit_read_ratio'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function calculateTotalStatistics(): array
|
||||
{
|
||||
$statistics = $this->getStatistics();
|
||||
$totals = [
|
||||
'calls' => 0,
|
||||
'time' => 0,
|
||||
'reads' => 0,
|
||||
'writes' => 0,
|
||||
'deletes' => 0,
|
||||
'hits' => 0,
|
||||
'misses' => 0,
|
||||
];
|
||||
foreach ($statistics as $name => $values) {
|
||||
foreach ($totals as $key => $value) {
|
||||
$totals[$key] += $statistics[$name][$key];
|
||||
}
|
||||
}
|
||||
if ($totals['reads']) {
|
||||
$totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2);
|
||||
} else {
|
||||
$totals['hit_read_ratio'] = null;
|
||||
}
|
||||
|
||||
return $totals;
|
||||
}
|
||||
}
|
94
vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php
vendored
Normal file
94
vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
|
||||
use Symfony\Component\Cache\Adapter\TraceableAdapter;
|
||||
use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Inject a data collector to all the cache services to be able to get detailed statistics.
|
||||
*
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
class CacheCollectorPass implements CompilerPassInterface
|
||||
{
|
||||
private $dataCollectorCacheId;
|
||||
private $cachePoolTag;
|
||||
private $cachePoolRecorderInnerSuffix;
|
||||
|
||||
public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner')
|
||||
{
|
||||
if (0 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
|
||||
}
|
||||
|
||||
$this->dataCollectorCacheId = $dataCollectorCacheId;
|
||||
$this->cachePoolTag = $cachePoolTag;
|
||||
$this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition($this->dataCollectorCacheId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) {
|
||||
$poolName = $attributes[0]['name'] ?? $id;
|
||||
|
||||
$this->addToCollector($id, $poolName, $container);
|
||||
}
|
||||
}
|
||||
|
||||
private function addToCollector(string $id, string $name, ContainerBuilder $container)
|
||||
{
|
||||
$definition = $container->getDefinition($id);
|
||||
if ($definition->isAbstract()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$collectorDefinition = $container->getDefinition($this->dataCollectorCacheId);
|
||||
$recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class);
|
||||
$recorder->setTags($definition->getTags());
|
||||
if (!$definition->isPublic() || !$definition->isPrivate()) {
|
||||
$recorder->setPublic($definition->isPublic());
|
||||
}
|
||||
$recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]);
|
||||
|
||||
foreach ($definition->getMethodCalls() as [$method, $args]) {
|
||||
if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) {
|
||||
continue;
|
||||
}
|
||||
if ([new Reference($id), 'setCallbackWrapper'] == $args[0]->getArguments()[2]->getFactory()) {
|
||||
$args[0]->getArguments()[2]->setFactory([new Reference($innerId), 'setCallbackWrapper']);
|
||||
}
|
||||
}
|
||||
|
||||
$definition->setTags([]);
|
||||
$definition->setPublic(false);
|
||||
|
||||
$container->setDefinition($innerId, $definition);
|
||||
$container->setDefinition($id, $recorder);
|
||||
|
||||
// Tell the collector to add the new instance
|
||||
$collectorDefinition->addMethodCall('addInstance', [$name, new Reference($id)]);
|
||||
$collectorDefinition->setPublic(false);
|
||||
}
|
||||
}
|
52
vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php
vendored
Normal file
52
vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class CachePoolClearerPass implements CompilerPassInterface
|
||||
{
|
||||
private $cachePoolClearerTag;
|
||||
|
||||
public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer')
|
||||
{
|
||||
if (0 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
|
||||
}
|
||||
|
||||
$this->cachePoolClearerTag = $cachePoolClearerTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$container->getParameterBag()->remove('cache.prefix.seed');
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) {
|
||||
$clearer = $container->getDefinition($id);
|
||||
$pools = [];
|
||||
foreach ($clearer->getArgument(0) as $name => $ref) {
|
||||
if ($container->hasDefinition($ref)) {
|
||||
$pools[$name] = new Reference($ref);
|
||||
}
|
||||
}
|
||||
$clearer->replaceArgument(0, $pools);
|
||||
}
|
||||
}
|
||||
}
|
274
vendor/symfony/cache/DependencyInjection/CachePoolPass.php
vendored
Normal file
274
vendor/symfony/cache/DependencyInjection/CachePoolPass.php
vendored
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ChainAdapter;
|
||||
use Symfony\Component\Cache\Adapter\NullAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ParameterNormalizer;
|
||||
use Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class CachePoolPass implements CompilerPassInterface
|
||||
{
|
||||
private $cachePoolTag;
|
||||
private $kernelResetTag;
|
||||
private $cacheClearerId;
|
||||
private $cachePoolClearerTag;
|
||||
private $cacheSystemClearerId;
|
||||
private $cacheSystemClearerTag;
|
||||
private $reverseContainerId;
|
||||
private $reversibleTag;
|
||||
private $messageHandlerId;
|
||||
|
||||
public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer', string $reverseContainerId = 'reverse_container', string $reversibleTag = 'container.reversible', string $messageHandlerId = 'cache.early_expiration_handler')
|
||||
{
|
||||
if (0 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
|
||||
}
|
||||
|
||||
$this->cachePoolTag = $cachePoolTag;
|
||||
$this->kernelResetTag = $kernelResetTag;
|
||||
$this->cacheClearerId = $cacheClearerId;
|
||||
$this->cachePoolClearerTag = $cachePoolClearerTag;
|
||||
$this->cacheSystemClearerId = $cacheSystemClearerId;
|
||||
$this->cacheSystemClearerTag = $cacheSystemClearerTag;
|
||||
$this->reverseContainerId = $reverseContainerId;
|
||||
$this->reversibleTag = $reversibleTag;
|
||||
$this->messageHandlerId = $messageHandlerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if ($container->hasParameter('cache.prefix.seed')) {
|
||||
$seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
|
||||
} else {
|
||||
$seed = '_'.$container->getParameter('kernel.project_dir');
|
||||
$seed .= '.'.$container->getParameter('kernel.container_class');
|
||||
}
|
||||
|
||||
$needsMessageHandler = false;
|
||||
$allPools = [];
|
||||
$clearers = [];
|
||||
$attributes = [
|
||||
'provider',
|
||||
'name',
|
||||
'namespace',
|
||||
'default_lifetime',
|
||||
'early_expiration_message_bus',
|
||||
'reset',
|
||||
];
|
||||
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
|
||||
$adapter = $pool = $container->getDefinition($id);
|
||||
if ($pool->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
$class = $adapter->getClass();
|
||||
while ($adapter instanceof ChildDefinition) {
|
||||
$adapter = $container->findDefinition($adapter->getParent());
|
||||
$class = $class ?: $adapter->getClass();
|
||||
if ($t = $adapter->getTag($this->cachePoolTag)) {
|
||||
$tags[0] += $t[0];
|
||||
}
|
||||
}
|
||||
$name = $tags[0]['name'] ?? $id;
|
||||
if (!isset($tags[0]['namespace'])) {
|
||||
$namespaceSeed = $seed;
|
||||
if (null !== $class) {
|
||||
$namespaceSeed .= '.'.$class;
|
||||
}
|
||||
|
||||
$tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name);
|
||||
}
|
||||
if (isset($tags[0]['clearer'])) {
|
||||
$clearer = $tags[0]['clearer'];
|
||||
while ($container->hasAlias($clearer)) {
|
||||
$clearer = (string) $container->getAlias($clearer);
|
||||
}
|
||||
} else {
|
||||
$clearer = null;
|
||||
}
|
||||
unset($tags[0]['clearer'], $tags[0]['name']);
|
||||
|
||||
if (isset($tags[0]['provider'])) {
|
||||
$tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
|
||||
}
|
||||
|
||||
if (ChainAdapter::class === $class) {
|
||||
$adapters = [];
|
||||
foreach ($adapter->getArgument(0) as $provider => $adapter) {
|
||||
if ($adapter instanceof ChildDefinition) {
|
||||
$chainedPool = $adapter;
|
||||
} else {
|
||||
$chainedPool = $adapter = new ChildDefinition($adapter);
|
||||
}
|
||||
|
||||
$chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]];
|
||||
$chainedClass = '';
|
||||
|
||||
while ($adapter instanceof ChildDefinition) {
|
||||
$adapter = $container->findDefinition($adapter->getParent());
|
||||
$chainedClass = $chainedClass ?: $adapter->getClass();
|
||||
if ($t = $adapter->getTag($this->cachePoolTag)) {
|
||||
$chainedTags[0] += $t[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (ChainAdapter::class === $chainedClass) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent()));
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
|
||||
if (isset($chainedTags[0]['provider'])) {
|
||||
$chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider'])));
|
||||
}
|
||||
|
||||
if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], true)) {
|
||||
$chainedPool->replaceArgument($i++, $tags[0]['namespace']);
|
||||
}
|
||||
|
||||
if (isset($tags[0]['default_lifetime'])) {
|
||||
$chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']);
|
||||
}
|
||||
|
||||
$adapters[] = $chainedPool;
|
||||
}
|
||||
|
||||
$pool->replaceArgument(0, $adapters);
|
||||
unset($tags[0]['provider'], $tags[0]['namespace']);
|
||||
$i = 1;
|
||||
} else {
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
foreach ($attributes as $attr) {
|
||||
if (!isset($tags[0][$attr])) {
|
||||
// no-op
|
||||
} elseif ('reset' === $attr) {
|
||||
if ($tags[0][$attr]) {
|
||||
$pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
|
||||
}
|
||||
} elseif ('early_expiration_message_bus' === $attr) {
|
||||
$needsMessageHandler = true;
|
||||
$pool->addMethodCall('setCallbackWrapper', [(new Definition(EarlyExpirationDispatcher::class))
|
||||
->addArgument(new Reference($tags[0]['early_expiration_message_bus']))
|
||||
->addArgument(new Reference($this->reverseContainerId))
|
||||
->addArgument((new Definition('callable'))
|
||||
->setFactory([new Reference($id), 'setCallbackWrapper'])
|
||||
->addArgument(null)
|
||||
),
|
||||
]);
|
||||
$pool->addTag($this->reversibleTag);
|
||||
} elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) {
|
||||
$argument = $tags[0][$attr];
|
||||
|
||||
if ('default_lifetime' === $attr && !is_numeric($argument)) {
|
||||
$argument = (new Definition('int', [$argument]))
|
||||
->setFactory([ParameterNormalizer::class, 'normalizeDuration']);
|
||||
}
|
||||
|
||||
$pool->replaceArgument($i++, $argument);
|
||||
}
|
||||
unset($tags[0][$attr]);
|
||||
}
|
||||
if (!empty($tags[0])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
|
||||
}
|
||||
|
||||
if (null !== $clearer) {
|
||||
$clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
|
||||
}
|
||||
|
||||
$allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
|
||||
}
|
||||
|
||||
if (!$needsMessageHandler) {
|
||||
$container->removeDefinition($this->messageHandlerId);
|
||||
}
|
||||
|
||||
$notAliasedCacheClearerId = $this->cacheClearerId;
|
||||
while ($container->hasAlias($this->cacheClearerId)) {
|
||||
$this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);
|
||||
}
|
||||
if ($container->hasDefinition($this->cacheClearerId)) {
|
||||
$clearers[$notAliasedCacheClearerId] = $allPools;
|
||||
}
|
||||
|
||||
foreach ($clearers as $id => $pools) {
|
||||
$clearer = $container->getDefinition($id);
|
||||
if ($clearer instanceof ChildDefinition) {
|
||||
$clearer->replaceArgument(0, $pools);
|
||||
} else {
|
||||
$clearer->setArgument(0, $pools);
|
||||
}
|
||||
$clearer->addTag($this->cachePoolClearerTag);
|
||||
|
||||
if ($this->cacheSystemClearerId === $id) {
|
||||
$clearer->addTag($this->cacheSystemClearerTag);
|
||||
}
|
||||
}
|
||||
|
||||
$allPoolsKeys = array_keys($allPools);
|
||||
|
||||
if ($container->hasDefinition('console.command.cache_pool_list')) {
|
||||
$container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, $allPoolsKeys);
|
||||
}
|
||||
|
||||
if ($container->hasDefinition('console.command.cache_pool_clear')) {
|
||||
$container->getDefinition('console.command.cache_pool_clear')->addArgument($allPoolsKeys);
|
||||
}
|
||||
|
||||
if ($container->hasDefinition('console.command.cache_pool_delete')) {
|
||||
$container->getDefinition('console.command.cache_pool_delete')->addArgument($allPoolsKeys);
|
||||
}
|
||||
}
|
||||
|
||||
private function getNamespace(string $seed, string $id)
|
||||
{
|
||||
return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function getServiceProvider(ContainerBuilder $container, string $name)
|
||||
{
|
||||
$container->resolveEnvPlaceholders($name, null, $usedEnvs);
|
||||
|
||||
if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) {
|
||||
$dsn = $name;
|
||||
|
||||
if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) {
|
||||
$definition = new Definition(AbstractAdapter::class);
|
||||
$definition->setPublic(false);
|
||||
$definition->setFactory([AbstractAdapter::class, 'createConnection']);
|
||||
$definition->setArguments([$dsn, ['lazy' => true]]);
|
||||
$container->setDefinition($name, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
64
vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
vendored
Normal file
64
vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Rob Frawley 2nd <rmf@src.run>
|
||||
*/
|
||||
class CachePoolPrunerPass implements CompilerPassInterface
|
||||
{
|
||||
private $cacheCommandServiceId;
|
||||
private $cachePoolTag;
|
||||
|
||||
public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool')
|
||||
{
|
||||
if (0 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
|
||||
}
|
||||
|
||||
$this->cacheCommandServiceId = $cacheCommandServiceId;
|
||||
$this->cachePoolTag = $cachePoolTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition($this->cacheCommandServiceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$services = [];
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
|
||||
$class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
|
||||
|
||||
if (!$reflection = $container->getReflectionClass($class)) {
|
||||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
|
||||
}
|
||||
|
||||
if ($reflection->implementsInterface(PruneableInterface::class)) {
|
||||
$services[$id] = new Reference($id);
|
||||
}
|
||||
}
|
||||
|
||||
$container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
|
||||
}
|
||||
}
|
124
vendor/symfony/cache/DoctrineProvider.php
vendored
Normal file
124
vendor/symfony/cache/DoctrineProvider.php
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache;
|
||||
|
||||
use Doctrine\Common\Cache\CacheProvider;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
if (!class_exists(CacheProvider::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @deprecated Use Doctrine\Common\Cache\Psr6\DoctrineProvider instead
|
||||
*/
|
||||
class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface
|
||||
{
|
||||
private $pool;
|
||||
|
||||
public function __construct(CacheItemPoolInterface $pool)
|
||||
{
|
||||
trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "Doctrine\Common\Cache\Psr6\DoctrineProvider" instead.', __CLASS__);
|
||||
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
return $this->pool instanceof PruneableInterface && $this->pool->prune();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
if ($this->pool instanceof ResetInterface) {
|
||||
$this->pool->reset();
|
||||
}
|
||||
$this->setNamespace($this->getNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function doFetch($id)
|
||||
{
|
||||
$item = $this->pool->getItem(rawurlencode($id));
|
||||
|
||||
return $item->isHit() ? $item->get() : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function doContains($id)
|
||||
{
|
||||
return $this->pool->hasItem(rawurlencode($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function doSave($id, $data, $lifeTime = 0)
|
||||
{
|
||||
$item = $this->pool->getItem(rawurlencode($id));
|
||||
|
||||
if (0 < $lifeTime) {
|
||||
$item->expiresAfter($lifeTime);
|
||||
}
|
||||
|
||||
return $this->pool->save($item->set($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function doDelete($id)
|
||||
{
|
||||
return $this->pool->deleteItem(rawurlencode($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function doFlush()
|
||||
{
|
||||
return $this->pool->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function doGetStats()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
25
vendor/symfony/cache/Exception/CacheException.php
vendored
Normal file
25
vendor/symfony/cache/Exception/CacheException.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Exception;
|
||||
|
||||
use Psr\Cache\CacheException as Psr6CacheInterface;
|
||||
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
|
||||
|
||||
if (interface_exists(SimpleCacheInterface::class)) {
|
||||
class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
|
||||
{
|
||||
}
|
||||
} else {
|
||||
class CacheException extends \Exception implements Psr6CacheInterface
|
||||
{
|
||||
}
|
||||
}
|
25
vendor/symfony/cache/Exception/InvalidArgumentException.php
vendored
Normal file
25
vendor/symfony/cache/Exception/InvalidArgumentException.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Exception;
|
||||
|
||||
use Psr\Cache\InvalidArgumentException as Psr6CacheInterface;
|
||||
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
|
||||
|
||||
if (interface_exists(SimpleCacheInterface::class)) {
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
|
||||
{
|
||||
}
|
||||
} else {
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
|
||||
{
|
||||
}
|
||||
}
|
25
vendor/symfony/cache/Exception/LogicException.php
vendored
Normal file
25
vendor/symfony/cache/Exception/LogicException.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Exception;
|
||||
|
||||
use Psr\Cache\CacheException as Psr6CacheInterface;
|
||||
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
|
||||
|
||||
if (interface_exists(SimpleCacheInterface::class)) {
|
||||
class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface
|
||||
{
|
||||
}
|
||||
} else {
|
||||
class LogicException extends \LogicException implements Psr6CacheInterface
|
||||
{
|
||||
}
|
||||
}
|
19
vendor/symfony/cache/LICENSE
vendored
Normal file
19
vendor/symfony/cache/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016-2022 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.
|
165
vendor/symfony/cache/LockRegistry.php
vendored
Normal file
165
vendor/symfony/cache/LockRegistry.php
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
/**
|
||||
* LockRegistry is used internally by existing adapters to protect against cache stampede.
|
||||
*
|
||||
* It does so by wrapping the computation of items in a pool of locks.
|
||||
* Foreach each apps, there can be at most 20 concurrent processes that
|
||||
* compute items at the same time and only one per cache-key.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class LockRegistry
|
||||
{
|
||||
private static $openedFiles = [];
|
||||
private static $lockedFiles;
|
||||
private static $signalingException;
|
||||
private static $signalingCallback;
|
||||
|
||||
/**
|
||||
* The number of items in this list controls the max number of concurrent processes.
|
||||
*/
|
||||
private static $files = [
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseBucketAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseCollectionAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineDbalAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ParameterNormalizer.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php',
|
||||
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* Defines a set of existing files that will be used as keys to acquire locks.
|
||||
*
|
||||
* @return array The previously defined set of files
|
||||
*/
|
||||
public static function setFiles(array $files): array
|
||||
{
|
||||
$previousFiles = self::$files;
|
||||
self::$files = $files;
|
||||
|
||||
foreach (self::$openedFiles as $file) {
|
||||
if ($file) {
|
||||
flock($file, \LOCK_UN);
|
||||
fclose($file);
|
||||
}
|
||||
}
|
||||
self::$openedFiles = self::$lockedFiles = [];
|
||||
|
||||
return $previousFiles;
|
||||
}
|
||||
|
||||
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
|
||||
{
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) {
|
||||
// disable locking on Windows by default
|
||||
self::$files = self::$lockedFiles = [];
|
||||
}
|
||||
|
||||
$key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1;
|
||||
|
||||
if ($key < 0 || self::$lockedFiles || !$lock = self::open($key)) {
|
||||
return $callback($item, $save);
|
||||
}
|
||||
|
||||
self::$signalingException ?? self::$signalingException = unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
|
||||
self::$signalingCallback ?? self::$signalingCallback = function () { throw self::$signalingException; };
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
$locked = false;
|
||||
// race to get the lock in non-blocking mode
|
||||
$locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
|
||||
|
||||
if ($locked || !$wouldBlock) {
|
||||
$logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
|
||||
self::$lockedFiles[$key] = true;
|
||||
|
||||
$value = $callback($item, $save);
|
||||
|
||||
if ($save) {
|
||||
if ($setMetadata) {
|
||||
$setMetadata($item);
|
||||
}
|
||||
|
||||
$pool->save($item->set($value));
|
||||
$save = false;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
// if we failed the race, retry locking in blocking mode to wait for the winner
|
||||
$logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
|
||||
flock($lock, \LOCK_SH);
|
||||
} finally {
|
||||
flock($lock, \LOCK_UN);
|
||||
unset(self::$lockedFiles[$key]);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $pool->get($item->getKey(), self::$signalingCallback, 0);
|
||||
$logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
|
||||
$save = false;
|
||||
|
||||
return $value;
|
||||
} catch (\Exception $e) {
|
||||
if (self::$signalingException !== $e) {
|
||||
throw $e;
|
||||
}
|
||||
$logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function open(int $key)
|
||||
{
|
||||
if (null !== $h = self::$openedFiles[$key] ?? null) {
|
||||
return $h;
|
||||
}
|
||||
set_error_handler(function () {});
|
||||
try {
|
||||
$h = fopen(self::$files[$key], 'r+');
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
|
||||
}
|
||||
}
|
104
vendor/symfony/cache/Marshaller/DefaultMarshaller.php
vendored
Normal file
104
vendor/symfony/cache/Marshaller/DefaultMarshaller.php
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Marshaller;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
|
||||
/**
|
||||
* Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class DefaultMarshaller implements MarshallerInterface
|
||||
{
|
||||
private $useIgbinarySerialize = true;
|
||||
private $throwOnSerializationFailure;
|
||||
|
||||
public function __construct(bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false)
|
||||
{
|
||||
if (null === $useIgbinarySerialize) {
|
||||
$useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.6', phpversion('igbinary'), '<='));
|
||||
} elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.6', phpversion('igbinary'), '>')))) {
|
||||
throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.');
|
||||
}
|
||||
$this->useIgbinarySerialize = $useIgbinarySerialize;
|
||||
$this->throwOnSerializationFailure = $throwOnSerializationFailure;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function marshall(array $values, ?array &$failed): array
|
||||
{
|
||||
$serialized = $failed = [];
|
||||
|
||||
foreach ($values as $id => $value) {
|
||||
try {
|
||||
if ($this->useIgbinarySerialize) {
|
||||
$serialized[$id] = igbinary_serialize($value);
|
||||
} else {
|
||||
$serialized[$id] = serialize($value);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($this->throwOnSerializationFailure) {
|
||||
throw new \ValueError($e->getMessage(), 0, $e);
|
||||
}
|
||||
$failed[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unmarshall(string $value)
|
||||
{
|
||||
if ('b:0;' === $value) {
|
||||
return false;
|
||||
}
|
||||
if ('N;' === $value) {
|
||||
return null;
|
||||
}
|
||||
static $igbinaryNull;
|
||||
if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') ? igbinary_serialize(null) : false)) {
|
||||
return null;
|
||||
}
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
||||
try {
|
||||
if (':' === ($value[1] ?? ':')) {
|
||||
if (false !== $value = unserialize($value)) {
|
||||
return $value;
|
||||
}
|
||||
} elseif (false === $igbinaryNull) {
|
||||
throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
|
||||
} elseif (null !== $value = igbinary_unserialize($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function handleUnserializeCallback(string $class)
|
||||
{
|
||||
throw new \DomainException('Class not found: '.$class);
|
||||
}
|
||||
}
|
53
vendor/symfony/cache/Marshaller/DeflateMarshaller.php
vendored
Normal file
53
vendor/symfony/cache/Marshaller/DeflateMarshaller.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Marshaller;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
|
||||
/**
|
||||
* Compresses values using gzdeflate().
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class DeflateMarshaller implements MarshallerInterface
|
||||
{
|
||||
private $marshaller;
|
||||
|
||||
public function __construct(MarshallerInterface $marshaller)
|
||||
{
|
||||
if (!\function_exists('gzdeflate')) {
|
||||
throw new CacheException('The "zlib" PHP extension is not loaded.');
|
||||
}
|
||||
|
||||
$this->marshaller = $marshaller;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function marshall(array $values, ?array &$failed): array
|
||||
{
|
||||
return array_map('gzdeflate', $this->marshaller->marshall($values, $failed));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unmarshall(string $value)
|
||||
{
|
||||
if (false !== $inflatedValue = @gzinflate($value)) {
|
||||
$value = $inflatedValue;
|
||||
}
|
||||
|
||||
return $this->marshaller->unmarshall($value);
|
||||
}
|
||||
}
|
40
vendor/symfony/cache/Marshaller/MarshallerInterface.php
vendored
Normal file
40
vendor/symfony/cache/Marshaller/MarshallerInterface.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Cache\Marshaller;
|
||||
|
||||
/**
|
||||
* Serializes/unserializes PHP values.
|
||||
*
|
||||
* Implementations of this interface MUST deal with errors carefully. They MUST
|
||||
* also deal with forward and backward compatibility at the storage format level.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface MarshallerInterface
|
||||
{
|
||||
/**
|
||||
* Serializes a list of values.
|
||||
*
|
||||
* When serialization fails for a specific value, no exception should be
|
||||
* thrown. Instead, its key should be listed in $failed.
|
||||
*/
|
||||
public function marshall(array $values, ?array &$failed): array;
|
||||
|
||||
/**
|
||||
* Unserializes a single value and throws an exception if anything goes wrong.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Exception Whenever unserialization fails
|
||||
*/
|
||||
public function unmarshall(string $value);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user