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..199c1a50 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 = [ @@ -96,7 +96,7 @@ public function enterHash(Cursor $cursor, int $type, string|int|null $class, boo if ($hasChild) { $this->line .= '=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.9", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + "reference": "505a30ad386daa5211f08a318e47015b501cad30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "url": "https://api.github.com/repos/nette/utils/zipball/505a30ad386daa5211f08a318e47015b501cad30", + "reference": "505a30ad386daa5211f08a318e47015b501cad30", "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.9" }, - "time": "2025-06-03T04:55:08+00:00" + "time": "2025-10-31T00:45:47+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" diff --git a/psalm.xml b/psalm.xml index ff5d28dd..494b7e12 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ 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 c172e182..00000000 Binary files a/tests/Feature/Interfaces/TCP/Smtp/sample.pdf and /dev/null differ 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: