From 5e31d83527e815930e35fe4fca46d98fc96bc8a2 Mon Sep 17 00:00:00 2001 From: butschster Date: Sun, 23 Nov 2025 10:55:20 +0400 Subject: [PATCH 1/4] Rework SMTP server Use built-in RR SMTP server --- .docker/Dockerfile | 30 +- .docker/velox.toml | 96 + .github/workflows/docker-dev-image.yml | 1 + .github/workflows/docker-image.yml | 1 + .rr-prod.yaml | 22 +- .rr.yaml | 8 + README.md | 2 +- app/config/queue.php | 5 +- app/config/tcp.php | 2 - app/modules/Smtp/Application/Mail/Parser.php | 108 -- .../Smtp/Application/SmtpBootloader.php | 9 +- .../Application/Storage/EmailBodyStorage.php | 46 - .../Smtp/Application/Storage/Message.php | 76 - .../Smtp/Interfaces/Jobs/EmailHandler.php | 79 + .../Smtp/Interfaces/TCP/ResponseMessage.php | 85 - app/modules/Smtp/Interfaces/TCP/Service.php | 141 -- .../VarDumper/Application/Dump/HtmlDumper.php | 2 +- .../Bootloader/LoggingBootloader.php | 17 + app/src/Application/Kernel.php | 3 +- .../Service/HttpHandler/HandlerPipeline.php | 2 +- composer.json | 3 +- composer.lock | 1564 +++++++++++------ docker-compose.yaml | 19 +- 23 files changed, 1273 insertions(+), 1048 deletions(-) create mode 100644 .docker/velox.toml delete mode 100644 app/modules/Smtp/Application/Mail/Parser.php delete mode 100644 app/modules/Smtp/Application/Storage/EmailBodyStorage.php delete mode 100644 app/modules/Smtp/Application/Storage/Message.php create mode 100644 app/modules/Smtp/Interfaces/Jobs/EmailHandler.php delete mode 100644 app/modules/Smtp/Interfaces/TCP/ResponseMessage.php delete mode 100644 app/modules/Smtp/Interfaces/TCP/Service.php create mode 100644 app/src/Application/Bootloader/LoggingBootloader.php diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 5ba9ade5..d11cdc98 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -2,13 +2,37 @@ ARG ROAD_RUNNER_IMAGE=2024.2.1 ARG CENTRIFUGO_IMAGE=v4 ARG DOLT_IMAGE=1.42.8 ARG FRONTEND_IMAGE_TAG=latest +ARG GH_TOKEN # Build centrifugo binary FROM centrifugo/centrifugo:$CENTRIFUGO_IMAGE as centrifugo # Build dolt binary FROM dolthub/dolt:$DOLT_IMAGE as dolt # Build rr binary -FROM ghcr.io/roadrunner-server/roadrunner:$ROAD_RUNNER_IMAGE as rr +FROM ghcr.io/roadrunner-server/velox:2025.1.1 as velox +#FROM ghcr.io/roadrunner-server/roadrunner:$ROAD_RUNNER_IMAGE as rr + +FROM golang:1.25-alpine as rr + +ARG APP_VERSION="2025.1.1" +ARG VELOX_CONFIG="velox.toml" +ARG GH_TOKEN + +# copy required files from builder image +COPY --from=velox /usr/bin/vx /usr/bin/vx +COPY ${VELOX_CONFIG} . + +# we don't need CGO +ENV CGO_ENABLED=0 +ENV RT_TOKEN=${GH_TOKEN} +ENV VELOX_CONFIG=${VELOX_CONFIG} +ARG CACHE_BUST=1 + +RUN echo $CACHE_BUST + +# RUN build +RUN vx build -c ${VELOX_CONFIG} -o /usr/bin/ + # Build JS files FROM ghcr.io/buggregator/frontend:$FRONTEND_IMAGE_TAG as frontend # Clone the project @@ -23,7 +47,7 @@ FROM ghcr.io/buggregator/docker:latest as backend COPY --from=git /app /app COPY --from=frontend /app /app/frontend -COPY --from=rr /usr/bin/rr /app +COPY --from=rr /usr/bin/rr /usr/bin/rr COPY --from=centrifugo /usr/local/bin/centrifugo /app/bin COPY --from=dolt /usr/local/bin/dolt /app/bin @@ -65,4 +89,4 @@ LABEL org.opencontainers.image.source=$REPOSITORY LABEL org.opencontainers.image.description="Buggregator" LABEL org.opencontainers.image.licenses=MIT -CMD ./rr serve -c .rr-prod.yaml +CMD /usr/bin/rr serve -c .rr-prod.yaml -w /app diff --git a/.docker/velox.toml b/.docker/velox.toml new file mode 100644 index 00000000..902d57f2 --- /dev/null +++ b/.docker/velox.toml @@ -0,0 +1,96 @@ +[roadrunner] +ref = "v2025.1.1" + +[log] +level = "info" +mode = "production" + +[github] +[github.token] +token = "${GH_TOKEN}" + +[github.plugins] +[github.plugins.logger] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "logger" + +[github.plugins.server] +ref = "v5.2.9" +owner = "roadrunner-server" +repository = "server" + +[github.plugins.rpc] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "rpc" + +[github.plugins.service] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "service" + +[github.plugins.lock] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "lock" + +[github.plugins.http] +ref = "v5.2.7" +owner = "roadrunner-server" +repository = "http" + +[github.plugins.gzip] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "gzip" + +[github.plugins.headers] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "headers" + +[github.plugins.jobs] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "jobs" + +[github.plugins.kv] +ref = "v5.2.8" +owner = "roadrunner-server" +repository = "kv" + +[github.plugins.memory] +ref = "v5.2.8" +owner = "roadrunner-server" +repository = "memory" + +[github.plugins.metrics] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "metrics" + +[github.plugins.status] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "status" + +[github.plugins.appLogger] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "app-logger" + +[github.plugins.centrifuge] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "centrifuge" + +[github.plugins.tcp] +ref = "v5.0.0" +owner = "roadrunner-server" +repository = "tcp" + +[github.plugins.smtp-server] +ref = "2.0.0" +owner = "buggregator" +repository = "smtp-server" \ No newline at end of file diff --git a/.github/workflows/docker-dev-image.yml b/.github/workflows/docker-dev-image.yml index e904990d..5f844cce 100644 --- a/.github/workflows/docker-dev-image.yml +++ b/.github/workflows/docker-dev-image.yml @@ -45,5 +45,6 @@ jobs: APP_VERSION=${{ steps.previoustag.outputs.tag }} FRONTEND_IMAGE_TAG=latest BRANCH=${{ steps.previoustag.outputs.tag }} + GH_TOKEN=${{ secrets.GHCR_PASSWORD }} tags: ghcr.io/${{ github.repository }}:dev, ghcr.io/${{ github.repository }}:${{ steps.previoustag.outputs.tag }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index f94dcf74..6238133e 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -98,6 +98,7 @@ jobs: APP_VERSION=${{ github.ref_name }} FRONTEND_IMAGE_TAG=${{ secrets.FRONTEND_IMAGE_TAG }} BRANCH=${{ github.ref_name }} + GH_TOKEN=${{ secrets.GHCR_PASSWORD }} tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} cache-from: type=gha diff --git a/.rr-prod.yaml b/.rr-prod.yaml index 1c22c728..250033d0 100644 --- a/.rr-prod.yaml +++ b/.rr-prod.yaml @@ -29,7 +29,7 @@ logs: level: ${RR_LOG_TCP_LEVEL:-warn} jobs: # JOBS plugin logging level can be "panic", "error", "warn", "info", "debug". - level: ${RR_LOG_TCP_LEVEL:-warn} + level: ${RR_LOG_JOBS_LEVEL:-warn} centrifuge: # Centrifuge plugin logging level can be "panic", "error", "warn", "info", "debug". level: ${RR_LOG_CENTRIFUGE_LEVEL:-warn} @@ -64,9 +64,6 @@ tcp: # Address to listen. addr: ${RR_TCP_VAR_DUMPER_ADDR:-:9912} delimiter: "\n" - smtp: - # Address to listen. - addr: ${RR_TCP_SMTP_ADDR:-:1025} # Chunks that RR uses to read the data. In bytes. # If you expect big payloads on a TCP server, to reduce `read` syscalls, # would be a good practice to use a fairly big enough buffer. @@ -81,10 +78,23 @@ kv: config: { } jobs: - consume: [ ] + consume: + - smtp + pipelines: + smtp: + driver: memory + config: + priority: 10 + prefetch: 10 pool: num_workers: ${RR_JOBS_NUM_WORKERS:-1} +smtp: + addr: ${RR_SMTP_ADDR:-:1025} + hostname: "buggregator.local" + jobs: + pipeline: smtp + service: nginx: service_name_in_log: true @@ -106,4 +116,4 @@ centrifuge: proxy_address: ${RR_CENTRIFUGE_PROXY_ADDRESS} grpc_api_address: ${RR_CENTRIFUGE_GRPC_API_ADDRESS} pool: - num_workers: ${RR_CENTRIFUGE_NUM_WORKERS:-2} + num_workers: ${RR_CENTRIFUGE_NUM_WORKERS:-2} \ No newline at end of file diff --git a/.rr.yaml b/.rr.yaml index 1b12296d..69c99da1 100644 --- a/.rr.yaml +++ b/.rr.yaml @@ -52,6 +52,14 @@ jobs: pool: num_workers: 1 + +smtp: + addr: ${RR_SMTP_ADDR:-:1025} + hostname: "buggregator.local" + include_raw: true + jobs: + pipeline: "smtp" + service: centrifuge: service_name_in_log: true diff --git a/README.md b/README.md index ce254fa9..6188c7cc 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ See the [documentation](https://docs.buggregator.dev/) for detailed installati We enthusiastically invite you to contribute to Buggregator Server! Whether you've uncovered a bug, have innovative feature suggestions, or wish to contribute in any other capacity, we warmly welcome your participation. Simply open an issue or submit a pull request on our GitHub repository to get started. > **Note** -> Read more how to contribute [here](https://docs.buggregator.dev/contributing/architecture.html) +> Read more how to contribute [here](https://docs.buggregator.dev/contributing.html) --- diff --git a/app/config/queue.php b/app/config/queue.php index f0ad8692..84dca517 100644 --- a/app/config/queue.php +++ b/app/config/queue.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Modules\Smtp\Interfaces\Jobs\EmailHandler; use Modules\Webhooks\Interfaces\Job\WebhookHandler; use Spiral\Queue\Driver\SyncDriver; use Spiral\RoadRunner\Jobs\Queue\MemoryCreateInfo; @@ -36,7 +37,9 @@ 'roadrunner' => Queue::class, ], 'registry' => [ - 'handlers' => [], + 'handlers' => [ + 'smtp.email' => EmailHandler::class, + ], 'serializers' => [ WebhookHandler::class => 'symfony-json', ], diff --git a/app/config/tcp.php b/app/config/tcp.php index afc7e92d..9f01607b 100644 --- a/app/config/tcp.php +++ b/app/config/tcp.php @@ -5,13 +5,11 @@ use App\Application\TCP\ExceptionHandlerInterceptor; use Modules\VarDumper\Interfaces\TCP\Service as VarDumperService; use Modules\Monolog\Interfaces\TCP\Service as MonologService; -use Modules\Smtp\Interfaces\TCP\Service as SmtpService; return [ 'services' => [ 'var-dumper' => VarDumperService::class, 'monolog' => MonologService::class, - 'smtp' => SmtpService::class, ], 'interceptors' => [ diff --git a/app/modules/Smtp/Application/Mail/Parser.php b/app/modules/Smtp/Application/Mail/Parser.php deleted file mode 100644 index 8b0dce33..00000000 --- a/app/modules/Smtp/Application/Mail/Parser.php +++ /dev/null @@ -1,108 +0,0 @@ -getHeader('from')?->getParts()[0] ?? null; - $from = [['email' => $fromData?->getValue(), 'name' => $fromData?->getName()]]; - - /** @var AddressHeader|null $toHeader */ - $toHeader = $message->getHeader('to'); - $recipients = $this->joinNameAndEmail($toHeader ? $toHeader->getAddresses() : []); - /** @var AddressHeader|null $ccHeader */ - $ccHeader = $message->getHeader('cc'); - $ccs = $this->joinNameAndEmail($ccHeader ? $ccHeader->getAddresses() : []); - $subject = (string) $message->getHeaderValue('subject'); - $html = (string) $message->getHtmlContent(); - $text = (string) $message->getTextContent(); - /** @var AbstractHeader|null $replyToHeader */ - $replyToHeader = $message->getHeader('reply-to')?->getParts()[0] ?? null; - $replyTo = $replyToHeader ? [ - [ - 'email' => $replyToHeader?->getValue(), - 'name' => $replyToHeader?->getName(), - ], - ] : []; - - $attachments = $this->buildAttachmentFrom( - $message->getAllAttachmentParts(), - ); - - return new Message( - $message->getHeader('Message - Id')?->getValue(), - $body, - $from, - $recipients, - $ccs, - $subject, - $html, - $text, - $replyTo, - $allRecipients, - $attachments, - ); - } - - /** - * @param ParseMessage\IMessagePart[] $attachments - * @return Attachment[] - */ - private function buildAttachmentFrom(array $attachments): array - { - $result = []; - - foreach ($attachments as $part) { - try { - $processor = $this->processorFactory->createProcessor($part); - $attachment = $processor->processAttachment($part); - $result[] = $attachment; - } catch (\Throwable $e) { - $this->reporter->report($e); - // Create a fallback attachment - $fallbackFilename = 'failed_attachment_' . uniqid() . '.bin'; - $result[] = new Attachment( - filename: $fallbackFilename, - content: $part->getContent() ?? '', - type: $part->getContentType() ?? 'application/octet-stream', - contentId: $part->getContentId(), - ); - } - } - - return $result; - } - - /** - * @param AddressPart[] $addresses - * @return string[] - */ - private function joinNameAndEmail(array $addresses): array - { - return \array_map(function (AddressPart $addressPart) { - $name = $addressPart->getName(); - $email = $addressPart->getValue(); - - return ['name' => $name, 'email' => $email]; - }, $addresses); - } -} diff --git a/app/modules/Smtp/Application/SmtpBootloader.php b/app/modules/Smtp/Application/SmtpBootloader.php index b73a44a6..31349e66 100644 --- a/app/modules/Smtp/Application/SmtpBootloader.php +++ b/app/modules/Smtp/Application/SmtpBootloader.php @@ -11,14 +11,13 @@ use Cycle\ORM\ORMInterface; use Cycle\ORM\Select; use Modules\Smtp\Application\Storage\AttachmentStorage; -use Modules\Smtp\Application\Storage\EmailBodyStorage; use Modules\Smtp\Domain\Attachment; use Modules\Smtp\Domain\AttachmentFactoryInterface; use Modules\Smtp\Domain\AttachmentRepositoryInterface; use Modules\Smtp\Domain\AttachmentStorageInterface; use Modules\Smtp\Integration\CycleOrm\AttachmentRepository; use Spiral\Boot\Bootloader\Bootloader; -use Spiral\Cache\CacheStorageProviderInterface; +use Spiral\Boot\KernelInterface; use Spiral\Core\FactoryInterface; use Spiral\Storage\StorageInterface; @@ -27,12 +26,6 @@ final class SmtpBootloader extends Bootloader public function defineSingletons(): array { return [ - EmailBodyStorage::class => static fn( - CacheStorageProviderInterface $provider, - ) => new EmailBodyStorage( - cache: $provider->storage('smtp'), - ), - AttachmentStorageInterface::class => static fn( StorageInterface $storage, AttachmentRepositoryInterface $attachments, diff --git a/app/modules/Smtp/Application/Storage/EmailBodyStorage.php b/app/modules/Smtp/Application/Storage/EmailBodyStorage.php deleted file mode 100644 index c3865415..00000000 --- a/app/modules/Smtp/Application/Storage/EmailBodyStorage.php +++ /dev/null @@ -1,46 +0,0 @@ -cache->get($this->getCacheKey($uuid), new Message($uuid)); - } - - public function cleanup(string $uuid): Message - { - $this->cache->delete($this->getCacheKey($uuid)); - - return new Message($uuid); - } - - public function persist(Message $message): void - { - $this->cache->set( - $this->getCacheKey($message->uuid), - $message, - Carbon::now()->addMinutes(1)->diffAsCarbonInterval(), - ); - } - - public function delete(Message $message): void - { - $this->cache->delete($this->getCacheKey($message->uuid)); - } - - private function getCacheKey(string $uuid): string - { - return $uuid; - } -} diff --git a/app/modules/Smtp/Application/Storage/Message.php b/app/modules/Smtp/Application/Storage/Message.php deleted file mode 100644 index 9ae39b53..00000000 --- a/app/modules/Smtp/Application/Storage/Message.php +++ /dev/null @@ -1,76 +0,0 @@ -username = \base64_decode(\trim($username)); - $this->waitUsername = false; - $this->waitPassword = true; - } - - public function setPassword(string $password): void - { - $this->password = \base64_decode(\trim($password)); - $this->waitPassword = false; - } - - public function addRecipient(string $recipient): void - { - $this->recipients[] = $recipient; - } - - public function setFrom(string $from): void - { - $this->from = $from; - } - - public function appendBody(string $body): void - { - // Handle escaped periods at the beginning of lines per SMTP spec - $safeBody = \preg_replace("/^(\.\.)/m", '.', $body); - - // Ensure body is properly appended even with multi-byte characters - $this->body .= $safeBody; - } - - public function bodyHasEos(): bool - { - // More robust check for end of stream marker - // This handles potential encoding issues with multi-byte characters - return \mb_substr($this->body, -5) === "\r\n.\r\n"; - } - - public function getBody(): string - { - // Remove the end of stream marker in a way that's safe for multi-byte strings - if ($this->bodyHasEos()) { - return \mb_substr($this->body, 0, \mb_strlen($this->body) - 5); - } - - return $this->body; - } - - public function parse(Parser $parser): \Modules\Smtp\Application\Mail\Message - { - return $parser->parse($this->getBody(), $this->recipients); - } -} diff --git a/app/modules/Smtp/Interfaces/Jobs/EmailHandler.php b/app/modules/Smtp/Interfaces/Jobs/EmailHandler.php new file mode 100644 index 00000000..f3822a5e --- /dev/null +++ b/app/modules/Smtp/Interfaces/Jobs/EmailHandler.php @@ -0,0 +1,79 @@ +dispatchMessage(new Message( + id: $data['message']['id'], + raw: $rawBody, + sender: $data['envelope']['from'], + recipients: $data['envelope']['to'], + ccs: $data['envelope']['ccs'], + subject: $data['message']['subject'], + htmlBody: (string) $message->getHtmlContent(), + textBody: (string) $message->getTextContent(), + replyTo: $data['envelope']['replyTo'], + allRecipients: $data['envelope']['allRecipients'], + attachments: \array_map( + static fn(array $attachment) => new Attachment( + filename: $attachment['filename'], + content: \base64_decode($attachment['content'] ?? ''), + type: $attachment['type'] ?? 'application/octet-stream', + contentId: $attachment['contentId'] ?? null, + ), + $data['attachments'], + ), + )); + } + + private function dispatchMessage(Message $message, ?string $project = null): Uuid + { + $uuid = Uuid::generate(); + $data = $message->jsonSerialize(); + + $result = $this->attachments->store(eventUuid: $uuid, attachments: $message->attachments); + // TODO: Refactor this + foreach ($result as $cid => $url) { + $data['html'] = \str_replace("cid:$cid", $url, $data['html']); + } + + $this->bus->dispatch( + new HandleReceivedEvent( + type: 'smtp', + payload: $data, + project: $project, + uuid: $uuid, + ), + ); + + return $uuid; + } +} diff --git a/app/modules/Smtp/Interfaces/TCP/ResponseMessage.php b/app/modules/Smtp/Interfaces/TCP/ResponseMessage.php deleted file mode 100644 index d30e5d4c..00000000 --- a/app/modules/Smtp/Interfaces/TCP/ResponseMessage.php +++ /dev/null @@ -1,85 +0,0 @@ -code, - $this->separator, - $this->message === '' || $this->message === '0' ? '' : $this->message . $this->eosSeparator, - $this->lineEnding, - ); - } -} diff --git a/app/modules/Smtp/Interfaces/TCP/Service.php b/app/modules/Smtp/Interfaces/TCP/Service.php deleted file mode 100644 index 91463f6a..00000000 --- a/app/modules/Smtp/Interfaces/TCP/Service.php +++ /dev/null @@ -1,141 +0,0 @@ -event === TcpEvent::Connected) { - return $this->makeResponse(ResponseMessage::ready()); - } - - $message = $this->emailBodyStorage->getMessage($request->connectionUuid); - - $response = new CloseConnection(); - $dispatched = false; - - if ($request->event === TcpEvent::Close) { - $this->emailBodyStorage->delete($message); - - return new CloseConnection(); - } elseif (\preg_match('/^(EHLO|HELO)/', $request->body)) { - $response = $this->sendMultiply( - ResponseMessage::ok(separator: '-buggregator'), - ResponseMessage::authRequired(), - ); - } elseif (\preg_match('/^MAIL FROM:\s*<(.*)>/', $request->body, $matches)) { - $message->setFrom($matches[1]); - $response = $this->makeResponse(ResponseMessage::ok()); - } elseif (\str_starts_with($request->body, 'AUTH')) { - $response = $this->makeResponse(ResponseMessage::enterUsername()); - $message->waitUsername = true; - } elseif ($message->waitUsername) { - $message->setUsername($request->body); - $response = $this->makeResponse(ResponseMessage::enterPassword()); - } elseif ($message->waitPassword) { - $message->setPassword($request->body); - $response = $this->makeResponse(ResponseMessage::authenticated()); - } elseif (\preg_match('/^RCPT TO:\s*<(.*)>/', $request->body, $matches)) { - $message->addRecipient($matches[1]); - $response = $this->makeResponse(ResponseMessage::ok()); - } elseif (\str_starts_with($request->body, 'QUIT')) { - $response = $this->makeResponse(ResponseMessage::closing(), close: true); - $message = $this->emailBodyStorage->cleanup($request->connectionUuid); - } elseif ($request->body === "DATA\r\n") { - // Reset the body to empty string when starting a new DATA command - // This prevents confusion between multiple DATA commands in the same session - $message->body = ''; - $response = $this->makeResponse(ResponseMessage::provideBody()); - $message->waitBody = true; - } elseif ($request->body === "RSET\r\n") { - $message = $this->emailBodyStorage->cleanup($request->connectionUuid); - $response = $this->makeResponse(ResponseMessage::ok()); - } elseif ($request->body === "NOOP\r\n") { - $response = $this->makeResponse(ResponseMessage::ok()); - } elseif ($message->waitBody) { - $message->appendBody($request->body); - - // FIX: Only send one response when data ends - if ($message->bodyHasEos()) { - $uuid = $this->dispatchMessage($message->parse($this->parser), project: $message->username); - $response = $this->makeResponse(ResponseMessage::accepted($uuid)); - $dispatched = true; - // Reset the waitBody flag to false since we've processed the message - $message->waitBody = false; - } else { - // Only send "OK" response if we're not at the end of data - $response = $this->makeResponse(ResponseMessage::ok()); - } - } - - if ( - $response instanceof CloseConnection || - $response->getAction() === TcpResponse::RespondClose || - $dispatched - ) { - return $response; - } - - $this->emailBodyStorage->persist($message); - - return $response; - } - - private function dispatchMessage(Message $message, ?string $project = null): Uuid - { - $uuid = Uuid::generate(); - $data = $message->jsonSerialize(); - - $result = $this->attachments->store(eventUuid: $uuid, attachments: $message->attachments); - // TODO: Refactor this - foreach ($result as $cid => $url) { - $data['html'] = \str_replace("cid:$cid", $url, $data['html']); - } - - $this->bus->dispatch( - new HandleReceivedEvent( - type: 'smtp', - payload: $data, - project: $project, - uuid: $uuid, - ), - ); - - return $uuid; - } - - private function makeResponse(ResponseMessage $message, bool $close = false): RespondMessage - { - return new RespondMessage((string) $message, $close); - } - - private function sendMultiply(ResponseMessage...$message): RespondMessage - { - return new RespondMessage(\implode("", $message)); - } -} diff --git a/app/modules/VarDumper/Application/Dump/HtmlDumper.php b/app/modules/VarDumper/Application/Dump/HtmlDumper.php index 2902713f..576650c2 100644 --- a/app/modules/VarDumper/Application/Dump/HtmlDumper.php +++ b/app/modules/VarDumper/Application/Dump/HtmlDumper.php @@ -17,7 +17,7 @@ final class HtmlDumper extends CliDumper protected string $dumpPrefix = '
';
     protected string $dumpSuffix = '
'; protected string $dumpId = 'sf-dump'; - protected $colors = true; + protected bool $colors = true; protected int $lastDepth = -1; private array $displayOptions = [ diff --git a/app/src/Application/Bootloader/LoggingBootloader.php b/app/src/Application/Bootloader/LoggingBootloader.php new file mode 100644 index 00000000..bb8cf58e --- /dev/null +++ b/app/src/Application/Bootloader/LoggingBootloader.php @@ -0,0 +1,17 @@ +position++; if ($handler === null) { - return new Response(404); + return new Response(404, [], 'Unprocessable Entity'); } try { diff --git a/composer.json b/composer.json index f271e547..2ca778ce 100644 --- a/composer.json +++ b/composer.json @@ -51,12 +51,13 @@ "spiral/roadrunner-tcp": "^3.1", "spiral/validator": "^1.1", "symfony/mime": "^6.2", - "symfony/var-dumper": "^6.1", + "symfony/var-dumper": "^7.1", "symfony/yaml": "^7.0", "zbateson/mail-mime-parser": "^2.0", "zentlix/swagger-php": "1.x-dev" }, "require-dev": { + "buggregator/trap": "^1.13", "friendsofphp/php-cs-fixer": "^3.40", "phpunit/phpunit": "^10.0", "qossmic/deptrac-shim": "^1.0", diff --git a/composer.lock b/composer.lock index fcf6c6e4..efed8ca3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fa37944c218cf8876412fa3df8602aae", + "content-hash": "5acf6397ca05b9039d78ca54c431a3e8", "packages": [ { "name": "auth0/auth0-php", - "version": "8.15.0", + "version": "8.17.0", "source": { "type": "git", "url": "https://github.com/auth0/auth0-PHP.git", - "reference": "7f4414045fc7dc257baf8451a9cda2aacb7ed03b" + "reference": "1767d11b3dd7a5c622c034fd22649ed254a7683c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/auth0/auth0-PHP/zipball/7f4414045fc7dc257baf8451a9cda2aacb7ed03b", - "reference": "7f4414045fc7dc257baf8451a9cda2aacb7ed03b", + "url": "https://api.github.com/repos/auth0/auth0-PHP/zipball/1767d11b3dd7a5c622c034fd22649ed254a7683c", + "reference": "1767d11b3dd7a5c622c034fd22649ed254a7683c", "shasum": "" }, "require": { @@ -101,31 +101,31 @@ ], "support": { "issues": "https://github.com/auth0/auth0-PHP/issues", - "source": "https://github.com/auth0/auth0-PHP/tree/8.15.0" + "source": "https://github.com/auth0/auth0-PHP/tree/8.17.0" }, - "time": "2025-05-30T09:50:59+00:00" + "time": "2025-10-01T08:49:46+00:00" }, { "name": "brick/math", - "version": "0.13.1", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -155,7 +155,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.13.1" + "source": "https://github.com/brick/math/tree/0.14.0" }, "funding": [ { @@ -163,7 +163,7 @@ "type": "github" } ], - "time": "2025-03-29T13:50:30+00:00" + "time": "2025-08-29T12:40:03+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -424,16 +424,16 @@ }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -485,7 +485,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -495,29 +495,26 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "cycle/annotated", - "version": "v4.3.0", + "version": "v4.4.0", "source": { "type": "git", "url": "https://github.com/cycle/annotated.git", - "reference": "35890d8fe16b6a7a29cbacef5715d31b13b78212" + "reference": "7564927e04449eeec22b3cf94af2d406f129d931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/annotated/zipball/35890d8fe16b6a7a29cbacef5715d31b13b78212", - "reference": "35890d8fe16b6a7a29cbacef5715d31b13b78212", + "url": "https://api.github.com/repos/cycle/annotated/zipball/7564927e04449eeec22b3cf94af2d406f129d931", + "reference": "7564927e04449eeec22b3cf94af2d406f129d931", "shasum": "" }, "require": { + "cycle/database": "^2.15", "cycle/orm": "^2.9.2", "cycle/schema-builder": "^2.11.1", "doctrine/inflector": "^2.0", @@ -574,20 +571,20 @@ "type": "github" } ], - "time": "2025-05-14T14:48:40+00:00" + "time": "2025-11-04T08:20:33+00:00" }, { "name": "cycle/database", - "version": "2.14.0", + "version": "2.15.0", "source": { "type": "git", "url": "https://github.com/cycle/database.git", - "reference": "876fbc2bc0d068f047388c0bd9b354e4d891af07" + "reference": "3d7ee3524b299c5897e2b03dc51bad2ddd609a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/database/zipball/876fbc2bc0d068f047388c0bd9b354e4d891af07", - "reference": "876fbc2bc0d068f047388c0bd9b354e4d891af07", + "url": "https://api.github.com/repos/cycle/database/zipball/3d7ee3524b299c5897e2b03dc51bad2ddd609a90", + "reference": "3d7ee3524b299c5897e2b03dc51bad2ddd609a90", "shasum": "" }, "require": { @@ -667,7 +664,7 @@ "type": "github" } ], - "time": "2025-07-14T11:36:41+00:00" + "time": "2025-07-22T05:27:52+00:00" }, { "name": "cycle/migrations", @@ -717,16 +714,16 @@ }, { "name": "cycle/orm", - "version": "v2.10.1", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/cycle/orm.git", - "reference": "0b659067c00c3ffbee05109fa17812754acc2525" + "reference": "d712c79eab82a2393863c67c15e37b89fd64b555" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/orm/zipball/0b659067c00c3ffbee05109fa17812754acc2525", - "reference": "0b659067c00c3ffbee05109fa17812754acc2525", + "url": "https://api.github.com/repos/cycle/orm/zipball/d712c79eab82a2393863c67c15e37b89fd64b555", + "reference": "d712c79eab82a2393863c67c15e37b89fd64b555", "shasum": "" }, "require": { @@ -800,7 +797,7 @@ "type": "github" } ], - "time": "2025-03-31T19:41:17+00:00" + "time": "2025-09-09T09:42:43+00:00" }, { "name": "cycle/schema-builder", @@ -926,16 +923,16 @@ }, { "name": "cycle/schema-renderer", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/cycle/schema-renderer.git", - "reference": "75afcb552432eb58dffda15d63f4451601c60c82" + "reference": "1a471320ce4987a9722b5e39c64d3b4dc741cf1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/schema-renderer/zipball/75afcb552432eb58dffda15d63f4451601c60c82", - "reference": "75afcb552432eb58dffda15d63f4451601c60c82", + "url": "https://api.github.com/repos/cycle/schema-renderer/zipball/1a471320ce4987a9722b5e39c64d3b4dc741cf1b", + "reference": "1a471320ce4987a9722b5e39c64d3b4dc741cf1b", "shasum": "" }, "require": { @@ -962,9 +959,9 @@ "description": "Utils for Cycle ORM Schema rendering", "support": { "issues": "https://github.com/cycle/schema-renderer/issues", - "source": "https://github.com/cycle/schema-renderer/tree/1.3.0" + "source": "https://github.com/cycle/schema-renderer/tree/1.4.0" }, - "time": "2025-05-08T08:51:06+00:00" + "time": "2025-11-04T05:47:31+00:00" }, { "name": "defuse/php-encryption", @@ -1107,6 +1104,7 @@ "issues": "https://github.com/doctrine/annotations/issues", "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, + "abandoned": true, "time": "2024-09-05T10:17:24+00:00" }, { @@ -1229,33 +1227,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1300,7 +1297,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -1316,7 +1313,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/instantiator", @@ -1597,21 +1594,21 @@ }, { "name": "google/common-protos", - "version": "4.12.1", + "version": "4.12.4", "source": { "type": "git", "url": "https://github.com/googleapis/common-protos-php.git", - "reference": "70c4eb1abab5484a23c17a43b0d455259f5d8c1b" + "reference": "0127156899af0df2681bd42024c60bd5360d64e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/70c4eb1abab5484a23c17a43b0d455259f5d8c1b", - "reference": "70c4eb1abab5484a23c17a43b0d455259f5d8c1b", + "url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/0127156899af0df2681bd42024c60bd5360d64e3", + "reference": "0127156899af0df2681bd42024c60bd5360d64e3", "shasum": "" }, "require": { - "google/protobuf": "^v3.25.3||^4.26.1", - "php": "^8.0" + "google/protobuf": "^4.31", + "php": "^8.1" }, "require-dev": { "phpunit/phpunit": "^9.6" @@ -1650,29 +1647,29 @@ "google" ], "support": { - "source": "https://github.com/googleapis/common-protos-php/tree/v4.12.1" + "source": "https://github.com/googleapis/common-protos-php/tree/v4.12.4" }, - "time": "2025-05-20T19:49:54+00:00" + "time": "2025-09-20T01:29:44+00:00" }, { "name": "google/protobuf", - "version": "v4.31.1", + "version": "v4.33.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" + "reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0cd73ccf0cd26c3e72299cce1ea6144091a57e12", + "reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=8.1.0" }, "require-dev": { - "phpunit/phpunit": ">=5.0.0" + "phpunit/phpunit": ">=5.0.0 <8.5.27" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -1694,9 +1691,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.1" }, - "time": "2025-05-28T18:52:35+00:00" + "time": "2025-11-12T21:58:05+00:00" }, { "name": "graham-campbell/result-type", @@ -1762,16 +1759,16 @@ }, { "name": "grpc/grpc", - "version": "1.57.0", + "version": "1.74.0", "source": { "type": "git", "url": "https://github.com/grpc/grpc-php.git", - "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf" + "reference": "32bf4dba256d60d395582fb6e4e8d3936bcdb713" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grpc/grpc-php/zipball/b610c42022ed3a22f831439cb93802f2a4502fdf", - "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf", + "url": "https://api.github.com/repos/grpc/grpc-php/zipball/32bf4dba256d60d395582fb6e4e8d3936bcdb713", + "reference": "32bf4dba256d60d395582fb6e4e8d3936bcdb713", "shasum": "" }, "require": { @@ -1800,28 +1797,28 @@ "rpc" ], "support": { - "source": "https://github.com/grpc/grpc-php/tree/v1.57.0" + "source": "https://github.com/grpc/grpc-php/tree/v1.74.0" }, - "time": "2023-08-14T23:57:54+00:00" + "time": "2025-07-24T20:02:16+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.9.3", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1912,7 +1909,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -1928,20 +1925,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:37:11+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -1949,7 +1946,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -1995,7 +1992,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -2011,20 +2008,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -2040,7 +2037,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2111,7 +2108,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -2127,26 +2124,89 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2025-08-23T21:21:41+00:00" + }, + { + "name": "internal/destroy", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-internal/destroy.git", + "reference": "93068c4f7da218034f5373e31407f564b74b4a06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-internal/destroy/zipball/93068c4f7da218034f5373e31407f564b74b4a06", + "reference": "93068c4f7da218034f5373e31407f564b74b4a06", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "buggregator/trap": "^1.10", + "phpunit/phpunit": "^10.5", + "spiral/code-style": "^2.2.2", + "ta-tikoma/phpunit-architecture-test": "^0.8.4", + "vimeo/psalm": "^6.10" + }, + "suggest": { + "ext-simplexml": "to support XML configs parsing" + }, + "type": "library", + "autoload": { + "psr-4": { + "Internal\\Destroy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" + } + ], + "keywords": [ + "download binaries", + "memory" + ], + "support": { + "issues": "https://github.com/php-internal/destroy/issues", + "source": "https://github.com/php-internal/destroy/tree/1.0.0" + }, + "funding": [ + { + "url": "https://patreon.com/roxblnfk", + "type": "patreon" + } + ], + "time": "2025-09-08T14:29:16+00:00" }, { "name": "internal/dload", - "version": "1.4.1", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/php-internal/dload.git", - "reference": "a2e909878c3a65d1903a3f38d9270b0cb8e28133" + "reference": "9d4230d0c271db8b4e92b8e66e412c91f921def5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-internal/dload/zipball/a2e909878c3a65d1903a3f38d9270b0cb8e28133", - "reference": "a2e909878c3a65d1903a3f38d9270b0cb8e28133", + "url": "https://api.github.com/repos/php-internal/dload/zipball/9d4230d0c271db8b4e92b8e66e412c91f921def5", + "reference": "9d4230d0c271db8b4e92b8e66e412c91f921def5", "shasum": "" }, "require": { "composer/semver": "^3.4", + "internal/destroy": "^1.0", + "internal/toml": "^1.0.2", + "nyholm/psr7": "^1.8", "php": ">=8.1", "psr/container": "1 - 2", + "psr/http-client": "^1.0", "react/async": "^3.2 || ^4.3", "react/promise": "^2.10 || ^3.2", "symfony/console": "^6.4 || ^7", @@ -2190,7 +2250,63 @@ ], "support": { "issues": "https://github.com/php-internal/dload/issues", - "source": "https://github.com/php-internal/dload/tree/1.4.1" + "source": "https://github.com/php-internal/dload/tree/1.7.0" + }, + "funding": [ + { + "url": "https://patreon.com/roxblnfk", + "type": "patreon" + } + ], + "time": "2025-11-11T13:28:07+00:00" + }, + { + "name": "internal/toml", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-internal/toml.git", + "reference": "519d4d1c523249250a2e01a8c63d7f41e5be5d70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-internal/toml/zipball/519d4d1c523249250a2e01a8c63d7f41e5be5d70", + "reference": "519d4d1c523249250a2e01a8c63d7f41e5be5d70", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "buggregator/trap": "^1.13", + "phpunit/phpunit": "^10.5", + "spiral/code-style": "^2.3.0", + "ta-tikoma/phpunit-architecture-test": "^0.8.5", + "vimeo/psalm": "^6.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Internal\\Toml\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" + } + ], + "description": "TOML support for PHP", + "keywords": [ + "toml" + ], + "support": { + "issues": "https://github.com/php-internal/toml/issues", + "source": "https://github.com/php-internal/toml/tree/1.0.3" }, "funding": [ { @@ -2198,7 +2314,7 @@ "type": "patreon" } ], - "time": "2025-06-27T07:44:06+00:00" + "time": "2025-11-17T15:05:00+00:00" }, { "name": "kinde-oss/kinde-auth-php", @@ -2319,16 +2435,16 @@ }, { "name": "league/flysystem", - "version": "3.30.0", + "version": "3.30.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", - "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", "shasum": "" }, "require": { @@ -2396,22 +2512,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" }, - "time": "2025-06-25T13:29:59+00:00" + "time": "2025-11-10T17:13:11+00:00" }, { "name": "league/flysystem-local", - "version": "3.30.0", + "version": "3.30.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10" + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/6691915f77c7fb69adfb87dcd550052dc184ee10", - "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", "shasum": "" }, "require": { @@ -2445,9 +2561,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" }, - "time": "2025-05-21T10:34:19+00:00" + "time": "2025-11-10T11:23:37+00:00" }, { "name": "league/mime-type-detection", @@ -2610,16 +2726,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -2658,7 +2774,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -2666,7 +2782,7 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nesbot/carbon", @@ -2777,27 +2893,27 @@ }, { "name": "nette/php-generator", - "version": "v4.1.8", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "42806049a7774a2bd316c958f5dcf01c6b5c56fa" + "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/42806049a7774a2bd316c958f5dcf01c6b5c56fa", - "reference": "42806049a7774a2bd316c958f5dcf01c6b5c56fa", + "url": "https://api.github.com/repos/nette/php-generator/zipball/4707546a1f11badd72f5d82af4f8a6bc64bd56ac", + "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac", "shasum": "" }, "require": { - "nette/utils": "^3.2.9 || ^4.0", - "php": "8.0 - 8.4" + "nette/utils": "^4.0.6", + "php": "8.1 - 8.5" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.4", - "nikic/php-parser": "^4.18 || ^5.0", - "phpstan/phpstan": "^1.0", + "nikic/php-parser": "^5.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.8" }, "suggest": { @@ -2806,10 +2922,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -2830,7 +2949,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.4 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.", "homepage": "https://nette.org", "keywords": [ "code", @@ -2840,35 +2959,35 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v4.1.8" + "source": "https://github.com/nette/php-generator/tree/v4.2.0" }, - "time": "2025-03-31T00:29:29+00:00" + "time": "2025-08-06T18:24:31+00:00" }, { "name": "nette/utils", - "version": "v4.0.7", + "version": "v4.0.8", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", "shasum": "" }, "require": { - "php": "8.0 - 8.4" + "php": "8.0 - 8.5" }, "conflict": { "nette/finder": "<3", "nette/schema": "<1.2.2" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.5", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -2886,6 +3005,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -2926,9 +3048,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.7" + "source": "https://github.com/nette/utils/tree/v4.0.8" }, - "time": "2025-06-03T04:55:08+00:00" + "time": "2025-08-06T21:43:34+00:00" }, { "name": "nikic/php-parser", @@ -3373,16 +3495,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.2", + "version": "5.6.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + "reference": "90a04bcbf03784066f16038e87e23a0a83cee3c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/90a04bcbf03784066f16038e87e23a0a83cee3c2", + "reference": "90a04bcbf03784066f16038e87e23a0a83cee3c2", "shasum": "" }, "require": { @@ -3431,22 +3553,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.4" }, - "time": "2025-04-13T19:20:35+00:00" + "time": "2025-11-17T21:13:10+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.10.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + "reference": "f626740b38009078de0dc8b2b9dc4e7f749c6eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/f626740b38009078de0dc8b2b9dc4e7f749c6eba", + "reference": "f626740b38009078de0dc8b2b9dc4e7f749c6eba", "shasum": "" }, "require": { @@ -3489,22 +3611,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.11.1" }, - "time": "2024-11-09T15:12:26+00:00" + "time": "2025-11-21T11:31:57+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", "shasum": "" }, "require": { @@ -3512,7 +3634,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { @@ -3554,7 +3676,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" }, "funding": [ { @@ -3566,20 +3688,20 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2025-08-21T11:53:16+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", "shasum": "" }, "require": { @@ -3611,22 +3733,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" }, - "time": "2025-07-13T07:04:09+00:00" + "time": "2025-08-30T15:50:23+00:00" }, { "name": "pimple/pimple", - "version": "v3.5.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/silexphp/Pimple.git", - "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" + "reference": "a70f552d338f9266eec6606c1f0b324da5514c96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", - "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a70f552d338f9266eec6606c1f0b324da5514c96", + "reference": "a70f552d338f9266eec6606c1f0b324da5514c96", "shasum": "" }, "require": { @@ -3634,7 +3756,7 @@ "psr/container": "^1.1 || ^2.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4@dev" + "phpunit/phpunit": "*" }, "type": "library", "extra": { @@ -3664,9 +3786,9 @@ "dependency injection" ], "support": { - "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" + "source": "https://github.com/silexphp/Pimple/tree/v3.6.0" }, - "time": "2021-10-28T11:13:42+00:00" + "time": "2025-11-12T12:31:38+00:00" }, { "name": "psr-discovery/all", @@ -4954,20 +5076,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.0", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -5026,9 +5148,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.0" + "source": "https://github.com/ramsey/uuid/tree/4.9.1" }, - "time": "2025-06-25T14:20:11+00:00" + "time": "2025-09-04T20:59:21+00:00" }, { "name": "react/async", @@ -5107,16 +5229,16 @@ }, { "name": "react/event-loop", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", "shasum": "" }, "require": { @@ -5167,7 +5289,7 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" }, "funding": [ { @@ -5175,27 +5297,27 @@ "type": "open_collective" } ], - "time": "2023-11-13T13:48:05+00:00" + "time": "2025-11-17T20:46:25+00:00" }, { "name": "react/promise", - "version": "v3.2.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", @@ -5240,7 +5362,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { @@ -5248,7 +5370,7 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { "name": "roadrunner-php/app-logger", @@ -5469,20 +5591,20 @@ }, { "name": "roadrunner-php/roadrunner-api-dto", - "version": "v1.12.0", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/roadrunner-php/roadrunner-api-dto.git", - "reference": "45f5726c2a55e293c6604a233212b6394ef36e2f" + "reference": "e6efb759f0a73b8516b7f28317230ecd4010005e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roadrunner-php/roadrunner-api-dto/zipball/45f5726c2a55e293c6604a233212b6394ef36e2f", - "reference": "45f5726c2a55e293c6604a233212b6394ef36e2f", + "url": "https://api.github.com/repos/roadrunner-php/roadrunner-api-dto/zipball/e6efb759f0a73b8516b7f28317230ecd4010005e", + "reference": "e6efb759f0a73b8516b7f28317230ecd4010005e", "shasum": "" }, "require": { - "google/protobuf": "^3.22 || ^4.0", + "google/protobuf": "^4.31.1", "php": "^8.1" }, "conflict": { @@ -5524,7 +5646,7 @@ "docs": "https://docs.roadrunner.dev", "forum": "https://forum.roadrunner.dev", "issues": "https://github.com/roadrunner-server/roadrunner/issues", - "source": "https://github.com/roadrunner-php/roadrunner-api-dto/tree/v1.12.0" + "source": "https://github.com/roadrunner-php/roadrunner-api-dto/tree/v1.14.0" }, "funding": [ { @@ -5532,7 +5654,7 @@ "type": "github" } ], - "time": "2025-05-05T14:38:45+00:00" + "time": "2025-11-06T13:03:11+00:00" }, { "name": "spiral-packages/cqrs", @@ -5947,25 +6069,27 @@ }, { "name": "spiral/data-grid", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/spiral/data-grid.git", - "reference": "dde45cec1a42802f84da191df6a67b11f7f30e3f" + "reference": "6718350c8f8d49483442f04f50f293c069f5fc78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/data-grid/zipball/dde45cec1a42802f84da191df6a67b11f7f30e3f", - "reference": "dde45cec1a42802f84da191df6a67b11f7f30e3f", + "url": "https://api.github.com/repos/spiral/data-grid/zipball/6718350c8f8d49483442f04f50f293c069f5fc78", + "reference": "6718350c8f8d49483442f04f50f293c069f5fc78", "shasum": "" }, "require": { "php": ">=8.1", - "spiral/attributes": "^2.10 || ^3.0" + "spiral/attributes": "^3.0" }, "require-dev": { "phpunit/phpunit": "^9.5.20", "ramsey/uuid": "^4.2.3", + "rector/rector": "^2.1.2", + "spiral/code-style": "^2.2.2", "vimeo/psalm": "^4.27" }, "type": "library", @@ -6002,7 +6126,7 @@ "issues": "https://github.com/spiral/framework/issues", "source": "https://github.com/spiral/data-grid" }, - "time": "2022-09-14T18:34:05+00:00" + "time": "2025-07-27T09:23:44+00:00" }, { "name": "spiral/data-grid-bridge", @@ -6631,16 +6755,16 @@ }, { "name": "spiral/roadrunner-http", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/roadrunner-php/http.git", - "reference": "c00ab7afd289df7a6b49f9ef07ce57dcb8020df1" + "reference": "a44a5f7d54d4ee8a14fe99cd22dcd128db270c88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roadrunner-php/http/zipball/c00ab7afd289df7a6b49f9ef07ce57dcb8020df1", - "reference": "c00ab7afd289df7a6b49f9ef07ce57dcb8020df1", + "url": "https://api.github.com/repos/roadrunner-php/http/zipball/a44a5f7d54d4ee8a14fe99cd22dcd128db270c88", + "reference": "a44a5f7d54d4ee8a14fe99cd22dcd128db270c88", "shasum": "" }, "require": { @@ -6656,9 +6780,11 @@ "require-dev": { "jetbrains/phpstorm-attributes": "^1.0", "nyholm/psr7": "^1.3", - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^10.5", + "spiral/code-style": "^2.3", + "spiral/dumper": "^3.3", "symfony/process": "^6.2 || ^7.0", - "vimeo/psalm": "^5.9" + "vimeo/psalm": "^6.13" }, "suggest": { "ext-protobuf": "Provides Protocol Buffers support. Without it, performance will be lower.", @@ -6707,7 +6833,7 @@ "docs": "https://docs.roadrunner.dev", "forum": "https://forum.roadrunner.dev/", "issues": "https://github.com/roadrunner-server/roadrunner/issues", - "source": "https://github.com/roadrunner-php/http/tree/v3.5.2" + "source": "https://github.com/roadrunner-php/http/tree/v3.6.0" }, "funding": [ { @@ -6715,7 +6841,7 @@ "type": "github" } ], - "time": "2025-05-13T09:40:10+00:00" + "time": "2025-08-31T12:42:23+00:00" }, { "name": "spiral/roadrunner-jobs", @@ -7128,16 +7254,16 @@ }, { "name": "spiral/validator", - "version": "1.5.4", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/spiral/validator.git", - "reference": "1588b3d005d2529af21f308f885d8a5a717c8813" + "reference": "93835961bff292a9afef0dd266de3d33b8aac4d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/validator/zipball/1588b3d005d2529af21f308f885d8a5a717c8813", - "reference": "1588b3d005d2529af21f308f885d8a5a717c8813", + "url": "https://api.github.com/repos/spiral/validator/zipball/93835961bff292a9afef0dd266de3d33b8aac4d1", + "reference": "93835961bff292a9afef0dd266de3d33b8aac4d1", "shasum": "" }, "require": { @@ -7184,7 +7310,7 @@ "type": "github" } ], - "time": "2025-01-24T10:27:42+00:00" + "time": "2025-10-24T09:41:39+00:00" }, { "name": "symfony/clock", @@ -7262,16 +7388,16 @@ }, { "name": "symfony/console", - "version": "v7.3.1", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", "shasum": "" }, "require": { @@ -7336,7 +7462,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.1" + "source": "https://github.com/symfony/console/tree/v7.3.6" }, "funding": [ { @@ -7347,12 +7473,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-11-04T01:21:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -7423,16 +7553,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { @@ -7483,7 +7613,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -7494,12 +7624,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-22T09:11:45+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -7579,16 +7713,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -7623,7 +7757,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -7634,25 +7768,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/http-client", - "version": "v7.3.1", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64" + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64", + "url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", "shasum": "" }, "require": { @@ -7660,6 +7798,7 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -7718,7 +7857,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.1" + "source": "https://github.com/symfony/http-client/tree/v7.3.6" }, "funding": [ { @@ -7729,12 +7868,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T07:58:39+00:00" + "time": "2025-11-05T17:41:46+00:00" }, { "name": "symfony/http-client-contracts", @@ -7896,16 +8039,16 @@ }, { "name": "symfony/messenger", - "version": "v6.4.23", + "version": "v6.4.28", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "862d99460cd12a1e6cc2ef928b06ec4bae47d39f" + "reference": "6a758210a2392c4a326de84a6c8c70e7e18296f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/862d99460cd12a1e6cc2ef928b06ec4bae47d39f", - "reference": "862d99460cd12a1e6cc2ef928b06ec4bae47d39f", + "url": "https://api.github.com/repos/symfony/messenger/zipball/6a758210a2392c4a326de84a6c8c70e7e18296f9", + "reference": "6a758210a2392c4a326de84a6c8c70e7e18296f9", "shasum": "" }, "require": { @@ -7963,7 +8106,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v6.4.23" + "source": "https://github.com/symfony/messenger/tree/v6.4.28" }, "funding": [ { @@ -7974,25 +8117,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-26T21:24:02+00:00" + "time": "2025-11-06T11:12:43+00:00" }, { "name": "symfony/mime", - "version": "v6.4.21", + "version": "v6.4.26", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "fec8aa5231f3904754955fad33c2db50594d22d1" + "reference": "61ab9681cdfe315071eb4fa79b6ad6ab030a9235" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/fec8aa5231f3904754955fad33c2db50594d22d1", - "reference": "fec8aa5231f3904754955fad33c2db50594d22d1", + "url": "https://api.github.com/repos/symfony/mime/zipball/61ab9681cdfe315071eb4fa79b6ad6ab030a9235", + "reference": "61ab9681cdfe315071eb4fa79b6ad6ab030a9235", "shasum": "" }, "require": { @@ -8048,7 +8195,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.21" + "source": "https://github.com/symfony/mime/tree/v6.4.26" }, "funding": [ { @@ -8059,16 +8206,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-27T13:27:38+00:00" + "time": "2025-09-16T08:22:30+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -8127,7 +8278,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -8138,6 +8289,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8147,7 +8302,7 @@ }, { "name": "symfony/polyfill-iconv", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", @@ -8207,7 +8362,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.33.0" }, "funding": [ { @@ -8218,6 +8373,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8227,16 +8386,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -8285,7 +8444,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -8296,16 +8455,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -8368,7 +8531,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -8379,6 +8542,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8388,7 +8555,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -8449,7 +8616,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -8460,6 +8627,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8469,7 +8640,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -8530,7 +8701,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -8541,6 +8712,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8550,7 +8725,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -8610,7 +8785,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -8621,6 +8796,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8630,16 +8809,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -8686,7 +8865,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -8697,25 +8876,109 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" }, { "name": "symfony/property-access", - "version": "v7.3.1", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "518d15c8cca726ebe665dcd7154074584cf862e8" + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/518d15c8cca726ebe665dcd7154074584cf862e8", - "reference": "518d15c8cca726ebe665dcd7154074584cf862e8", + "url": "https://api.github.com/repos/symfony/property-access/zipball/4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", "shasum": "" }, "require": { @@ -8762,7 +9025,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.3.1" + "source": "https://github.com/symfony/property-access/tree/v7.3.3" }, "funding": [ { @@ -8773,32 +9036,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-24T04:04:43+00:00" + "time": "2025-08-04T15:15:28+00:00" }, { "name": "symfony/property-info", - "version": "v7.3.1", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970" + "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/90586acbf2a6dd13bee4f09f09111c8bd4773970", - "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970", + "url": "https://api.github.com/repos/symfony/property-info/zipball/0b346ed259dc5da43535caf243005fe7d4b0f051", + "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/string": "^6.4|^7.0", - "symfony/type-info": "~7.2.8|^7.3.1" + "symfony/type-info": "^7.3.5" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", @@ -8848,7 +9115,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.3.1" + "source": "https://github.com/symfony/property-info/tree/v7.3.5" }, "funding": [ { @@ -8859,31 +9126,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-10-05T22:12:41+00:00" }, { "name": "symfony/serializer", - "version": "v7.3.1", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "feaf837cedbbc8287986602223175d3fd639922d" + "reference": "ba2e50a5f2870c93f0f47ca1a4e56e4bbe274035" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/feaf837cedbbc8287986602223175d3fd639922d", - "reference": "feaf837cedbbc8287986602223175d3fd639922d", + "url": "https://api.github.com/repos/symfony/serializer/zipball/ba2e50a5f2870c93f0f47ca1a4e56e4bbe274035", + "reference": "ba2e50a5f2870c93f0f47ca1a4e56e4bbe274035", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", @@ -8913,7 +9185,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.8", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -8946,7 +9218,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.3.1" + "source": "https://github.com/symfony/serializer/tree/v7.3.5" }, "funding": [ { @@ -8957,25 +9229,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-10-08T11:26:21+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -9029,7 +9305,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -9040,25 +9316,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -9073,7 +9353,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -9116,7 +9395,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -9127,25 +9406,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "symfony/translation", - "version": "v6.4.23", + "version": "v6.4.26", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643" + "reference": "c8559fe25c7ee7aa9d28f228903a46db008156a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/de8afa521e04a5220e9e58a1dc99971ab7cac643", - "reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643", + "url": "https://api.github.com/repos/symfony/translation/zipball/c8559fe25c7ee7aa9d28f228903a46db008156a4", + "reference": "c8559fe25c7ee7aa9d28f228903a46db008156a4", "shasum": "" }, "require": { @@ -9211,7 +9494,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.23" + "source": "https://github.com/symfony/translation/tree/v6.4.26" }, "funding": [ { @@ -9222,25 +9505,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-26T21:24:02+00:00" + "time": "2025-09-05T18:17:25+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -9289,7 +9576,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -9300,25 +9587,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/type-info", - "version": "v7.3.1", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "5fa6e25e4195e73ce9e457b521ac5e61ec271150" + "reference": "8b36f41421160db56914f897b57eaa6a830758b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/5fa6e25e4195e73ce9e457b521ac5e61ec271150", - "reference": "5fa6e25e4195e73ce9e457b521ac5e61ec271150", + "url": "https://api.github.com/repos/symfony/type-info/zipball/8b36f41421160db56914f897b57eaa6a830758b3", + "reference": "8b36f41421160db56914f897b57eaa6a830758b3", "shasum": "" }, "require": { @@ -9368,7 +9659,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.3.1" + "source": "https://github.com/symfony/type-info/tree/v7.3.5" }, "funding": [ { @@ -9379,43 +9670,45 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-10-16T12:30:12+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.23", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "d55b1834cdbfcc31bc2cd7e095ba5ed9a88f6600" + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d55b1834cdbfcc31bc2cd7e095ba5ed9a88f6600", - "reference": "d55b1834cdbfcc31bc2cd7e095ba5ed9a88f6600", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -9453,7 +9746,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.23" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" }, "funding": [ { @@ -9464,25 +9757,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T15:05:27+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.1", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", + "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", "shasum": "" }, "require": { @@ -9525,7 +9822,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.1" + "source": "https://github.com/symfony/yaml/tree/v7.3.5" }, "funding": [ { @@ -9536,12 +9833,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-03T06:57:57+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "vlucas/phpdotenv", @@ -9629,28 +9930,28 @@ }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -9681,9 +9982,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-29T15:56:20+00:00" }, { "name": "yiisoft/friendly-exception", @@ -10169,16 +10470,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.4", + "version": "v2.6.5", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "url": "https://api.github.com/repos/amphp/amp/zipball/d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", "shasum": "" }, "require": { @@ -10194,11 +10495,6 @@ "vimeo/psalm": "^3.12" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php", @@ -10246,7 +10542,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.4" + "source": "https://github.com/amphp/amp/tree/v2.6.5" }, "funding": [ { @@ -10254,7 +10550,7 @@ "type": "github" } ], - "time": "2024-03-21T18:52:26+00:00" + "time": "2025-09-03T19:41:28+00:00" }, { "name": "amphp/byte-stream", @@ -10327,6 +10623,108 @@ ], "time": "2024-04-13T18:00:56+00:00" }, + { + "name": "buggregator/trap", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/buggregator/trap.git", + "reference": "f640520f4c1e5a3ad4325e77e15476028d224154" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/buggregator/trap/zipball/f640520f4c1e5a3ad4325e77e15476028d224154", + "reference": "f640520f4c1e5a3ad4325e77e15476028d224154", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.6", + "ext-filter": "*", + "ext-sockets": "*", + "internal/destroy": "^1.0", + "nyholm/psr7": "^1.8", + "php": ">=8.1", + "php-http/message": "^1.15", + "psr/container": "^1.1 || ^2.0", + "psr/http-message": "^1.1 || ^2", + "symfony/console": "^6.4 || ^7", + "symfony/var-dumper": "^6.3 || ^7", + "yiisoft/injector": "^1.2" + }, + "require-dev": { + "dereuromark/composer-prefer-lowest": "^0.1.10", + "ergebnis/phpunit-slow-test-detector": "^2.14", + "google/protobuf": "^3.25 || ^4.30", + "phpunit/phpunit": "^10.5.10", + "rector/rector": "^1.1", + "roxblnfk/unpoly": "^1.8.1", + "spiral/code-style": "^2.2.2", + "ta-tikoma/phpunit-architecture-test": "^0.8.4", + "vimeo/psalm": "^6.5" + }, + "suggest": { + "ext-simplexml": "To load trap.xml", + "roxblnfk/unpoly": "If you want to remove unnecessary PHP polyfills depend on PHP version." + }, + "bin": [ + "bin/trap" + ], + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Buggregator\\Trap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" + }, + { + "name": "Pavel Buchnev (butschster)", + "homepage": "https://github.com/butschster" + } + ], + "description": "A simple and powerful tool for debugging PHP applications.", + "homepage": "https://buggregator.dev/", + "keywords": [ + "Fibers", + "WebSockets", + "binary dump", + "cli", + "console", + "debug", + "dev", + "dump", + "dumper", + "helper", + "sentry", + "server", + "smtp" + ], + "support": { + "issues": "https://github.com/buggregator/trap/issues", + "source": "https://github.com/buggregator/trap/tree/1.14.0" + }, + "funding": [ + { + "url": "https://boosty.to/roxblnfk", + "type": "boosty" + }, + { + "url": "https://patreon.com/roxblnfk", + "type": "patreon" + } + ], + "time": "2025-11-20T15:33:37+00:00" + }, { "name": "butschster/entity-faker", "version": "v2.0.0", @@ -10837,16 +11235,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -10856,10 +11254,10 @@ "fidry/makefile": "^0.2.0", "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -10886,7 +11284,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -10894,63 +11292,61 @@ "type": "github" } ], - "time": "2024-08-06T10:04:20+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.84.0", + "version": "v3.90.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "38dad0767bf2a9b516b976852200ae722fe984ca" + "reference": "ad732c2e9299c9743f9c55ae53cc0e7642ab1155" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca", - "reference": "38dad0767bf2a9b516b976852200ae722fe984ca", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/ad732c2e9299c9743f9c55ae53cc0e7642ab1155", + "reference": "ad732c2e9299c9743f9c55ae53cc0e7642ab1155", "shasum": "" }, "require": { - "clue/ndjson-react": "^1.0", + "clue/ndjson-react": "^1.3", "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.5", "ext-filter": "*", "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.2", + "fidry/cpu-core-counter": "^1.3", "php": "^7.4 || ^8.0", "react/child-process": "^0.6.6", - "react/event-loop": "^1.0", - "react/promise": "^2.11 || ^3.0", - "react/socket": "^1.0", - "react/stream": "^1.0", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", - "symfony/console": "^5.4.45 || ^6.4.13 || ^7.0", - "symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0", - "symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0", - "symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0", - "symfony/options-resolver": "^5.4.45 || ^6.4.16 || ^7.0", - "symfony/polyfill-mbstring": "^1.32", - "symfony/polyfill-php80": "^1.32", - "symfony/polyfill-php81": "^1.32", - "symfony/process": "^5.4.47 || ^6.4.20 || ^7.2", - "symfony/stopwatch": "^5.4.45 || ^6.4.19 || ^7.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.6", - "infection/infection": "^0.29.14", - "justinrainbow/json-schema": "^5.3 || ^6.4", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.7", + "infection/infection": "^0.31.0", + "justinrainbow/json-schema": "^6.5", "keradus/cli-executor": "^2.2", "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.8", - "php-cs-fixer/accessible-object": "^1.1", + "php-coveralls/php-coveralls": "^2.9", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.23 || ^10.5.47 || ^11.5.25", - "symfony/polyfill-php84": "^1.32", - "symfony/var-dumper": "^5.4.48 || ^6.4.23 || ^7.3.1", - "symfony/yaml": "^5.4.45 || ^6.4.23 || ^7.3.1" + "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", + "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2 || ^8.0", + "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2 || ^8.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -10991,7 +11387,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.90.0" }, "funding": [ { @@ -10999,7 +11395,7 @@ "type": "github" } ], - "time": "2025-07-15T18:21:57+00:00" + "time": "2025-11-20T15:15:16+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -11114,21 +11510,21 @@ }, { "name": "laminas/laminas-hydrator", - "version": "4.16.0", + "version": "4.17.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-hydrator.git", - "reference": "a162bd571924968d67ef1f43aed044b8f9c108ef" + "reference": "626c5e446fdfa27865dfe3cd29123108408c2555" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/a162bd571924968d67ef1f43aed044b8f9c108ef", - "reference": "a162bd571924968d67ef1f43aed044b8f9c108ef", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/626c5e446fdfa27865dfe3cd29123108408c2555", + "reference": "626c5e446fdfa27865dfe3cd29123108408c2555", "shasum": "" }, "require": { "laminas/laminas-stdlib": "^3.20", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "webmozart/assert": "^1.11" }, "conflict": { @@ -11140,11 +11536,11 @@ "laminas/laminas-eventmanager": "^3.13.1", "laminas/laminas-modulemanager": "^2.16.0", "laminas/laminas-serializer": "^2.17.0", - "laminas/laminas-servicemanager": "^3.23.0", + "laminas/laminas-servicemanager": "^3.24.0", "phpbench/phpbench": "^1.3.1", - "phpunit/phpunit": "^10.5.38", + "phpunit/phpunit": "^11.5.42", "psalm/plugin-phpunit": "^0.19.0", - "vimeo/psalm": "^5.26.1" + "vimeo/psalm": "^6.13.1" }, "suggest": { "laminas/laminas-eventmanager": "^3.13, to support aggregate hydrator usage", @@ -11187,34 +11583,34 @@ "type": "community_bridge" } ], - "time": "2024-11-13T14:04:02+00:00" + "time": "2025-11-06T11:05:29+00:00" }, { "name": "laminas/laminas-stdlib", - "version": "3.20.0", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4" + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/8974a1213be42c3e2f70b2c27b17f910291ab2f4", - "reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/b1c81514cfe158aadf724c42b34d3d0a8164c096", + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "conflict": { "zendframework/zend-stdlib": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "^3.0", - "phpbench/phpbench": "^1.3.1", - "phpunit/phpunit": "^10.5.38", - "psalm/plugin-phpunit": "^0.19.0", - "vimeo/psalm": "^5.26.1" + "laminas/laminas-coding-standard": "^3.1.0", + "phpbench/phpbench": "^1.4.1", + "phpunit/phpunit": "^11.5.42", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13.1" }, "type": "library", "autoload": { @@ -11246,7 +11642,7 @@ "type": "community_bridge" } ], - "time": "2024-10-29T13:46:07+00:00" + "time": "2025-10-11T18:13:12+00:00" }, { "name": "mockery/mockery", @@ -11502,16 +11898,11 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.28", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9" - }, + "version": "1.12.32", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", - "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", + "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", "shasum": "" }, "require": { @@ -11556,7 +11947,7 @@ "type": "github" } ], - "time": "2025-07-17T17:15:39+00:00" + "time": "2025-09-30T10:16:31+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11881,16 +12272,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.48", + "version": "10.5.58", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541" + "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e0a2bc39f6fae7617989d690d76c48e6d2eb541", - "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca", + "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca", "shasum": "" }, "require": { @@ -11900,7 +12291,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.3", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -11911,13 +12302,13 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", + "sebastian/comparator": "^5.0.4", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.2", + "sebastian/exporter": "^5.1.4", "sebastian/global-state": "^6.0.2", "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", "sebastian/type": "^4.0.0", "sebastian/version": "^4.0.1" }, @@ -11962,7 +12353,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.48" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58" }, "funding": [ { @@ -11986,7 +12377,7 @@ "type": "tidelift" } ], - "time": "2025-07-11T04:07:17+00:00" + "time": "2025-09-28T12:04:46+00:00" }, { "name": "qossmic/deptrac-shim", @@ -12193,16 +12584,16 @@ }, { "name": "react/dns", - "version": "v1.13.0", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", "shasum": "" }, "require": { @@ -12257,7 +12648,7 @@ ], "support": { "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" + "source": "https://github.com/reactphp/dns/tree/v1.14.0" }, "funding": [ { @@ -12265,20 +12656,20 @@ "type": "open_collective" } ], - "time": "2024-06-13T14:18:03+00:00" + "time": "2025-11-18T19:34:28+00:00" }, { "name": "react/socket", - "version": "v1.16.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", "shasum": "" }, "require": { @@ -12337,7 +12728,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.16.0" + "source": "https://github.com/reactphp/socket/tree/v1.17.0" }, "funding": [ { @@ -12345,7 +12736,7 @@ "type": "open_collective" } ], - "time": "2024-07-26T10:38:09+00:00" + "time": "2025-11-19T20:47:34+00:00" }, { "name": "react/stream", @@ -12654,16 +13045,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.3", + "version": "5.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", "shasum": "" }, "require": { @@ -12719,15 +13110,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2024-10-18T14:56:07+00:00" + "time": "2025-09-07T05:25:07+00:00" }, { "name": "sebastian/complexity", @@ -12920,16 +13323,16 @@ }, { "name": "sebastian/exporter", - "version": "5.1.2", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + "reference": "0735b90f4da94969541dac1da743446e276defa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", "shasum": "" }, "require": { @@ -12938,7 +13341,7 @@ "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -12986,15 +13389,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T07:17:12+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { "name": "sebastian/global-state", @@ -13230,23 +13645,23 @@ }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -13281,15 +13696,28 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { "name": "sebastian/type", @@ -13402,16 +13830,16 @@ }, { "name": "sentry/sentry", - "version": "4.14.1", + "version": "4.18.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda" + "reference": "04dcf20b39742b731b676f8b8d4f02d1db488af8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/a28c4a6f5fda2bf730789a638501d7a737a64eda", - "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/04dcf20b39742b731b676f8b8d4f02d1db488af8", + "reference": "04dcf20b39742b731b676f8b8d4f02d1db488af8", "shasum": "" }, "require": { @@ -13422,7 +13850,7 @@ "jean85/pretty-package-versions": "^1.5|^2.0.4", "php": "^7.2|^8.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0|^8.0" }, "conflict": { "raven/raven": "*" @@ -13435,7 +13863,6 @@ "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^8.5|^9.6", - "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", "vimeo/psalm": "^4.17" }, "suggest": { @@ -13475,7 +13902,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.14.1" + "source": "https://github.com/getsentry/sentry-php/tree/4.18.1" }, "funding": [ { @@ -13487,20 +13914,20 @@ "type": "custom" } ], - "time": "2025-06-23T15:25:52+00:00" + "time": "2025-11-11T09:34:53+00:00" }, { "name": "spatie/array-to-xml", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67" + "reference": "6a740f39415aee8886aea10333403adc77d50791" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/6a740f39415aee8886aea10333403adc77d50791", + "reference": "6a740f39415aee8886aea10333403adc77d50791", "shasum": "" }, "require": { @@ -13543,7 +13970,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.4.0" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.1" }, "funding": [ { @@ -13555,20 +13982,20 @@ "type": "github" } ], - "time": "2024-12-16T12:45:15+00:00" + "time": "2025-11-12T10:32:50+00:00" }, { "name": "spatie/backtrace", - "version": "1.7.4", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe" + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/cd37a49fce7137359ac30ecc44ef3e16404cccbe", - "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", "shasum": "" }, "require": { @@ -13606,7 +14033,8 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.7.4" + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.8.1" }, "funding": [ { @@ -13618,7 +14046,7 @@ "type": "other" } ], - "time": "2025-05-08T15:41:09+00:00" + "time": "2025-08-26T08:22:30+00:00" }, { "name": "spatie/macroable", @@ -13672,35 +14100,35 @@ }, { "name": "spatie/ray", - "version": "1.42.0", + "version": "1.44.1", "source": { "type": "git", "url": "https://github.com/spatie/ray.git", - "reference": "152250ce7c490bf830349fa30ba5200084e95860" + "reference": "588e201dda9bd94ce27af365f5a734b1de706a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ray/zipball/152250ce7c490bf830349fa30ba5200084e95860", - "reference": "152250ce7c490bf830349fa30ba5200084e95860", + "url": "https://api.github.com/repos/spatie/ray/zipball/588e201dda9bd94ce27af365f5a734b1de706a81", + "reference": "588e201dda9bd94ce27af365f5a734b1de706a81", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", - "php": "^7.4 || ^8.0", - "ramsey/uuid": "^3.0 || ^4.1", + "php": "^7.4|^8.0", + "ramsey/uuid": "^3.0|^4.1", "spatie/backtrace": "^1.7.1", - "spatie/macroable": "^1.0 || ^2.0", - "symfony/stopwatch": "^4.2 || ^5.1 || ^6.0 || ^7.0", - "symfony/var-dumper": "^4.2 || ^5.1 || ^6.0 || ^7.0.3" + "spatie/macroable": "^1.0|^2.0", + "symfony/stopwatch": "^4.2|^5.1|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3|^8.0" }, "require-dev": { - "illuminate/support": "^7.20 || ^8.18 || ^9.0 || ^10.0 || ^11.0 || ^12.0", - "nesbot/carbon": "^2.63 || ^3.8.4", + "illuminate/support": "^7.20|^8.18|^9.0|^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.63|^3.8.4", "pestphp/pest": "^1.22", - "phpstan/phpstan": "^1.10.57 || ^2.0.3", + "phpstan/phpstan": "^1.10.57|^2.0.3", "phpunit/phpunit": "^9.5", - "rector/rector": "^0.19.2 || ^1.0.1 || ^2.0.0", + "rector/rector": "^0.19.2|^1.0.1|^2.0.0", "spatie/phpunit-snapshot-assertions": "^4.2", "spatie/test-time": "^1.2" }, @@ -13741,7 +14169,7 @@ ], "support": { "issues": "https://github.com/spatie/ray/issues", - "source": "https://github.com/spatie/ray/tree/1.42.0" + "source": "https://github.com/spatie/ray/tree/1.44.1" }, "funding": [ { @@ -13753,7 +14181,7 @@ "type": "other" } ], - "time": "2025-04-18T08:17:40+00:00" + "time": "2025-11-21T10:44:03+00:00" }, { "name": "spiral-packages/database-seeder", @@ -14013,16 +14441,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", "shasum": "" }, "require": { @@ -14059,7 +14487,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.6" }, "funding": [ { @@ -14070,25 +14498,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2025-11-05T09:52:27+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", "shasum": "" }, "require": { @@ -14126,7 +14558,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" }, "funding": [ { @@ -14137,16 +14569,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T13:12:05+00:00" + "time": "2025-08-05T10:16:07+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -14202,7 +14638,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -14213,6 +14649,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -14222,16 +14662,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -14263,7 +14703,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -14274,12 +14714,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/stopwatch", @@ -14345,16 +14789,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -14383,7 +14827,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -14391,7 +14835,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "vimeo/psalm", @@ -14515,6 +14959,6 @@ "php": ">=8.2", "ext-mbstring": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/docker-compose.yaml b/docker-compose.yaml index b467dbf2..5f32c782 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -25,20 +25,24 @@ services: - buggregator-network buggregator-server: - build: "./.docker" - ports: - - 1025:1025 - - 9912:9912 - - 9913:9913 + build: + dockerfile: Dockerfile + context: .docker + args: + GH_TOKEN: "${GH_TOKEN}" environment: - # RR_LOG_LEVEL: debug +# RR_LOG_LEVEL: debug + # RR_LOG_TCP_LEVEL: debug + # RR_LOG_JOBS_LEVEL: debug + # RR_LOG_SERVER_LEVEL: debug + MONOLOG_DEFAULT_CHANNEL: roadrunner PERSISTENCE_DRIVER: db - # DB_LOGGER: roadrunner DB_USERNAME: homestead DB_PASSWORD: secret DB_DRIVER: pgsql DB_HOST: buggregator-pgsql + # DB_LOGGER: roadrunner # Auth AUTH_ENABLED: false @@ -104,6 +108,7 @@ services: INSPECTOR_URL: http://inspector@buggregator-server:8000 INSPECTOR_API_KEY: test PROFILER_ENDPOINT: http://profiler@buggregator-server:8000 + HTTP_DUMP_ENDPOINT: http://http-dump@buggregator-server:8000 labels: - "traefik.enable=true" - "traefik.http.routers.buggregator-examples.entrypoints=web" From 733b734d32cb2782b800237aec39d2f259bbf602 Mon Sep 17 00:00:00 2001 From: butschster Date: Sun, 23 Nov 2025 11:04:21 +0400 Subject: [PATCH 2/4] Removes SMTP tests --- .../Feature/Interfaces/TCP/Smtp/EmailTest.php | 408 ------------------ .../Interfaces/TCP/Smtp/MessageTest.php | 83 ---- .../Interfaces/TCP/Smtp/ServiceTest.php | 202 --------- tests/Feature/Interfaces/TCP/Smtp/hello.txt | 1 - tests/Feature/Interfaces/TCP/Smtp/logo.svg | 1 - tests/Feature/Interfaces/TCP/Smtp/sample.pdf | Bin 61752 -> 0 bytes .../Mail/InlineAttachmentParsingTest.php | 287 ------------ 7 files changed, 982 deletions(-) delete mode 100644 tests/Feature/Interfaces/TCP/Smtp/EmailTest.php delete mode 100644 tests/Feature/Interfaces/TCP/Smtp/MessageTest.php delete mode 100644 tests/Feature/Interfaces/TCP/Smtp/ServiceTest.php delete mode 100644 tests/Feature/Interfaces/TCP/Smtp/hello.txt delete mode 100644 tests/Feature/Interfaces/TCP/Smtp/logo.svg delete mode 100644 tests/Feature/Interfaces/TCP/Smtp/sample.pdf delete mode 100644 tests/Feature/Modules/Smtp/Application/Mail/InlineAttachmentParsingTest.php diff --git a/tests/Feature/Interfaces/TCP/Smtp/EmailTest.php b/tests/Feature/Interfaces/TCP/Smtp/EmailTest.php deleted file mode 100644 index 503fc655..00000000 --- a/tests/Feature/Interfaces/TCP/Smtp/EmailTest.php +++ /dev/null @@ -1,408 +0,0 @@ -parser = $this->get(Parser::class); - $this->bucket = $this->fakeStorage()->bucket('smtp'); - $this->attachments = $this->mockContainer(AttachmentRepositoryInterface::class); - } - - public function testSendEmail(): void - { - $project = $this->createProject('foo'); - - $email = $this->buildEmail(); - $email->getHeaders()->addIdHeader('Message-ID', $id = $email->generateMessageId()); - - $client = $this->buildSmtpClient( - username: (string) $project->getKey(), - uuid: $connectionUuid = Uuid::uuid7(), - ); - - // Assert logo-embeddable is persisted to a database - $this->attachments - ->shouldReceive('store') - ->once() - ->with( - \Mockery::on(function (Attachment $attachment) { - $this->assertSame('logo-embeddable', $attachment->getFilename()); - $this->assertTrue(\in_array($attachment->getSize(), [ - 1207, // Size can vary based on SVG content - 1206, // Some systems might add a BOM or similar - ])); - $this->assertSame('image/svg+xml', $attachment->getMime()); - - // Check attachments storage - $this->bucket->assertCreated($attachment->getPath()); - return true; - }), - ); - - // Assert hello.txt is persisted to a database - $this->attachments - ->shouldReceive('store') - ->once() - ->with( - \Mockery::on(function (Attachment $attachment) { - $this->assertSame('hello.txt', $attachment->getFilename()); - $this->assertSame(13, $attachment->getSize()); - $this->assertSame('text/plain', $attachment->getMime()); - - // Check attachments storage - $this->bucket->assertCreated($attachment->getPath()); - return true; - }), - ); - - // Assert sample.pdf is persisted to a database - $this->attachments - ->shouldReceive('store') - ->once() - ->with( - \Mockery::on(function (Attachment $attachment) { - $this->assertSame('sample.pdf', $attachment->getFilename()); - $this->assertSame(61752, $attachment->getSize()); - $this->assertSame('application/pdf', $attachment->getMime()); - - // Check attachments storage - $this->bucket->assertCreated($attachment->getPath()); - return true; - }), - ); - - // Assert logo.svg is persisted to a database - $this->attachments - ->shouldReceive('store') - ->once() - ->with( - \Mockery::on(function (Attachment $attachment) { - $this->assertSame('logo.svg', $attachment->getFilename()); - $this->assertTrue(\in_array($attachment->getSize(), [ - 1207, // Size can vary based on SVG content - 1206, // Some systems might add a BOM or similar - ])); - $this->assertSame('image/svg+xml', $attachment->getMime()); - - // Check attachments storage - $this->bucket->assertCreated($attachment->getPath()); - return true; - }), - ); - - $sentMessage = $client->send($email); - - $this->validateMessage($id, (string) $connectionUuid); - - $response = $this->handleSmtpRequest(message: '', event: TCPEvent::Close); - $this->assertInstanceOf(CloseConnection::class, $response); - - $this->assertEventPushed($sentMessage, 'foo'); - } - - public function testSendEmailWithInlineAttachmentWithoutFilename(): void - { - $project = $this->createProject('foo'); - - $email = $this->buildEmailWithInlineAttachmentWithoutFilename(); - $email->getHeaders()->addIdHeader('Message-ID', $id = $email->generateMessageId()); - - $client = $this->buildSmtpClient( - username: (string) $project->getKey(), - uuid: $connectionUuid = Uuid::uuid7(), - ); - - // Assert inline attachment with generated filename is persisted - $this->attachments - ->shouldReceive('store') - ->once() - ->with( - \Mockery::on(function (Attachment $attachment) { - // The strategy should generate a filename based on content-id - $this->assertSame('qr_domain.com', $attachment->getFilename()); - $this->assertSame('image/png', $attachment->getMime()); - - // Check attachments storage - $this->bucket->assertCreated($attachment->getPath()); - return true; - }), - ); - - $sentMessage = $client->send($email); - - $this->validateMessageWithInlineAttachment($id, (string) $connectionUuid); - - $response = $this->handleSmtpRequest(message: '', event: TCPEvent::Close); - $this->assertInstanceOf(CloseConnection::class, $response); - - $this->assertEventPushed($sentMessage, 'foo'); - } - - public function testSendMultipleEmails(): void - { - $project = $this->createProject('foo'); - $connectionUuid = Uuid::uuid7(); - - // We'll send two emails in the same SMTP session - $email1 = $this->buildEmail(); - $email1->getHeaders()->addIdHeader('Message-ID', $id1 = $email1->generateMessageId()); - - $email2 = $this->buildEmailWithCyrillic(); - $email2->getHeaders()->addIdHeader('Message-ID', $id2 = $email2->generateMessageId()); - - // Set up attachment expectations (fixed to 7 based on the actual number of attachments) - // The first email has 4 attachments, the second has 3 - $this->attachments->shouldReceive('store')->times(7)->andReturn(true); - - // Build SMTP client - $client = $this->buildSmtpClient( - username: (string) $project->getKey(), - uuid: $connectionUuid, - ); - - // Send first email - $sentMessage1 = $client->send($email1); - - // Validate the first message - $this->validateMessage($id1, (string) $connectionUuid); - $this->assertEventPushed($sentMessage1, 'foo'); - - // Check that state is reset properly by sending second email - $client->send($email2); - - // This would fail before our fix if the state wasn't properly reset - $messageData2 = $this->getEmailMessage((string) $connectionUuid); - $this->assertFalse($messageData2->waitBody, 'waitBody flag should be reset after sending email'); - - $response = $this->handleSmtpRequest(message: '', event: TCPEvent::Close); - $this->assertInstanceOf(CloseConnection::class, $response); - } - - private function getEmailMessage(string $uuid): Message - { - return $this->get(EmailBodyStorage::class)->getMessage($uuid); - } - - private function validateMessage(string $messageId, string $uuid): void - { - $messageData = $this->getEmailMessage($uuid); - - $this->assertSame('foo', $messageData->username); - $this->assertSame('password', $messageData->password); - $this->assertSame('no-reply@site.com', $messageData->from); - $this->assertSame([ - 'alice@example.com', - 'barney@example.com', - 'john@example.com', - 'customer@example.com', - 'theboss@example.com', - ], $messageData->recipients); - - $parsedMessage = $messageData->parse($this->parser); - - $this->assertSame($messageId, $parsedMessage->id); - $this->assertSame( - [ - [ - 'email' => 'no-reply@site.com', - 'name' => 'Bob Example', - ], - ], - $parsedMessage->sender, - ); - $this->assertSame([], $parsedMessage->replyTo); - $this->assertSame('Test message', $parsedMessage->subject); - $this->assertSame([ - [ - 'name' => 'Alice Doe', - 'email' => 'alice@example.com', - ], - [ - 'name' => '', - 'email' => 'barney@example.com', - ], - [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - ], - ], $parsedMessage->recipients); - $this->assertSame([ - [ - 'name' => 'Customer', - 'email' => 'customer@example.com', - ], - [ - 'name' => '', - 'email' => 'theboss@example.com', - ], - ], $parsedMessage->ccs); - - $this->assertSame([], $parsedMessage->getBccs()); - - $this->assertStringEqualsStringIgnoringLineEndings( - <<<'HTML' - - Hello Alice.
This is a test message with 5 header fields and 4 lines in the message body. - HTML - , - $parsedMessage->htmlBody, - ); - - $this->assertStringContainsString( - "Subject: Test message\r -Date: Thu, 02 May 2024 16:01:33 +0000\r -To: Alice Doe , barney@example.com, John Doe\r - \r -Cc: Customer , theboss@example.com\r -From: Bob Example \r -Message-ID: <$messageId>\r", - $parsedMessage->raw, - ); - } - - private function validateMessageWithInlineAttachment(string $messageId, string $uuid): void - { - $messageData = $this->getEmailMessage($uuid); - $parsedMessage = $messageData->parse($this->parser); - - $this->assertSame($messageId, $parsedMessage->id); - $this->assertSame('Test message with inline attachment', $parsedMessage->subject); - - // Verify that the inline attachment was processed correctly - $this->assertCount(1, $parsedMessage->attachments); - $attachment = $parsedMessage->attachments[0]; - $this->assertSame('qr_domain.com', $attachment->getFilename()); - $this->assertSame('image/png', $attachment->getType()); - $this->assertSame('qr@domain.com', $attachment->getContentId()); - } - - private function assertEventPushed(SentMessage $message, ?string $project = null): void - { - $this->broadcastig->assertPushed(new EventsChannel($project), function (array $data) use ($message, $project) { - $this->assertSame('event.received', $data['event']); - $this->assertSame('smtp', $data['data']['type']); - $this->assertSame($project, $data['data']['project']); - - $this->assertSame($message->getMessageId(), $data['data']['uuid']); - $this->assertNotEmpty($data['data']['timestamp']); - - return true; - }); - } - - public function buildEmail(): Email - { - return (new Email()) - ->subject('Test message') - ->date(new \DateTimeImmutable('2024-05-02 16:01:33')) - ->addTo( - new Address('alice@example.com', 'Alice Doe'), - 'barney@example.com', - new Address('john@example.com', 'John Doe'), - ) - ->addCc( - new Address('customer@example.com', 'Customer'), - 'theboss@example.com', - ) - ->addFrom(new Address('no-reply@site.com', 'Bob Example')) - ->attachFromPath(path: __DIR__ . '/hello.txt') - ->attachFromPath(path: __DIR__ . '/sample.pdf') - ->attachFromPath(path: __DIR__ . '/logo.svg') - ->addPart( - (new DataPart(new File(__DIR__ . '/logo.svg'), 'logo-embeddable'))->asInline()->setContentId( - 'test-cid@buggregator', - ), - ) - ->html( - body: <<<'TEXT' - - Hello Alice.
This is a test message with 5 header fields and 4 lines in the message body. - TEXT - , - ); - } - - public function buildEmailWithInlineAttachmentWithoutFilename(): Email - { - // Create a fake PNG content (simple base64 encoded 1x1 pixel PNG) - $pngContent = base64_decode( - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==', - ); - - return (new Email()) - ->subject('Test message with inline attachment') - ->date(new \DateTimeImmutable('2024-05-02 16:01:33')) - ->addTo(new Address('alice@example.com', 'Alice Doe')) - ->addFrom(new Address('no-reply@site.com', 'Bob Example')) - ->addPart( - // Create inline attachment WITHOUT filename - this should trigger the issue - (new DataPart($pngContent, null, 'image/png'))->asInline()->setContentId('qr@domain.com'), - ) - ->html( - body: <<<'HTML' - -

This email contains an inline attachment without a filename.

- HTML, - ); - } - - public function buildEmailWithCyrillic(): Email - { - // Similar to buildEmail but with Cyrillic content - return (new Email()) - ->subject('Test message with Cyrillic') - ->date(new \DateTimeImmutable('2024-05-02 16:01:33')) - ->addTo( - new Address('alice@example.com', 'Alice Doe'), - 'barney@example.com', - ) - ->addFrom(new Address('no-reply@site.com', 'Bob Example')) - ->attachFromPath(path: __DIR__ . '/hello.txt') - ->attachFromPath(path: __DIR__ . '/logo.svg') - ->addPart( - (new DataPart(new File(__DIR__ . '/logo.svg'), 'logo-embeddable'))->asInline()->setContentId( - 'test-cid@buggregator', - ), - ) - ->html( - body: <<<'TEXT' - -

съешь же ещё этих мягких французских булок, да выпей чаю

-

съешь же ещё этих мягких французских булок, да выпей чаю

-

съешь же ещё этих мягких французских булок, да выпей чаю

-

съешь же ещё этих мягких французских булок, да выпей чаю

-

съешь же ещё этих мягких французских булок, да выпей чаю

- TEXT - , - ); - } -} diff --git a/tests/Feature/Interfaces/TCP/Smtp/MessageTest.php b/tests/Feature/Interfaces/TCP/Smtp/MessageTest.php deleted file mode 100644 index 7bb07b12..00000000 --- a/tests/Feature/Interfaces/TCP/Smtp/MessageTest.php +++ /dev/null @@ -1,83 +0,0 @@ -appendBody("Hello World\r\n"); - $this->assertFalse($message->bodyHasEos()); - - // Add Cyrillic content - $message->appendBody("съешь же ещё этих мягких французских булок, да выпей чаю\r\n"); - $this->assertFalse($message->bodyHasEos()); - - // Add more Cyrillic content with varying lengths - $cyrillicRepeated = str_repeat("съешь же ещё этих мягких французских булок, да выпей чаю\r\n", 5); - $message->appendBody($cyrillicRepeated); - $this->assertFalse($message->bodyHasEos()); - - // Add end of stream marker - $message->appendBody(".\r\n"); - $this->assertTrue($message->bodyHasEos()); - - // Check that getBody removes the EOS marker - $body = $message->getBody(); - $this->assertStringNotContainsString("\r\n.\r\n", $body); - - // Verify content is preserved - $this->assertStringContainsString("Hello World", $body); - $this->assertStringContainsString("съешь же ещё этих мягких французских булок, да выпей чаю", $body); - } - - public function testMultipleEOSMarkers(): void - { - $message = new Message('test-uuid'); - - // Add content with a period at the start of a line (which should be escaped) - $message->appendBody("Line 1\r\n"); - $message->appendBody(".Line starting with period\r\n"); - $message->appendBody("Line 3\r\n"); - - // Add an EOS marker in the middle (shouldn't be considered as EOS) - $message->appendBody(".\r\nMore content\r\n"); - $this->assertFalse($message->bodyHasEos()); - - // Now add actual EOS marker at the end - $message->appendBody(".\r\n"); - $this->assertTrue($message->bodyHasEos()); - - // Verify the body content is correct - $body = $message->getBody(); - $this->assertStringContainsString("Line 1", $body); - $this->assertStringContainsString(".Line starting with period", $body); - $this->assertStringContainsString("Line 3", $body); - $this->assertStringContainsString(".\r\nMore content", $body); - } - - public function testLargeBodyWithEOS(): void - { - $message = new Message('test-uuid'); - - // Add a large body - $largeBody = str_repeat("Lorem ipsum dolor sit amet. ", 1000); - $message->appendBody($largeBody); - $message->appendBody("\r\n.\r\n"); - - $this->assertTrue($message->bodyHasEos()); - - // Verify the body content is correct - $body = $message->getBody(); - $this->assertStringContainsString("Lorem ipsum dolor sit amet.", $body); - $this->assertStringNotContainsString("\r\n.\r\n", $body); - } -} diff --git a/tests/Feature/Interfaces/TCP/Smtp/ServiceTest.php b/tests/Feature/Interfaces/TCP/Smtp/ServiceTest.php deleted file mode 100644 index a792cfad..00000000 --- a/tests/Feature/Interfaces/TCP/Smtp/ServiceTest.php +++ /dev/null @@ -1,202 +0,0 @@ -cache = $this->createMock(CacheInterface::class); - - // Create a real EmailBodyStorage with a mock cache - $this->emailBodyStorage = new EmailBodyStorage($this->cache); - - $this->attachments = $this->createMock(AttachmentStorageInterface::class); - $this->bus = $this->createMock(CommandBusInterface::class); - - $this->service = new Service( - $this->bus, - $this->emailBodyStorage, - $this->attachments, - $this->get(Parser::class), - ); - } - - public function testResetBodyOnDataCommand(): void - { - $connectionUuid = (string) Uuid::uuid4(); - $message = new Message($connectionUuid); - $message->body = 'Some existing content that should be cleared'; - - // Setup the cache mock to return our message with existing content - $this->cache - ->expects($this->once()) - ->method('get') - ->with($connectionUuid, $this->anything()) - ->willReturn($message); - - // Expect the cache to be updated with the reset message - $this->cache - ->expects($this->once()) - ->method('set') - ->with( - $connectionUuid, - $this->callback( - fn($persistedMessage) => $persistedMessage->body === '' && $persistedMessage->waitBody === true, - ), - $this->anything(), - ); - - // Create a request with DATA command - $request = new Request( - remoteAddr: '127.0.0.1', - event: TcpEvent::Data, - body: "DATA\r\n", - connectionUuid: $connectionUuid, - server: 'test-server', - ); - - // Handle the request - $response = $this->service->handle($request); - - // Verify response is correct - $this->assertInstanceOf(RespondMessage::class, $response); - $this->assertStringContainsString('354', $response->getBody()); - } - - public function testResetWaitBodyAfterMessageProcessing(): void - { - $connectionUuid = (string) Uuid::uuid4(); - $message = new Message($connectionUuid); - $message->waitBody = true; - $message->body = "Test message content\r\n.\r\n"; // With EOS marker - - // Create a real MailMessage object instead of a mock - new MailMessage( - id: 'test-message-id', - raw: $message->getBody(), - sender: [['email' => 'test@example.com', 'name' => 'Test Sender']], - recipients: [], - ccs: [], - subject: 'Test Subject', - htmlBody: '

