diff --git a/.github/phpstan.neon b/.github/phpstan.neon new file mode 100644 index 0000000..092e73d --- /dev/null +++ b/.github/phpstan.neon @@ -0,0 +1,18 @@ +# Static analysis of the documentation's PHP examples against the real package. +# +# The paths are supplied on the command line by check-code-examples.php, which +# extracts the code blocks first. Reported line numbers are the markdown line +# numbers: the extractor blanks out everything that is not code rather than +# collapsing it. +parameters: + level: 5 + + scanFiles: + - stubs/placeholders.php + + ignoreErrors: + # Examples routinely assign something to show the shape of the wiring + # without going on to use it — the Symfony page's constructor injection + # is the whole point of that snippet. This can never indicate a + # documentation defect. + - identifier: property.onlyWritten diff --git a/.github/scripts/extract-code-examples.php b/.github/scripts/extract-code-examples.php new file mode 100755 index 0000000..bef4c43 --- /dev/null +++ b/.github/scripts/extract-code-examples.php @@ -0,0 +1,160 @@ +#!/usr/bin/env php + + */ + +declare(strict_types=1); + +const SOURCES = ['_docs', 'resources/includes']; + +$root = dirname(__DIR__, 2); +$outputDir = $argv[1] ?? null; + +if ($outputDir === null) { + fwrite(STDERR, "usage: extract-code-examples.php \n"); + exit(1); +} + +/** A namespace segment per path component, so each page is isolated. */ +function namespaceFor(string $relative): string +{ + $parts = preg_split('#[/\\\\]#', substr($relative, 0, -3)) ?: []; + $parts = array_map( + static fn (string $part): string => str_replace(' ', '', ucwords(str_replace(['-', '_', '.'], ' ', $part))), + $parts + ); + + return 'DocExample\\' . implode('\\', $parts); +} + +$files = []; +foreach (SOURCES as $source) { + if (! is_dir("$root/$source")) { + continue; + } + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator("$root/$source")) as $file) { + if ($file->isFile() && $file->getExtension() === 'md') { + $files[] = $file->getPathname(); + } + } +} +sort($files); + +$written = 0; +foreach ($files as $file) { + $relative = substr($file, strlen($root) + 1); + $lines = explode("\n", (string) file_get_contents($file)); + + $out = []; + $ownFiles = []; + $inFence = false; + $fence = []; + $fenceStart = 0; + $seenUse = []; + + foreach ($lines as $index => $line) { + if (! $inFence) { + $out[] = ''; + if (preg_match('/^```php\s*$/', $line)) { + $inFence = true; + $fence = []; + $fenceStart = $index + 1; + } + continue; + } + + if (preg_match('/^```\s*$/', $line)) { + // A fence declaring its own namespace stands for a separate file in + // the reader's application. It gets its own analysed file, keeping + // the namespace it declares, rather than being merged into the page. + $isOwnFile = false; + foreach ($fence as $fenceLine) { + if (preg_match('/^\s*namespace\s+/', $fenceLine)) { + $isOwnFile = true; + break; + } + } + + if ($isOwnFile) { + $standalone = array_fill(0, count($lines), ''); + foreach ($fence as $offset => $fenceLine) { + $standalone[$fenceStart + $offset] = preg_match('/^<\?php\s*$/', trim($fenceLine)) + ? '' + : $fenceLine; + } + $standalone[0] = ' $fenceLine) { + $keep = ! $isOwnFile; + + if ($keep && preg_match('/^<\?php\s*$/', trim($fenceLine))) { + $keep = false; + } + + // Repeating a `use` across fences of one page is correct in the + // docs but a redeclaration once merged. + if ($keep && preg_match('/^use\s+[^;]+;$/', trim($fenceLine))) { + if (isset($seenUse[trim($fenceLine)])) { + $keep = false; + } else { + $seenUse[trim($fenceLine)] = true; + } + } + + $out[$fenceStart + $offset] = $keep ? $fenceLine : ''; + } + + $out[] = ''; + $inFence = false; + continue; + } + + $fence[] = $line; + $out[] = ''; + } + + // Mirror the source tree so a reported path maps straight back to the page. + foreach ($ownFiles as $index => $contents) { + $target = $outputDir . '/' . $relative . '.' . $index . '.php'; + if (! is_dir(dirname($target))) { + mkdir(dirname($target), 0777, true); + } + file_put_contents($target, $contents); + $written++; + } + + if (trim(implode('', $out)) === '') { + continue; + } + + // Line 1 of a markdown page is front matter or a fence marker, never code, + // so the declaration can live there without shifting anything. + $out[0] = 'isFile() && $file->getExtension() === 'md') { + $files[] = $file->getPathname(); + } + } + } + sort($files); + + return $files; +} + +/** + * Pull the ```php fences out of one file. + * + * @return array line is 1-indexed and + * points at the fence marker itself. + */ +function phpBlocks(string $markdown): array +{ + $blocks = []; + $lines = explode("\n", $markdown); + $open = null; + $body = []; + + foreach ($lines as $index => $line) { + if ($open === null) { + if (preg_match('/^```php\s*$/', $line)) { + $open = $index + 1; + $body = []; + } + continue; + } + + if (preg_match('/^```\s*$/', $line)) { + $blocks[] = ['line' => $open, 'code' => implode("\n", $body)]; + $open = null; + continue; + } + + $body[] = $line; + } + + return $blocks; +} + +$files = markdownFiles($root); +$checked = 0; +$failures = []; + +foreach ($files as $file) { + $relative = substr($file, strlen($root) + 1); + + foreach (phpBlocks((string) file_get_contents($file)) as $block) { + $code = ltrim($block['code'], "\n"); + + // A block may or may not open with its own tag. Normalise to exactly one, + // and remember whether that shifted the code down a line so the linter's + // line numbers can be translated back to the markdown. + if (preg_match('/^<\?php\s*$/m', strtok($code, "\n") ?: '')) { + $offset = $block['line']; + } else { + $code = "&1', escapeshellarg(PHP_BINARY), escapeshellarg($temp)), $output, $status); + unlink($temp); + $checked++; + + if ($status === 0) { + $output = []; + continue; + } + + // php -l prints two lines: the diagnostic, then "Errors parsing ". + // Keep the first, and translate its line number back into the markdown. + $message = ''; + foreach ($output as $candidate) { + if (str_contains($candidate, ' on line ')) { + $message = $candidate; + break; + } + } + $message = $message !== '' ? $message : ($output[0] ?? 'could not be parsed'); + $output = []; + + $line = $block['line']; + if (preg_match('/ on line (\d+)/', $message, $matches)) { + $line = $offset + (int) $matches[1]; + } + $message = preg_replace('/ in \S+ on line \d+/', '', $message) ?? $message; + $message = trim(preg_replace('/^(PHP )?(Parse|Fatal) error:\s*/i', '', $message) ?? $message); + + $failures[] = ['file' => $relative, 'line' => $line, 'message' => $message]; + } +} + +foreach ($failures as $failure) { + $text = sprintf('%s:%d %s', $failure['file'], $failure['line'], $failure['message']); + echo $onCi + ? sprintf("::error file=%s,line=%d::%s\n", $failure['file'], $failure['line'], $failure['message']) + : $text . "\n"; +} + +if ($failures !== []) { + printf("\n%d of %d code blocks in %d files failed to parse.\n", count($failures), $checked, count($files)); + exit(1); +} + +printf("All %d PHP code blocks in %d files parse cleanly.\n", $checked, count($files)); diff --git a/.github/stubs/placeholders.php b/.github/stubs/placeholders.php new file mode 100644 index 0000000..60224e7 --- /dev/null +++ b/.github/stubs/placeholders.php @@ -0,0 +1,85 @@ +cache = $cache; + } + + public function handle( + \stdClass $json, + \JsonMapper\Wrapper\ObjectWrapper $object, + \JsonMapper\ValueObjects\PropertyMap $map, + \JsonMapper\JsonMapperInterface $mapper + ): void { + } + } +} + +namespace JsonMapper\SymfonyBundle { + /** json-mapper/symfony-bundle — conflicts with Hyde's dependencies. */ + class JsonMapperBundle + { + } +} diff --git a/.github/workflows/lint-examples.yml b/.github/workflows/lint-examples.yml new file mode 100644 index 0000000..49bc093 --- /dev/null +++ b/.github/workflows/lint-examples.yml @@ -0,0 +1,81 @@ +name: Check documentation examples + +# The package this site documents lives in a different repository, so nothing in +# the build compiles the examples — a broken snippet renders perfectly and ships +# broken. That is how the drift reported in #63 went unnoticed: the Setup page +# called JsonMapperBuilder::create(), which does not exist, and the landing page +# passed a JSON string to mapToClass(), whose first parameter is a \stdClass. +# +# Two passes, cheapest first: +# +# 1. A parse check, needing only a PHP binary. Fails in seconds, before the +# Composer install, and keeps working if dependency resolution ever breaks. +# 2. PHPStan against the real json-mapper/json-mapper, which is what catches a +# call to a method that does not exist, a missing constructor argument or an +# argument of the wrong type. Every defect in #63 except one is caught here. +# +# Neither pass runs the examples, so a snippet that is valid and well-typed but +# throws at run time still gets through. Verify output by hand before changing an +# example. + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: check-examples-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Parse and analyse the PHP code blocks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + + - name: Lint the PHP code blocks + run: php .github/scripts/lint-code-examples.php + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer packages + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + # Unlike the deploy workflow this needs the dev dependencies: PHPStan and + # the package whose API the examples are checked against. + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-interaction + + - name: Extract the code blocks + run: php .github/scripts/extract-code-examples.php .phpstan-doc-examples + + # PHPStan reports against the extracted files; rewriting the paths puts the + # annotations back on the markdown, whose line numbers they already carry. + # pipefail keeps PHPStan's exit code through the rewrite. + - name: Analyse the code blocks against JsonMapper + run: | + set -o pipefail + vendor/bin/phpstan analyse \ + --configuration=.github/phpstan.neon \ + --error-format=github \ + --no-progress \ + --memory-limit=512M \ + .phpstan-doc-examples \ + | sed -E 's#\.phpstan-doc-examples/##; s#\.md(\.[0-9]+)?\.php#.md#' diff --git a/.gitignore b/.gitignore index f6cc552..be58729 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /.idea/ /.DS_Store .env +.phpstan-doc-examples/ diff --git a/README.md b/README.md index b597d88..126a008 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,70 @@ from the file path. Ordering and labels live in `config/docs.php` under `sidebar Documentation output is flat, so `_docs/usage/installation.md` is served at `/docs/installation`. +## Code examples + +The package these pages document lives in +[JsonMapper/JsonMapper](https://github.com/JsonMapper/JsonMapper), so nothing in this repo compiles the +examples. A broken snippet renders perfectly and ships. Two conventions and one CI check keep that in +hand. + +**Write class names out in full; do not add an import block.** A snippet is usually a dozen lines, and +five lines of `use` above it costs more than the width does: + +```php +$mapper = (new \JsonMapper\JsonMapperFactory())->default(); +$mapper->push(new \JsonMapper\Middleware\CaseConversion( + \JsonMapper\Enums\TextNotation::STUDLY_CAPS(), + \JsonMapper\Enums\TextNotation::CAMEL_CASE() +)); +``` + +The exception is a snippet standing in for a real file in the reader's application — one that declares +its own `namespace`. Those use imports, because that is what the reader would write. + +**Let the example define the classes it maps onto.** Do not reference the package's test fixtures; a +reader copying `\JsonMapper\Tests\Implementation\SimpleObject` gets a class their project does not +have. Three lines of `class User { public string $name; }` is enough. + +**Note the introducing release only for APIs added during 2.x**, as `_Available since JsonMapper +x.y.z_` under the page intro. The site documents v2, so "available since 0.3.0" answers a question +nobody has. + +Do not write the *documented* release into the prose. It is read from the installed +`json-mapper/json-mapper` by `App\Support\DocumentedVersion` and shown once, at the foot of the +documentation sidebar, so the version the pages claim to describe is by construction the version their +examples are checked against. Bumping the dependency updates the site. The deploy installs with +`--no-dev`, where the package is absent, so the resolver falls back to `composer.lock`. + +`.github/workflows/lint-examples.yml` checks every ` ```php ` block on each pull request, in two +passes. Run them locally with: + +```shell +$PHP .github/scripts/lint-code-examples.php # 1. parse +$PHP .github/scripts/extract-code-examples.php .phpstan-doc-examples # 2. analyse +$PHP vendor/bin/phpstan analyse -c .github/phpstan.neon --memory-limit=512M .phpstan-doc-examples +``` + +The first is a parse check needing only a PHP binary. The second runs PHPStan against the real +`json-mapper/json-mapper`, which is a dev dependency here for exactly that reason, and is what catches +a call to a method that does not exist, a missing constructor argument, or an argument of the wrong +type. Both report against the markdown file and line: the extractor blanks out everything that is not +code rather than collapsing it, so an analysed file's line numbers are the page's line numbers. + +Examples are extracted **per page**, since a page routinely defines a class in one fence and maps onto +it in the next, and each page is given its own namespace so that the `User` several pages define does +not collide. A fence that declares its own `namespace` is analysed as its own file. + +Classes the examples borrow from the reader's imagination (`\App\…`) or from packages that cannot be +installed alongside Hyde are declared in `.github/stubs/placeholders.php`. Prefer a real dev dependency +over a stub where the two can coexist — `json-mapper/laravel-package` and `monolog/monolog` are +installed for this reason, so those pages are checked against the genuine classes. + +Neither pass runs the examples, so a snippet that is valid and well-typed but throws at run time still +gets through — `(new JsonMapperFactory())->create()` with no middleware is the case that motivated +this. Before changing a snippet, check it against a real checkout of JsonMapper and make the inline +`// "John Doe"` comments the output it genuinely produces. + ## Deployment Pushing to `main` triggers `.github/workflows/build.yml`, which builds the site and publishes it to diff --git a/_docs/guides/symfony-usage.md b/_docs/guides/symfony-usage.md index a3d49e1..ca0065e 100644 --- a/_docs/guides/symfony-usage.md +++ b/_docs/guides/symfony-usage.md @@ -21,7 +21,7 @@ If your application does not use [Symfony Flex](https://symfony.com/doc/current/ return [ // ... - JsonMapper\SymfonyBundle\JsonMapperBundle::class => ['all' => true], + \JsonMapper\SymfonyBundle\JsonMapperBundle::class => ['all' => true], ]; ``` diff --git a/_docs/index.md b/_docs/index.md index b74f99e..a3a2ffb 100644 --- a/_docs/index.md +++ b/_docs/index.md @@ -12,9 +12,9 @@ own models. These pages cover installing it, the middleware it ships with, and h New here? Start with [Getting started](/_docs/guides/getting-started.md), which walks through mapping a first response end to end. -These pages describe JsonMapper **2.25.1**, which requires PHP 7.4 or higher. Anything added during -the 2.x line carries an "Available since" line naming the release that introduced it; everything else -has been there since 2.0. +JsonMapper requires PHP 7.4 or higher. Anything added during the 2.x line carries an "Available since" +line naming the release that introduced it; everything else has been there since 2.0. The release these +pages describe is shown at the foot of the sidebar. ## Where to look diff --git a/_docs/middleware/final-callback.md b/_docs/middleware/final-callback.md index a93702f..0c298af 100644 --- a/_docs/middleware/final-callback.md +++ b/_docs/middleware/final-callback.md @@ -27,10 +27,9 @@ $mapper->push(new \JsonMapper\Middleware\FinalCallback(function( \JsonMapper\ValueObjects\PropertyMap $map, \JsonMapper\JsonMapperInterface $mapper ) { - // Call a method on the object + // Call a method on the object now that it has been filled $object->getObject()->done(); - // Or persist it in the cache - Cache::put('key', $object->getObject(), $seconds); + // ...or hand it to a cache, a queue, an event dispatcher, and so on })); $object = new User(); diff --git a/_docs/middleware/laravel-eloquent.md b/_docs/middleware/laravel-eloquent.md index 6fcc4fc..bc21b86 100644 --- a/_docs/middleware/laravel-eloquent.md +++ b/_docs/middleware/laravel-eloquent.md @@ -17,8 +17,8 @@ $data = file_get_contents($url); $mapper = (new \JsonMapper\JsonMapperFactory())->bestFit(); $mapper->push(new \JsonMapper\EloquentMiddleware\EloquentMiddleware(new \JsonMapper\Cache\ArrayCache())); -$licenses = $mapper->mapArrayFromString($data, new License()); -\Illuminate\Support\Collection::make($licenses)->each(fn(License $l) => $l->save()); +$licenses = $mapper->mapArrayFromString($data, new \App\Models\License()); +\Illuminate\Support\Collection::make($licenses)->each(fn(\App\Models\License $l) => $l->save()); ``` _This middleware is part of separate repository and need to be installed using `composer require json-mapper/eloquent-middleware`_ diff --git a/_docs/usage/setup.md b/_docs/usage/setup.md index bd3ad39..3b8e486 100644 --- a/_docs/usage/setup.md +++ b/_docs/usage/setup.md @@ -50,7 +50,7 @@ $propertyMapper = \JsonMapper\Builders\PropertyMapperBuilder::new() ->build(); $mapper = \JsonMapper\JsonMapperBuilder::new() - ->withJsonMapperClassName(YourExtendedJsonMapper::class) + ->withJsonMapperClassName(\App\YourExtendedJsonMapper::class) ->withPropertyMapper($propertyMapper) ->withDefaultCache(new \JsonMapper\Cache\ArrayCache()) ->withDocBlockAnnotationsMiddleware() diff --git a/app/Support/DocumentedVersion.php b/app/Support/DocumentedVersion.php new file mode 100644 index 0000000..41b0ea9 --- /dev/null +++ b/app/Support/DocumentedVersion.php @@ -0,0 +1,81 @@ +=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, { "name": "hyde/realtime-compiler", "version": "v4.5.0", @@ -6687,6 +6735,1103 @@ } ], "time": "2026-07-09T23:56:00+00:00" + }, + { + "name": "json-mapper/json-mapper", + "version": "2.25.1", + "source": { + "type": "git", + "url": "https://github.com/JsonMapper/JsonMapper.git", + "reference": "4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JsonMapper/JsonMapper/zipball/4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61", + "reference": "4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61", + "shasum": "" + }, + "require": { + "ext-json": "*", + "myclabs/php-enum": "^1.7", + "nikic/php-parser": "^4.13 || ^5.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-docblock": "^5.6", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "psr/simple-cache": " ^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/polyfill-php73": "^1.18" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5 || ^7.0", + "php-coveralls/php-coveralls": "^2.4", + "phpstan/phpstan": "^0.12.14", + "phpstan/phpstan-phpunit": "^0.12.17", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "vimeo/psalm": "^4.10 || ^5.0" + }, + "suggest": { + "json-mapper/laravel-package": "Use JsonMapper directly with Laravel", + "json-mapper/symfony-bundle": "Use JsonMapper directly with Symfony" + }, + "type": "library", + "autoload": { + "psr-4": { + "JsonMapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Map JSON structures to PHP classes", + "homepage": "https://jsonmapper.net", + "keywords": [ + "json", + "jsonmapper", + "mapper", + "middleware" + ], + "support": { + "docs": "https://jsonmapper.net", + "issues": "https://github.com/JsonMapper/JsonMapper/issues", + "source": "https://github.com/JsonMapper/JsonMapper" + }, + "funding": [ + { + "url": "https://github.com/DannyvdSluijs", + "type": "github" + } + ], + "time": "2025-05-26T09:51:24+00:00" + }, + { + "name": "json-mapper/laravel-package", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/JsonMapper/LaravelPackage.git", + "reference": "e0494cbfa405417bb47ab06ed6f68e4f87ad36b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JsonMapper/LaravelPackage/zipball/e0494cbfa405417bb47ab06ed6f68e4f87ad36b3", + "reference": "e0494cbfa405417bb47ab06ed6f68e4f87ad36b3", + "shasum": "" + }, + "require": { + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "json-mapper/json-mapper": "^2.3", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5 || ^7.0", + "orchestra/testbench": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0", + "php-coveralls/php-coveralls": "^2.4", + "phpstan/phpstan": "^0.12.19 || ^1.0.0 || ^2.1", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.5 || ^11.5.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "JsonMapper\\LaravelPackage\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "JsonMapper\\LaravelPackage\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The JsonMapper package for Laravel", + "keywords": [ + "json", + "jsonmapper", + "laravel", + "mapper", + "middleware" + ], + "support": { + "issues": "https://github.com/JsonMapper/LaravelPackage/issues", + "source": "https://github.com/JsonMapper/LaravelPackage/tree/3.0.0" + }, + "time": "2026-04-05T19:27:41+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2025-01-14T11:49:03+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.7", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "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.7" + }, + "time": "2026-03-18T20:47:46+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "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.12.0" + }, + "time": "2025-11-21T15:09:14+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "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.3.3" + }, + "time": "2026-07-08T07:01:06+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.2.9", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", + "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-08-22T07:38:16+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "f6028442dd1dfa4f88e9c7360d753948d165e938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/f6028442dd1dfa4f88e9c7360d753948d165e938", + "reference": "f6028442dd1dfa4f88e9c7360d753948d165e938", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "ext-relay": "<0.12.1", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "^1.0.3", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.4.17" + }, + "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": "2026-08-19T08:28:05+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "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": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1" + }, + "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": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "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\\Php73\\": "" + }, + "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 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.4.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "ca31404415670aa3834809005b529df1b84f0790" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/ca31404415670aa3834809005b529df1b84f0790", + "reference": "ca31404415670aa3834809005b529df1b84f0790", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.4.16" + }, + "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": "2026-07-30T12:37:26+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.4.1" + }, + "time": "2026-06-15T15:31:57+00:00" } ], "aliases": [], diff --git a/config/docs.php b/config/docs.php index b336c9d..3686544 100644 --- a/config/docs.php +++ b/config/docs.php @@ -31,7 +31,13 @@ 'collapsible' => true, // A string of Markdown to show in the footer. Set to `false` to disable. - 'footer' => '[GitHub](https://github.com/JsonMapper/JsonMapper) · [Twitter](https://twitter.com/JsonMapper)', + // The documented release is read from the installed JsonMapper rather + // than written out, so it cannot drift from the version the code + // examples are checked against. See App\Support\DocumentedVersion. + 'footer' => '[GitHub](https://github.com/JsonMapper/JsonMapper) · [Twitter](https://twitter.com/JsonMapper)' + .(\App\Support\DocumentedVersion::get() !== null + ? ' · Documenting JsonMapper '.\App\Support\DocumentedVersion::get() + : ''), /* |--------------------------------------------------------------------------