Test HTML

', - textBody: 'Test plain text', - replyTo: [], - allRecipients: [], - attachments: [], - ); - - // Set up the cache mock - $this->cache - ->expects($this->once()) - ->method('get') - ->with($connectionUuid, $this->anything()) - ->willReturn($message); - - // Setup mocks for dispatchMessage - $this->attachments - ->expects($this->once()) - ->method('store') - ->willReturn([]); - - $this->bus - ->expects($this->once()) - ->method('dispatch') - ->with($this->isInstanceOf(HandleReceivedEvent::class)); - - // Create a request with the last part of message body - $request = new Request( - remoteAddr: '127.0.0.1', - event: TcpEvent::Data, - body: "Final line of the message\r\n.\r\n", - connectionUuid: $connectionUuid, - server: 'test-server', - ); - - // Handle the request - $response = $this->service->handle($request); - - // Verify the waitBody flag was reset - $this->assertFalse($message->waitBody, 'waitBody flag should be reset after processing a message'); - - // Verify response code is 250 (message accepted) - $this->assertInstanceOf(RespondMessage::class, $response); - $this->assertStringContainsString('250', $response->getBody()); - } - - public function testCorrectResponseForDataWithCyrillic(): void - { - $connectionUuid = (string) Uuid::uuid4(); - $message = new Message($connectionUuid); - $message->waitBody = true; - - // Setup the cache mock - $this->cache - ->expects($this->once()) - ->method('get') - ->with($connectionUuid, $this->anything()) - ->willReturn($message); - - // Expect the cache to be updated - $this->cache - ->expects($this->once()) - ->method('set') - ->with( - $connectionUuid, - $this->callback(fn($persistedMessage) => str_contains( - $persistedMessage->body, - "съешь же ещё этих мягких французских булок, да выпей чаю", - )), - $this->anything(), - ); - - // Create a request with Cyrillic content - $request = new Request( - remoteAddr: '127.0.0.1', - event: TcpEvent::Data, - body: "съешь же ещё этих мягких французских булок, да выпей чаю\r\n", - connectionUuid: $connectionUuid, - server: 'test-server', - ); - - // Handle the request - $response = $this->service->handle($request); - - // Verify the body contains the Cyrillic content - $this->assertStringContainsString( - "съешь же ещё этих мягких французских булок, да выпей чаю", - $message->body, - ); - - // Verify correct response for partial data - $this->assertInstanceOf(RespondMessage::class, $response); - $this->assertStringContainsString('250', $response->getBody()); // OK response - } -} diff --git a/tests/Feature/Interfaces/TCP/Smtp/hello.txt b/tests/Feature/Interfaces/TCP/Smtp/hello.txt deleted file mode 100644 index cd087558..00000000 --- a/tests/Feature/Interfaces/TCP/Smtp/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello world! diff --git a/tests/Feature/Interfaces/TCP/Smtp/logo.svg b/tests/Feature/Interfaces/TCP/Smtp/logo.svg deleted file mode 100644 index 2330ca97..00000000 --- a/tests/Feature/Interfaces/TCP/Smtp/logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/Feature/Interfaces/TCP/Smtp/sample.pdf b/tests/Feature/Interfaces/TCP/Smtp/sample.pdf deleted file mode 100644 index c172e182af60026565211f600bfc2140e1b7711e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61752 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

oU9ipR7nHOp*H2xrO` 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 KtGuQH!G8g-^L#Y` diff --git a/tests/Feature/Modules/Smtp/Application/Mail/InlineAttachmentParsingTest.php b/tests/Feature/Modules/Smtp/Application/Mail/InlineAttachmentParsingTest.php deleted file mode 100644 index f70528f5..00000000 --- a/tests/Feature/Modules/Smtp/Application/Mail/InlineAttachmentParsingTest.php +++ /dev/null @@ -1,287 +0,0 @@ -parser = $this->get(Parser::class); - } - - public function testParseEmailWithInlineAttachmentWithoutFilename(): void - { - // This simulates the problematic case where an inline attachment has no filename - $rawEmail = $this->buildRawEmailWithInlineAttachmentWithoutFilename(); - - $message = $this->parser->parse($rawEmail); - - // Verify the message was parsed successfully - $this->assertSame('Test with inline attachment', $message->subject); - $this->assertCount(1, $message->attachments); - - // Verify the attachment was processed correctly - $attachment = $message->attachments[0]; - - $this->assertSame('qr_domain.com.png', $attachment->getFilename()); - $this->assertSame('image/png', $attachment->getType()); - $this->assertSame('qr@domain.com', $attachment->getContentId()); - $this->assertNotEmpty($attachment->getContent()); - } - - public function testParseEmailWithComplexContentId(): void - { - $rawEmail = $this->buildRawEmailWithComplexContentId(); - - $message = $this->parser->parse($rawEmail); - - $this->assertCount(1, $message->attachments); - $attachment = $message->attachments[0]; - - // Complex content-id should be sanitized to valid filename - $this->assertSame('logo-embeddable_test.buggregator.svg', $attachment->getFilename()); - $this->assertSame('image/svg+xml', $attachment->getType()); - $this->assertSame('logo-embeddable@test.buggregator', $attachment->getContentId()); - } - - public function testParseEmailWithMixedAttachments(): void - { - $rawEmail = $this->buildRawEmailWithMixedAttachments(); - - $message = $this->parser->parse($rawEmail); - - $this->assertCount(2, $message->attachments); - - // First attachment: inline without filename - $inlineAttachment = $message->attachments[0]; - $this->assertSame('test-id_example.com.jpg', $inlineAttachment->getFilename()); - $this->assertSame('image/jpeg', $inlineAttachment->getType()); - $this->assertSame('test-id@example.com', $inlineAttachment->getContentId()); - - // Second attachment: regular with filename - $regularAttachment = $message->attachments[1]; - $this->assertSame('document.pdf', $regularAttachment->getFilename()); - $this->assertSame('application/pdf', $regularAttachment->getType()); - $this->assertNull($regularAttachment->getContentId()); - } - - private function buildRawEmailWithInlineAttachmentWithoutFilename(): string - { - return <<<'EMAIL' - From: sender@example.com - To: recipient@example.com - Subject: Test with inline attachment - Content-Type: multipart/related; boundary="boundary123" - - --boundary123 - Content-Type: text/html; charset=UTF-8 - - - -

This is a test with inline attachment:

- QR Code - - - - --boundary123 - Content-Type: image/png - Content-Disposition: inline - Content-ID: - - iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== - --boundary123-- - EMAIL; - } - - private function buildRawEmailWithComplexContentId(): string - { - return <<<'EMAIL' - From: sender@example.com - To: recipient@example.com - Subject: Test with complex content-id - Content-Type: multipart/related; boundary="boundary456" - - --boundary456 - Content-Type: text/html; charset=UTF-8 - - - -

This is a test with complex content-id:

- Logo - - - - --boundary456 - Content-Type: image/svg+xml - Content-Disposition: inline - Content-ID: - - - - - --boundary456-- - EMAIL; - } - - private function buildRawEmailWithMixedAttachments(): string - { - return <<<'EMAIL' - From: sender@example.com - To: recipient@example.com - Subject: Test with mixed attachments - Content-Type: multipart/mixed; boundary="boundary789" - - --boundary789 - Content-Type: text/html; charset=UTF-8 - - - -

This email has both inline and regular attachments:

- Inline Image -

And a regular attachment below.

- - - - --boundary789 - Content-Type: image/jpeg - Content-Disposition: inline - Content-ID: - - /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/gA== - - --boundary789 - Content-Type: application/pdf - Content-Disposition: attachment; filename="document.pdf" - - JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKL01lZGlhQm94IFswIDAgNjEyIDc5Ml0KPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgo+PgplbmRvYmoKdHJhaWxlcgo8PAovUm9vdCAxIDAgUgo+PgpzdGFydHhyZWYKMjczCiUlRU9G - --boundary789-- - EMAIL; - } - - public function testParseEmailWithFallbackStrategy(): void - { - // Test edge case that should trigger fallback strategy - $rawEmail = $this->buildRawEmailWithUnknownAttachmentType(); - - $message = $this->parser->parse($rawEmail); - - $this->assertCount(1, $message->attachments); - $attachment = $message->attachments[0]; - - // Should generate a filename since no content-id or filename is provided - $this->assertStringStartsWith('attachment_', $attachment->getFilename()); - $this->assertStringEndsWith('.bin', $attachment->getFilename()); - $this->assertSame('application/octet-stream', $attachment->getType()); - } - - private function buildRawEmailWithUnknownAttachmentType(): string - { - return <<<'EMAIL' - From: sender@example.com - To: recipient@example.com - Subject: Test with unknown attachment - Content-Type: multipart/mixed; boundary="boundary999" - - --boundary999 - Content-Type: text/plain; charset=UTF-8 - - This email has an unknown attachment type. - - --boundary999 - Content-Type: application/octet-stream - Content-Disposition: attachment - - BinaryDataHere123456789 - --boundary999-- - EMAIL; - } - - public function testParseEmailWithEmptyContentId(): void - { - $rawEmail = $this->buildRawEmailWithEmptyContentId(); - - $message = $this->parser->parse($rawEmail); - - $this->assertCount(1, $message->attachments); - $attachment = $message->attachments[0]; - - // Should generate a filename when content-id is empty - $this->assertStringStartsWith('attachment_', $attachment->getFilename()); - $this->assertStringEndsWith('.png', $attachment->getFilename()); - $this->assertSame('image/png', $attachment->getType()); - } - - private function buildRawEmailWithEmptyContentId(): string - { - return <<<'EMAIL' - From: sender@example.com - To: recipient@example.com - Subject: Test with empty content-id - Content-Type: multipart/related; boundary="boundary111" - - --boundary111 - Content-Type: text/html; charset=UTF-8 - - - -

This has an empty content-id:

- Empty CID - - - - --boundary111 - Content-Type: image/png - Content-Disposition: inline - Content-ID: <> - - iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== - --boundary111-- - EMAIL; - } - - public function testParseEmailHandlesExceptionsGracefully(): void - { - // Test that parser handles malformed attachments without crashing - $rawEmail = $this->buildRawEmailWithMalformedAttachment(); - - // This should not throw an exception - $message = $this->parser->parse($rawEmail); - - // Should still parse the message even with malformed attachment - $this->assertSame('Test with malformed attachment', $message->subject); - - // Attachment processing might fail, but message parsing should continue - // The exact behavior depends on the underlying mail parser library - } - - private function buildRawEmailWithMalformedAttachment(): string - { - return <<<'EMAIL' - From: sender@example.com - To: recipient@example.com - Subject: Test with malformed attachment - Content-Type: multipart/mixed; boundary="boundary222" - - --boundary222 - Content-Type: text/plain; charset=UTF-8 - - This email has a malformed attachment. - - --boundary222 - Content-Type: image/png - Content-Disposition: attachment - Content-ID: Date: Sun, 23 Nov 2025 11:06:03 +0400 Subject: [PATCH 3/4] cs fix --- app/modules/VarDumper/Application/Dump/HtmlDumper.php | 2 +- psalm.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/VarDumper/Application/Dump/HtmlDumper.php b/app/modules/VarDumper/Application/Dump/HtmlDumper.php index 576650c2..199c1a50 100644 --- a/app/modules/VarDumper/Application/Dump/HtmlDumper.php +++ b/app/modules/VarDumper/Application/Dump/HtmlDumper.php @@ -96,7 +96,7 @@ public function enterHash(Cursor $cursor, int $type, string|int|null $class, boo if ($hasChild) { $this->line .= '