From 68822da326a0b0b65029bd1fd59ad3e39988a8a4 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Sat, 19 Oct 2024 21:36:52 +0200 Subject: [PATCH 01/31] feat: initial setup of demo app --- .env | 23 + .env.test | 7 + .gitignore | 29 + .php-cs-fixer.dist.php | 15 + LICENSE | 19 + README.md | 79 + assets/app.js | 3 + assets/bootstrap.js | 5 + assets/controllers.json | 30 + assets/controllers/chat_controller.js | 59 + assets/controllers/wikipedia_controller.js | 59 + assets/controllers/youtube_controller.js | 95 + assets/icons/bi/youtube.svg | 1 + assets/icons/entypo/chat.svg | 1 + assets/icons/fluent/bot-24-filled.svg | 1 + assets/icons/material-symbols/cancel.svg | 1 + assets/icons/mdi/github.svg | 1 + assets/icons/mdi/wikipedia.svg | 1 + assets/icons/mingcute/ai-fill.svg | 1 + assets/icons/mingcute/send-fill.svg | 1 + assets/icons/solar/code-linear.svg | 1 + assets/icons/solar/user-bold.svg | 1 + assets/icons/symfony.svg | 1 + assets/styles/app.css | 157 + bin/console | 21 + compose.yaml | 31 + composer.json | 111 + composer.lock | 8399 +++++++++++++++++ config/bundles.php | 17 + config/packages/asset_mapper.yaml | 11 + config/packages/cache.yaml | 19 + config/packages/chromadb.yaml | 8 + config/packages/debug.yaml | 5 + config/packages/framework.yaml | 16 + config/packages/llm_chain.yaml | 40 + config/packages/monolog.yaml | 62 + config/packages/routing.yaml | 10 + config/packages/twig.yaml | 6 + config/packages/twig_component.yaml | 5 + config/packages/web_profiler.yaml | 17 + config/preload.php | 5 + config/routes.yaml | 23 + config/routes/framework.yaml | 4 + config/routes/ux_live_component.yaml | 5 + config/routes/web_profiler.yaml | 8 + .../secrets/dev/dev.OPENAI_API_KEY.66949e.php | 3 + config/secrets/dev/dev.encrypt.public.php | 3 + config/secrets/dev/dev.list.php | 5 + config/services.yaml | 20 + demo.png | Bin 0 -> 54356 bytes docker/Dockerfile | 14 + docker/config.json | 42 + importmap.php | 44 + phpstan.dist.neon | 12 + phpunit.xml | 30 + public/favicon.ico | Bin 0 -> 15086 bytes public/index.php | 9 + public/wiki.png | Bin 0 -> 40205 bytes src/Chat/Rag.php | 63 + src/Chat/Wikipedia.php | 56 + src/Chat/YouTube.php | 76 + src/Command/BlogEmbedCommand.php | 130 + src/Command/ChromaTestCommand.php | 80 + src/Kernel.php | 11 + src/ProfilerSubscriber.php | 42 + src/Twig/RagComponent.php | 40 + src/Twig/WikipediaComponent.php | 40 + src/Twig/YouTubeComponent.php | 70 + src/YouTube/TranscriptFetcher.php | 56 + symfony.lock | 232 + templates/base.html.twig | 51 + templates/chat/rag.html.twig | 9 + templates/chat/wikipedia.html.twig | 9 + templates/chat/youtube.html.twig | 9 + templates/components/_message.html.twig | 47 + templates/components/rag.html.twig | 30 + templates/components/wikipedia.html.twig | 30 + templates/components/youtube.html.twig | 38 + templates/index.html.twig | 66 + tests/bootstrap.php | 14 + 80 files changed, 10795 insertions(+) create mode 100644 .env create mode 100644 .env.test create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assets/app.js create mode 100644 assets/bootstrap.js create mode 100644 assets/controllers.json create mode 100644 assets/controllers/chat_controller.js create mode 100644 assets/controllers/wikipedia_controller.js create mode 100644 assets/controllers/youtube_controller.js create mode 100644 assets/icons/bi/youtube.svg create mode 100644 assets/icons/entypo/chat.svg create mode 100644 assets/icons/fluent/bot-24-filled.svg create mode 100644 assets/icons/material-symbols/cancel.svg create mode 100644 assets/icons/mdi/github.svg create mode 100644 assets/icons/mdi/wikipedia.svg create mode 100644 assets/icons/mingcute/ai-fill.svg create mode 100644 assets/icons/mingcute/send-fill.svg create mode 100644 assets/icons/solar/code-linear.svg create mode 100644 assets/icons/solar/user-bold.svg create mode 100644 assets/icons/symfony.svg create mode 100644 assets/styles/app.css create mode 100755 bin/console create mode 100644 compose.yaml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config/bundles.php create mode 100644 config/packages/asset_mapper.yaml create mode 100644 config/packages/cache.yaml create mode 100644 config/packages/chromadb.yaml create mode 100644 config/packages/debug.yaml create mode 100644 config/packages/framework.yaml create mode 100644 config/packages/llm_chain.yaml create mode 100644 config/packages/monolog.yaml create mode 100644 config/packages/routing.yaml create mode 100644 config/packages/twig.yaml create mode 100644 config/packages/twig_component.yaml create mode 100644 config/packages/web_profiler.yaml create mode 100644 config/preload.php create mode 100644 config/routes.yaml create mode 100644 config/routes/framework.yaml create mode 100644 config/routes/ux_live_component.yaml create mode 100644 config/routes/web_profiler.yaml create mode 100644 config/secrets/dev/dev.OPENAI_API_KEY.66949e.php create mode 100644 config/secrets/dev/dev.encrypt.public.php create mode 100644 config/secrets/dev/dev.list.php create mode 100644 config/services.yaml create mode 100644 demo.png create mode 100644 docker/Dockerfile create mode 100644 docker/config.json create mode 100644 importmap.php create mode 100644 phpstan.dist.neon create mode 100644 phpunit.xml create mode 100644 public/favicon.ico create mode 100644 public/index.php create mode 100644 public/wiki.png create mode 100644 src/Chat/Rag.php create mode 100644 src/Chat/Wikipedia.php create mode 100644 src/Chat/YouTube.php create mode 100644 src/Command/BlogEmbedCommand.php create mode 100644 src/Command/ChromaTestCommand.php create mode 100644 src/Kernel.php create mode 100644 src/ProfilerSubscriber.php create mode 100644 src/Twig/RagComponent.php create mode 100644 src/Twig/WikipediaComponent.php create mode 100644 src/Twig/YouTubeComponent.php create mode 100644 src/YouTube/TranscriptFetcher.php create mode 100644 symfony.lock create mode 100644 templates/base.html.twig create mode 100644 templates/chat/rag.html.twig create mode 100644 templates/chat/wikipedia.html.twig create mode 100644 templates/chat/youtube.html.twig create mode 100644 templates/components/_message.html.twig create mode 100644 templates/components/rag.html.twig create mode 100644 templates/components/wikipedia.html.twig create mode 100644 templates/components/youtube.html.twig create mode 100644 templates/index.html.twig create mode 100644 tests/bootstrap.php diff --git a/.env b/.env new file mode 100644 index 000000000..52c6ea228 --- /dev/null +++ b/.env @@ -0,0 +1,23 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# https://symfony.com/doc/current/configuration/secrets.html +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=ccb9dca72dce53c683eaaf775bfdb253 +###< symfony/framework-bundle ### + +CHROMADB_HOST=chromadb +OPENAI_API_KEY=sk-... diff --git a/.env.test b/.env.test new file mode 100644 index 000000000..7da36bf4a --- /dev/null +++ b/.env.test @@ -0,0 +1,7 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +PANTHER_APP_ENV=panther +PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots +OPENAI_API_KEY=sk-proj-testing1234 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c1d6b1c8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/dev/dev.decrypt.private.php +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### +###> symfony/asset-mapper ### +/public/assets/ +/assets/vendor/ +###< symfony/asset-mapper ### + +###> php-cs-fixer/shim ### +/.php-cs-fixer.php +/.php-cs-fixer.cache +###< php-cs-fixer/shim ### + +###> phpstan/phpstan ### +phpstan.neon +###< phpstan/phpstan ### + +###> phpunit/phpunit ### +.phpunit.cache +###< phpunit/phpunit ### + +chromadb diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 000000000..28d51adac --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,15 @@ +in(__DIR__) + ->exclude('var') + ->exclude('config/secrets') +; + +return (new PhpCsFixer\Config()) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) + ->setRules([ + '@Symfony' => true, + ]) + ->setFinder($finder) +; diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..99fed694e --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Christopher Hertel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..8956bce2e --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# LLM Chain - Symfony Demo Chatbot Application + +Simple Symfony demo application on top of [LLM Chain](https://github.com/php-llm/llm-chain) and its [integration bundle](https://github.com/php-llm/llm-chain-bundle). + +## Examples +![demo.png](demo.png) + +## Requirements + +What you need to run this demo: + +* Internet Connection +* Terminal & Browser +* [Git](https://git-scm.com/) & [GitHub Account](https://github.com) +* [Docker](https://www.docker.com/) with [Docker Compose Plugin](https://docs.docker.com/compose/) +* Your Favorite IDE or Editor + +## Technology + +This small demo sits on top of following technologies: + +* [PHP >= 8.4](https://www.php.net/releases/8.4/en.php) +* [Symfony 7.2 incl. Twig, Asset Mapper & UX](https://symfony.com/) +* [Bootstrap 5](https://getbootstrap.com/docs/5.0/getting-started/introduction/) +* [OpenAI's GPT & Embeddings](https://platform.openai.com/docs/overview) +* [ChromaDB Vector Store](https://www.trychroma.com/) +* [FrankenPHP](https://frankenphp.dev/) + +## Setup + +The setup is split into three parts, the Symfony application, the OpenAI configuration, and initializing the Chroma DB. + +### 1. Symfony App + +Checkout the repository, start the docker environment and install dependencies: +```shell +git clone git@github.com:php-llm/symfony-demo.git +cd symfony-demo +docker compose up -d +docker compose run composer install --no-scripts +``` + +Now you should be able to open https://localhost/ in your browser, +and the chatbot UI should be available for you to start chatting. + +### 2. OpenAI Configuration + +For using GPT and embedding models from OpenAI, you need to configure an OpenAI API key as environment variable. +This requires you to have an OpenAI account, create a valid API key and set it as `OPENAI_API_KEY` in `.env.local` file. + +Verify the success of this step by running the following command: +```shell +docker compose exec app bin/console debug:dotenv +``` + +You should be able to see the `OPENAI_API_KEY` in the list of environment variables. + +### 3. Chroma DB Initialization + +The Chroma DB is a vector store that is used to store embeddings of the chatbot's context. + +To initialize the Chroma DB, you need to run the following command: +```shell +docker compose exec app bin/console app:blog:embed -vv +``` + +Now you should be able to run the test command and get some results: +```shell +docker compose exec app bin/console app:chroma:test +``` + +**Don't forget to set up the project in your favorite IDE or editor.** + +## Functionality + +* The chatbot application is a simple and small Symfony 7.2 application. +* The UI is coupled to a Twig LiveComponent, that integrates different `Chat` implementations on top of the user's session. +* You can reset the chat context by hitting the `Reset` button in the top right corner. +* You find three different usage scenarios in the upper navbar. diff --git a/assets/app.js b/assets/app.js new file mode 100644 index 000000000..dda9570a0 --- /dev/null +++ b/assets/app.js @@ -0,0 +1,3 @@ +import './bootstrap.js'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import './styles/app.css'; diff --git a/assets/bootstrap.js b/assets/bootstrap.js new file mode 100644 index 000000000..d4e50c919 --- /dev/null +++ b/assets/bootstrap.js @@ -0,0 +1,5 @@ +import { startStimulusApp } from '@symfony/stimulus-bundle'; + +const app = startStimulusApp(); +// register any custom, 3rd party controllers here +// app.register('some_controller_name', SomeImportedController); diff --git a/assets/controllers.json b/assets/controllers.json new file mode 100644 index 000000000..6dd960d42 --- /dev/null +++ b/assets/controllers.json @@ -0,0 +1,30 @@ +{ + "controllers": { + "@symfony/ux-live-component": { + "live": { + "enabled": true, + "fetch": "eager", + "autoimport": { + "@symfony/ux-live-component/dist/live.min.css": true + } + } + }, + "@symfony/ux-turbo": { + "turbo-core": { + "enabled": true, + "fetch": "eager" + }, + "mercure-turbo-stream": { + "enabled": false, + "fetch": "eager" + } + }, + "@symfony/ux-typed": { + "typed": { + "enabled": true, + "fetch": "eager" + } + } + }, + "entrypoints": [] +} diff --git a/assets/controllers/chat_controller.js b/assets/controllers/chat_controller.js new file mode 100644 index 000000000..d7cc4b87b --- /dev/null +++ b/assets/controllers/chat_controller.js @@ -0,0 +1,59 @@ +import { Controller } from '@hotwired/stimulus'; +import { getComponent } from '@symfony/ux-live-component'; + +export default class extends Controller { + async initialize() { + this.component = await getComponent(this.element); + this.scrollToBottom(); + + const input = document.getElementById('chat-message'); + input.addEventListener('keypress', (event) => { + if (event.key === 'Enter') { + this.submitMessage(); + } + }); + input.focus(); + + const resetButton = document.getElementById('chat-reset'); + resetButton.addEventListener('click', (event) => { + this.component.action('reset'); + }); + + const submitButton = document.getElementById('chat-submit'); + submitButton.addEventListener('click', (event) => { + this.submitMessage(); + }); + + this.component.on('loading.state:started', (e,r) => { + if (r.actions.includes('reset')) { + return; + } + document.getElementById('welcome')?.remove(); + document.getElementById('loading-message').removeAttribute('class'); + this.scrollToBottom(); + }); + + this.component.on('loading.state:finished', () => { + document.getElementById('loading-message').setAttribute('class', 'd-none'); + }); + + this.component.on('render:finished', () => { + this.scrollToBottom(); + }); + }; + + submitMessage() { + const input = document.getElementById('chat-message'); + const message = input.value; + document + .getElementById('loading-message') + .getElementsByClassName('user-message')[0].innerHTML = message; + this.component.action('submit', { message }); + input.value = ''; + } + + scrollToBottom() { + const chatBody = document.getElementById('chat-body'); + chatBody.scrollTop = chatBody.scrollHeight; + } +} diff --git a/assets/controllers/wikipedia_controller.js b/assets/controllers/wikipedia_controller.js new file mode 100644 index 000000000..d7cc4b87b --- /dev/null +++ b/assets/controllers/wikipedia_controller.js @@ -0,0 +1,59 @@ +import { Controller } from '@hotwired/stimulus'; +import { getComponent } from '@symfony/ux-live-component'; + +export default class extends Controller { + async initialize() { + this.component = await getComponent(this.element); + this.scrollToBottom(); + + const input = document.getElementById('chat-message'); + input.addEventListener('keypress', (event) => { + if (event.key === 'Enter') { + this.submitMessage(); + } + }); + input.focus(); + + const resetButton = document.getElementById('chat-reset'); + resetButton.addEventListener('click', (event) => { + this.component.action('reset'); + }); + + const submitButton = document.getElementById('chat-submit'); + submitButton.addEventListener('click', (event) => { + this.submitMessage(); + }); + + this.component.on('loading.state:started', (e,r) => { + if (r.actions.includes('reset')) { + return; + } + document.getElementById('welcome')?.remove(); + document.getElementById('loading-message').removeAttribute('class'); + this.scrollToBottom(); + }); + + this.component.on('loading.state:finished', () => { + document.getElementById('loading-message').setAttribute('class', 'd-none'); + }); + + this.component.on('render:finished', () => { + this.scrollToBottom(); + }); + }; + + submitMessage() { + const input = document.getElementById('chat-message'); + const message = input.value; + document + .getElementById('loading-message') + .getElementsByClassName('user-message')[0].innerHTML = message; + this.component.action('submit', { message }); + input.value = ''; + } + + scrollToBottom() { + const chatBody = document.getElementById('chat-body'); + chatBody.scrollTop = chatBody.scrollHeight; + } +} diff --git a/assets/controllers/youtube_controller.js b/assets/controllers/youtube_controller.js new file mode 100644 index 000000000..5765367ec --- /dev/null +++ b/assets/controllers/youtube_controller.js @@ -0,0 +1,95 @@ +import { Controller } from '@hotwired/stimulus'; +import { getComponent } from '@symfony/ux-live-component'; + +export default class extends Controller { + async initialize() { + this.component = await getComponent(this.element); + this.scrollToBottom(); + + const input = document.getElementById('chat-message'); + input.addEventListener('keypress', (event) => { + if (event.key === 'Enter') { + this.submitMessage(); + } + }); + input.focus(); + + const resetButton = document.getElementById('chat-reset'); + resetButton.addEventListener('click', (event) => { + this.component.action('reset'); + }); + + if (document.getElementById('welcome')) { + this.initStartButton(); + } + + const submitButton = document.getElementById('chat-submit'); + submitButton.addEventListener('click', (event) => { + this.submitMessage(); + }); + + this.component.on('loading.state:started', (e,r) => { + if (r.actions.includes('reset') || r.actions.includes('start')) { + return; + } + document.getElementById('welcome')?.remove(); + document.getElementById('loading-message').removeAttribute('class'); + this.scrollToBottom(); + }); + + this.component.on('loading.state:finished', () => { + document.getElementById('loading-message').setAttribute('class', 'd-none'); + }); + + this.component.on('render:finished', () => { + this.scrollToBottom(); + if (document.getElementById('welcome')) { + this.initStartButton(); + } + }); + }; + + initStartButton() { + const input = document.getElementById('youtube-id'); + input.disabled = false; + input.value = ''; + input.focus(); + const startButton = document.getElementById('chat-start'); + startButton.disabled = false; + startButton.addEventListener('click', (event) => { + this.start(); + }); + document.getElementById('chat-message').disabled = true; + document.getElementById('chat-submit').disabled = true; + } + + start() { + const input = document.getElementById('youtube-id'); + input.disabled = true; + const videoId = input.value; + const button = document.getElementById('chat-start'); + button.disabled = true; + button.innerHTML = 'Loading...'; + document + .getElementById('loading-message') + .getElementsByClassName('user-message')[0].innerHTML = 'Starting chat for video ID: ' + videoId; + this.component.action('start', { videoId }); + document.getElementById('chat-message').disabled = false; + document.getElementById('chat-submit').disabled = false; + } + + submitMessage() { + const input = document.getElementById('chat-message'); + const message = input.value; + document + .getElementById('loading-message') + .getElementsByClassName('user-message')[0].innerHTML = message; + this.component.action('submit', { message }); + input.value = ''; + } + + scrollToBottom() { + const chatBody = document.getElementById('chat-body'); + chatBody.scrollTop = chatBody.scrollHeight; + } +} diff --git a/assets/icons/bi/youtube.svg b/assets/icons/bi/youtube.svg new file mode 100644 index 000000000..6a25be026 --- /dev/null +++ b/assets/icons/bi/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/entypo/chat.svg b/assets/icons/entypo/chat.svg new file mode 100644 index 000000000..b1f6939e2 --- /dev/null +++ b/assets/icons/entypo/chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/fluent/bot-24-filled.svg b/assets/icons/fluent/bot-24-filled.svg new file mode 100644 index 000000000..5e27ff9c0 --- /dev/null +++ b/assets/icons/fluent/bot-24-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/material-symbols/cancel.svg b/assets/icons/material-symbols/cancel.svg new file mode 100644 index 000000000..7a69e8e34 --- /dev/null +++ b/assets/icons/material-symbols/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/mdi/github.svg b/assets/icons/mdi/github.svg new file mode 100644 index 000000000..ae68105b7 --- /dev/null +++ b/assets/icons/mdi/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/mdi/wikipedia.svg b/assets/icons/mdi/wikipedia.svg new file mode 100644 index 000000000..4b79068e1 --- /dev/null +++ b/assets/icons/mdi/wikipedia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/mingcute/ai-fill.svg b/assets/icons/mingcute/ai-fill.svg new file mode 100644 index 000000000..03ddb45f3 --- /dev/null +++ b/assets/icons/mingcute/ai-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/mingcute/send-fill.svg b/assets/icons/mingcute/send-fill.svg new file mode 100644 index 000000000..d1229e823 --- /dev/null +++ b/assets/icons/mingcute/send-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/solar/code-linear.svg b/assets/icons/solar/code-linear.svg new file mode 100644 index 000000000..96a0d8ece --- /dev/null +++ b/assets/icons/solar/code-linear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/solar/user-bold.svg b/assets/icons/solar/user-bold.svg new file mode 100644 index 000000000..48e20b78b --- /dev/null +++ b/assets/icons/solar/user-bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/symfony.svg b/assets/icons/symfony.svg new file mode 100644 index 000000000..93fb329cc --- /dev/null +++ b/assets/icons/symfony.svg @@ -0,0 +1 @@ + diff --git a/assets/styles/app.css b/assets/styles/app.css new file mode 100644 index 000000000..a3a7dcd3f --- /dev/null +++ b/assets/styles/app.css @@ -0,0 +1,157 @@ +body { + min-height: 100vh; + &.rag, .rag .card-img-top { + background: rgb(220,139,110); + background: linear-gradient(0deg, rgba(220,139,110,1) 0%, rgba(244,233,115,1) 100%); + } + &.youtube, .youtube .card-img-top { + background: rgb(34,34,34); + background: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(71, 71, 71) 100%); + } + &.wikipedia, .wikipedia .card-img-top { + background: url('/wiki.png') no-repeat right 50px bottom 50px fixed, linear-gradient(0deg, rgb(246, 246, 246) 0%, rgb(197, 197, 197) 100%); + } + + footer, footer a { + color: #6c757d; + } +} + +.index { + .card-img-top { + text-align: center; + + .youtube & { + color: #ff0000; + } + } +} + +.chat { + .card { + border: 1px solid #bcbcbc; + background-color: rgba(250, 250, 250, 0.9); + } + + .card-header { + background: #efefef; + + svg { + margin-top: -2px; + } + } + + .card-body { + height: 700px; + + .wikipedia & { + background-image: linear-gradient(135deg, #f2f2f2 16.67%, #ebebeb 16.67%, #ebebeb 50%, #f2f2f2 50%, #f2f2f2 66.67%, #ebebeb 66.67%, #ebebeb 100%); + background-size: 21.21px 21.21px; + } + + .user-message { + border-radius: 10px 10px 0 10px; + color: #292929; + + .rag & { + background: #f4e973; + } + + .youtube & { + background: #3e2926; + color: #fafafa; + } + + .wikipedia & { + background: #ffffff; + } + } + + .bot-message { + border-radius: 10px 10px 10px 0; + + .rag & { + background: #dc8b6e; + } + + .youtube & { + background: #df3535; + } + + .wikipedia & { + background: #ffffff; + color: #292929 !important; + } + + color: #fff; + + p { + margin-bottom: 0; + } + } + + .avatar { + width: 50px; + height: 50px; + border: 2px solid white; + + .rag &.bot { + outline: 1px solid #ffdacc; + background: #ffdacc; + } + + .rag &.user { + outline: 1px solid #fffad1; + background: #fffad1; + } + + .youtube &.bot { + outline: 1px solid #ffcccc; + background: #ffcccc; + } + + .youtube &.user { + outline: 1px solid #9e8282; + background: #9e8282; + } + + .wikipedia &.bot, .wikipedia &.user { + outline: 1px solid #eaeaea; + background: #eaeaea; + } + } + } + + .card-footer { + background: #efefef; + + input:focus { + outline: none !important; + box-shadow: none !important; + } + } + + #welcome { + h4 { + .rag & { + color: #f97b62; + } + + .youtube & { + color: #ff0000; + } + } + } + + #chat-reset, #chat-submit { + .rag &:hover { + background: #f97b62; + border-color: #f97b62; + } + + .youtube &:hover { + background: #ff0000; + border-color: #ff0000; + } + } +} diff --git a/bin/console b/bin/console new file mode 100755 index 000000000..d8d530e2c --- /dev/null +++ b/bin/console @@ -0,0 +1,21 @@ +#!/usr/bin/env php +=8.4", + "ext-ctype": "*", + "ext-iconv": "*", + "codewithkyrian/chromadb-php": "^0.3.0", + "league/commonmark": "^2.6", + "php-llm/llm-chain": "^0.9.3", + "php-llm/llm-chain-bundle": "dev-feat-prepare-0.8", + "phpdocumentor/reflection-docblock": "^5.5", + "phpstan/phpdoc-parser": "^1.33", + "runtime/frankenphp-symfony": "^0.2.0", + "symfony/asset": "7.2.*", + "symfony/asset-mapper": "7.2.*", + "symfony/clock": "7.2.*", + "symfony/console": "7.2.*", + "symfony/css-selector": "7.2.*", + "symfony/dom-crawler": "7.2.*", + "symfony/dotenv": "7.2.*", + "symfony/flex": "^2.4", + "symfony/framework-bundle": "7.2.*", + "symfony/http-client": "7.2.*", + "symfony/monolog-bundle": "^3.10", + "symfony/property-access": "7.2.*", + "symfony/property-info": "7.2.*", + "symfony/runtime": "7.2.*", + "symfony/serializer": "7.2.*", + "symfony/twig-bundle": "7.2.*", + "symfony/uid": "7.2.*", + "symfony/ux-icons": "^2.22", + "symfony/ux-live-component": "^2.22", + "symfony/ux-turbo": "^2.22", + "symfony/ux-typed": "^2.22", + "symfony/yaml": "7.2.*", + "twig/extra-bundle": "^3.16", + "twig/markdown-extra": "^3.16", + "twig/twig": "^3.16" + }, + "replace": { + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-mbstring": "*", + "symfony/polyfill-php72": "*", + "symfony/polyfill-php73": "*", + "symfony/polyfill-php74": "*", + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*", + "symfony/polyfill-php82": "*", + "symfony/polyfill-php83": "*", + "symfony/polyfill-php84": "*" + }, + "conflict": { + "symfony/symfony": "*" + }, + "require-dev": { + "nyholm/nsa": "^1.3", + "php-cs-fixer/shim": "^3.65", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.5", + "symfony/browser-kit": "7.2.*", + "symfony/debug-bundle": "7.2.*", + "symfony/stopwatch": "7.2.*", + "symfony/web-profiler-bundle": "7.2.*" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true, + "symfony/flex": true, + "symfony/runtime": true + }, + "platform": { + "php": "8.4.1" + }, + "sort-packages": true + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "7.2.*" + } + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "minimum-stability": "stable", + "prefer-stable": true, + "scripts": { + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ], + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd", + "importmap:install": "symfony-cmd" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..93b8b63c8 --- /dev/null +++ b/composer.lock @@ -0,0 +1,8399 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b3492f124ff5c8349f283273cd9e42fc", + "packages": [ + { + "name": "codewithkyrian/chromadb-php", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/CodeWithKyrian/chromadb-php.git", + "reference": "3f7ce55260a0f5b8afe078a7a6f21b3fc88ec53d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CodeWithKyrian/chromadb-php/zipball/3f7ce55260a0f5b8afe078a7a6f21b3fc88ec53d", + "reference": "3f7ce55260a0f5b8afe078a7a6f21b3fc88ec53d", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.0", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "pestphp/pest": "^2.19", + "symfony/var-dumper": "^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codewithkyrian\\ChromaDB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyrian Obikwelu", + "email": "kyrianobikwelu@gmail.com" + } + ], + "description": "A PHP client for the Chroma Open Source Embedding Database", + "keywords": [ + "chroma", + "chromadb", + "database", + "embedding", + "open-source", + "php", + "search", + "semantic", + "vectors" + ], + "support": { + "issues": "https://github.com/CodeWithKyrian/chromadb-php/issues", + "source": "https://github.com/CodeWithKyrian/chromadb-php/tree/0.3.0" + }, + "time": "2024-06-15T23:19:51+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "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.4" + }, + "time": "2024-12-07T21:18:45+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-24T11:22:20+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2024-10-17T10:06:22+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "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" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2024-07-18T11:15:46+00:00" + }, + { + "name": "league/commonmark", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "d150f911e0079e90ae3c106734c93137c184f932" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932", + "reference": "d150f911e0079e90ae3c106734c93137c184f932", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2024-12-07T15:34:16+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.8.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "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", + "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.8.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2024-12-05T17:15:07+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.4" + }, + "require-dev": { + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.2" + }, + "time": "2024-10-06T23:10:23+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.4" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.5", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.5" + }, + "time": "2024-08-07T15:39:19+00:00" + }, + { + "name": "php-llm/llm-chain", + "version": "0.9.3", + "source": { + "type": "git", + "url": "https://github.com/php-llm/llm-chain.git", + "reference": "9936ac9ae8de5797c42f2de25858e07038f12dc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-llm/llm-chain/zipball/9936ac9ae8de5797c42f2de25858e07038f12dc4", + "reference": "9936ac9ae8de5797c42f2de25858e07038f12dc4", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.4", + "psr/cache": "^3.0", + "psr/log": "^3.0", + "symfony/clock": "^6.4 || ^7.1", + "symfony/http-client": "^6.4 || ^7.1", + "symfony/property-access": "^6.4 || ^7.1", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.1", + "symfony/type-info": "^6.4 || ^7.1", + "symfony/uid": "^6.4 || ^7.1", + "webmozart/assert": "^1.11" + }, + "conflict": { + "mongodb/mongodb": "<1.20", + "phpdocumentor/reflection-docblock": ">=5.6" + }, + "require-dev": { + "codewithkyrian/chromadb-php": "^0.2.1", + "mongodb/mongodb": "^1.20", + "php-cs-fixer/shim": "^3.64", + "phpstan/phpstan": "^1.12", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^11.3", + "probots-io/pinecone-php": "^1.0", + "rector/rector": "^1.2", + "symfony/console": "^6.4 || ^7.1", + "symfony/css-selector": "^6.4 || ^7.1", + "symfony/dom-crawler": "^6.4 || ^7.1", + "symfony/dotenv": "^6.4 || ^7.1", + "symfony/finder": "^6.4 || ^7.1", + "symfony/process": "^6.4 || ^7.1", + "symfony/var-dumper": "^6.4 || ^7.1" + }, + "suggest": { + "codewithkyrian/chromadb-php": "For using the ChromaDB as retrieval vector store.", + "mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.", + "probots-io/pinecone-php": "For using the Pinecone as retrieval vector store.", + "symfony/css-selector": "For using the YouTube transcription tool.", + "symfony/dom-crawler": "For using the YouTube transcription tool." + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpLlm\\LlmChain\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + } + ], + "description": "A slim PHP component with tooling around LLMs.", + "support": { + "issues": "https://github.com/php-llm/llm-chain/issues", + "source": "https://github.com/php-llm/llm-chain/tree/0.9.3" + }, + "time": "2024-12-08T01:31:09+00:00" + }, + { + "name": "php-llm/llm-chain-bundle", + "version": "dev-feat-prepare-0.8", + "source": { + "type": "git", + "url": "https://github.com/php-llm/llm-chain-bundle.git", + "reference": "5db2d4643339e7dce884a6caea61e39b55ebc23a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-llm/llm-chain-bundle/zipball/5db2d4643339e7dce884a6caea61e39b55ebc23a", + "reference": "5db2d4643339e7dce884a6caea61e39b55ebc23a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "php-llm/llm-chain": "^0.9.3", + "symfony/config": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0" + }, + "require-dev": { + "php-cs-fixer/shim": "^3.64", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.3" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "PhpLlm\\LlmChainBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + } + ], + "description": "Symfony integration bundle for php-llm/llm-chain", + "support": { + "issues": "https://github.com/php-llm/llm-chain-bundle/issues", + "source": "https://github.com/php-llm/llm-chain-bundle/tree/feat-prepare-0.8" + }, + "time": "2024-12-08T16:49:35+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.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "0c70d2c566e899666f367ab7b80986beb3581e6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/0c70d2c566e899666f367ab7b80986beb3581e6f", + "reference": "0c70d2c566e899666f367ab7b80986beb3581e6f", + "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", + "webmozart/assert": "^1.9.1" + }, + "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.5.1" + }, + "time": "2024-11-06T11:58:54+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "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.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.33.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "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/1.33.0" + }, + "time": "2024-10-13T11:25:22+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": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "runtime/frankenphp-symfony", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-runtime/frankenphp-symfony.git", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-runtime/frankenphp-symfony/zipball/56822c3631d9522a3136a4c33082d006bdfe4bad", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/runtime": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Runtime\\FrankenPhpSymfony\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.dev" + } + ], + "description": "FrankenPHP runtime for Symfony", + "support": { + "issues": "https://github.com/php-runtime/frankenphp-symfony/issues", + "source": "https://github.com/php-runtime/frankenphp-symfony/tree/0.2.0" + }, + "funding": [ + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-12-12T12:06:11+00:00" + }, + { + "name": "symfony/asset", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/cb926cd59fefa1f9b4900b3695f0f846797ba5c0", + "reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/asset-mapper", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset-mapper.git", + "reference": "ffb733232bb6bb85ef6a994f47c817e7c2ecab9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/ffb733232bb6bb85ef6a994f47c817e7c2ecab9c", + "reference": "ffb733232bb6bb85ef6a994f47c817e7c2ecab9c", + "shasum": "" + }, + "require": { + "composer/semver": "^3.0", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^7.1", + "symfony/http-client": "^6.4|^7.0" + }, + "conflict": { + "symfony/framework-bundle": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/event-dispatcher-contracts": "^3.0", + "symfony/finder": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\AssetMapper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset-mapper/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-20T11:17:29+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a", + "reference": "2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "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": "dev-master", + "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", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.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.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T15:21:05+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "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.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "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": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/config", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "bcd3c4adf0144dee5011bb35454728c38adec055" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/bcd3c4adf0144dee5011bb35454728c38adec055", + "reference": "bcd3c4adf0144dee5011bb35454728c38adec055", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-04T11:36:24+00:00" + }, + { + "name": "symfony/console", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T14:24:19+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "a475747af1a1c98272a5471abc35f3da81197c5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a475747af1a1c98272a5471abc35f3da81197c5d", + "reference": "a475747af1a1c98272a5471abc35f3da81197c5d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T15:45:00+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "b176e1f1f550ef44c94eb971bf92488de08f7c6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b176e1f1f550ef44c94eb971bf92488de08f7c6b", + "reference": "b176e1f1f550ef44c94eb971bf92488de08f7c6b", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T16:15:23+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "28347a897771d0c28e99b75166dd2689099f3045" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/28347a897771d0c28e99b75166dd2689099f3045", + "reference": "28347a897771d0c28e99b75166dd2689099f3045", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-27T11:18:42+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/672b3dd1ef8b87119b446d67c58c106c43f965fe", + "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-05T15:35:02+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-23T06:56:12+00:00" + }, + { + "name": "symfony/flex", + "version": "v2.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/92f4fba342161ff36072bd3b8e0b3c6c23160402", + "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.1", + "php": ">=8.0" + }, + "conflict": { + "composer/semver": "<1.7.2" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Flex\\Flex" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "support": { + "issues": "https://github.com/symfony/flex/issues", + "source": "https://github.com/symfony/flex/tree/v2.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-07T08:51:54+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "a8d0da4110fe643ab3cde7c938a03e222fe787c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a8d0da4110fe643ab3cde7c938a03e222fe787c6", + "reference": "a8d0da4110fe643ab3cde7c938a03e222fe787c6", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^7.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^6.4|^7.0" + }, + "conflict": { + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<6.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", + "symfony/security-core": "<6.4", + "symfony/security-csrf": "<7.2", + "symfony/serializer": "<7.1", + "symfony/stopwatch": "<6.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/webhook": "<7.2", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/webhook": "^7.2", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-20T16:27:35+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "955e43336aff03df1e8a8e17daefabb0127a313b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/955e43336aff03df1e8a8e17daefabb0127a313b", + "reference": "955e43336aff03df1e8a8e17daefabb0127a313b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.3|^3.5.1", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "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 powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-29T08:22:02+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9", + "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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 HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T12:02:18+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e88a66c3997859532bc2ddd6dd8f35aba2711744", + "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T18:58:46+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", + "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-29T08:42:40+00:00" + }, + { + "name": "symfony/monolog-bridge", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d", + "reference": "bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d", + "shasum": "" + }, + "require": { + "monolog/monolog": "^3", + "php": ">=8.2", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Monolog\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Monolog with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/monolog-bridge/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-14T18:16:08+00:00" + }, + { + "name": "symfony/monolog-bundle", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bundle.git", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "shasum": "" + }, + "require": { + "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0", + "php": ">=7.2.5", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MonologBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony MonologBundle", + "homepage": "https://symfony.com", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/symfony/monolog-bundle/issues", + "source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-06T17:08:13+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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 for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "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 for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/property-access", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", + "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-26T12:28:35+00:00" + }, + { + "name": "symfony/property-info", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "b00580d9d7c9654e1df95df85105d0da67418b3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/b00580d9d7c9654e1df95df85105d0da67418b3f", + "reference": "b00580d9d7c9654e1df95df85105d0da67418b3f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "^7.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-27T09:50:52+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T11:08:51+00:00" + }, + { + "name": "symfony/runtime", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "2c350568f3eaccb25fbbbf962bd67cde273121a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/2c350568f3eaccb25fbbbf962bd67cde273121a7", + "reference": "2c350568f3eaccb25fbbbf962bd67cde273121a7", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" + }, + "conflict": { + "symfony/dotenv": "<6.4" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "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": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:43:25+00:00" + }, + { + "name": "symfony/serializer", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", + "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "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/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T15:21:05+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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 writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/stimulus-bundle", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/stimulus-bundle.git", + "reference": "e13034d428354023c82a1db108d40fdf6cec2d36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/e13034d428354023c82a1db108d40fdf6cec2d36", + "reference": "e13034d428354023c82a1db108d40fdf6cec2d36", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^2.0|^3.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "twig/twig": "^2.15.3|^3.8" + }, + "require-dev": { + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "zenstruck/browser": "^1.4" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\UX\\StimulusBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Integration with your Symfony app & Stimulus!", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-06T14:30:33+00:00" + }, + { + "name": "symfony/string", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "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", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "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 an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T13:31:26+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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 translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "9958f5a5b6640734fe4b24c18897191f77a02c61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9958f5a5b6640734fe4b24c18897191f77a02c61", + "reference": "9958f5a5b6640734fe4b24c18897191f77a02c61", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.12" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T14:26:33+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "cd2be4563afaef5285bb6e0a06c5445e644a5c01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/cd2be4563afaef5285bb6e0a06c5445e644a5c01", + "reference": "cd2be4563afaef5285bb6e0a06c5445e644a5c01", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "conflict": { + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-23T08:11:15+00:00" + }, + { + "name": "symfony/type-info", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "e0bfd95bceb3886c59487828537691aecb7d9c6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/e0bfd95bceb3886c59487828537691aecb7d9c6b", + "reference": "e0bfd95bceb3886c59487828537691aecb7d9c6b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.0", + "symfony/dependency-injection": "<6.4" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-18T09:51:31+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/ux-icons", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-icons.git", + "reference": "3a6fd4293fc200530b09960c41941a71354bc5fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-icons/zipball/3a6fd4293fc200530b09960c41941a71354bc5fc", + "reference": "3a6fd4293fc200530b09960c41941a71354bc5fc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0" + }, + "conflict": { + "symfony/flex": "<1.13", + "symfony/ux-twig-component": "<2.21" + }, + "require-dev": { + "psr/log": "^2|^3", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "6.4|^7.0", + "symfony/phpunit-bridge": "^6.3|^7.0", + "symfony/ux-twig-component": "^2.14", + "zenstruck/console-test": "^1.5" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Icons\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Bond", + "email": "kevinbond@gmail.com" + }, + { + "name": "Simon André", + "email": "smn.andre@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Renders local and remote SVG icons in your Twig templates.", + "homepage": "https://symfony.com", + "keywords": [ + "icons", + "svg", + "symfony-ux", + "twig" + ], + "support": { + "source": "https://github.com/symfony/ux-icons/tree/v2.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-04T11:34:13+00:00" + }, + { + "name": "symfony/ux-live-component", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-live-component.git", + "reference": "060e0c64e64125a4dfbf37dec281157faade1feb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-live-component/zipball/060e0c64e64125a4dfbf37dec281157faade1feb", + "reference": "060e0c64e64125a4dfbf37dec281157faade1feb", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/property-access": "^5.4.5|^6.0|^7.0", + "symfony/stimulus-bundle": "^2.9", + "symfony/ux-twig-component": "^2.8", + "twig/twig": "^3.8.0" + }, + "conflict": { + "symfony/config": "<5.4.0" + }, + "require-dev": { + "doctrine/annotations": "^1.0", + "doctrine/collections": "^1.6.8|^2.0", + "doctrine/doctrine-bundle": "^2.4.3", + "doctrine/orm": "^2.9.4", + "doctrine/persistence": "^2.5.2|^3.0", + "phpdocumentor/reflection-docblock": "5.x-dev", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/options-resolver": "^5.4|^6.0|^7.0", + "symfony/phpunit-bridge": "^6.1|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/security-bundle": "^5.4|^6.0|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/validator": "^5.4|^6.0|^7.0", + "zenstruck/browser": "^1.2.0", + "zenstruck/foundry": "^2.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\LiveComponent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Live components for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "components", + "symfony-ux", + "twig" + ], + "support": { + "source": "https://github.com/symfony/ux-live-component/tree/v2.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T10:13:15+00:00" + }, + { + "name": "symfony/ux-turbo", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-turbo.git", + "reference": "97718ea4bca26f0db843c3c0de338d6900c5a002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/97718ea4bca26f0db843c3c0de338d6900c5a002", + "reference": "97718ea4bca26f0db843c3c0de338d6900c5a002", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/stimulus-bundle": "^2.9.1" + }, + "conflict": { + "symfony/flex": "<1.13" + }, + "require-dev": { + "dbrekelmans/bdi": "dev-main", + "doctrine/doctrine-bundle": "^2.4.3", + "doctrine/orm": "^2.8 | 3.0", + "phpstan/phpstan": "^1.10", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/debug-bundle": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/mercure-bundle": "^0.3.7", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/panther": "^2.1", + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|6.3.*|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/ux-twig-component": "^2.21", + "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Turbo\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.fr" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Hotwire Turbo integration for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "hotwire", + "javascript", + "mercure", + "symfony-ux", + "turbo", + "turbo-stream" + ], + "support": { + "source": "https://github.com/symfony/ux-turbo/tree/v2.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-05T14:25:02+00:00" + }, + { + "name": "symfony/ux-twig-component", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-twig-component.git", + "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/9b347f6ca2d9e18cee630787f0a6aa453982bf18", + "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "twig/twig": "^3.8" + }, + "conflict": { + "symfony/config": "<5.4.0" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/phpunit-bridge": "^6.0|^7.0", + "symfony/stimulus-bundle": "^2.9.1", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/webpack-encore-bundle": "^1.15" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\TwigComponent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Twig components for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "components", + "symfony-ux", + "twig" + ], + "support": { + "source": "https://github.com/symfony/ux-twig-component/tree/v2.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T18:05:50+00:00" + }, + { + "name": "symfony/ux-typed", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-typed.git", + "reference": "07b8b33a68a66b20dd4df8e3f9b9618e74a786f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-typed/zipball/07b8b33a68a66b20dd4df8e3f9b9618e74a786f1", + "reference": "07b8b33a68a66b20dd4df8e3f9b9618e74a786f1", + "shasum": "" + }, + "conflict": { + "symfony/flex": "<1.13" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Typed\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "email": "contact@spomky-labs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Typed integration for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/ux-typed/tree/v2.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-05T14:25:02+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "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" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-08T15:48:14+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.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.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-18T07:58:17+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "099581e99f557e9f16b43c5916c26380b54abb22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22", + "reference": "099581e99f557e9f16b43c5916c26380b54abb22", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-23T06:56:12+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.16.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/9746573ca4bc1cd03a767a183faadaf84e0c31fa", + "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/framework-bundle": "^5.4|^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0", + "twig/twig": "^3.2|^4.0" + }, + "require-dev": { + "league/commonmark": "^1.0|^2.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^3.0", + "twig/html-extra": "^3.0", + "twig/inky-extra": "^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.0", + "twig/string-extra": "^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Twig\\Extra\\TwigExtraBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Symfony bundle for extra Twig extensions", + "homepage": "https://twig.symfony.com", + "keywords": [ + "bundle", + "extra", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.16.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-09-26T19:22:23+00:00" + }, + { + "name": "twig/markdown-extra", + "version": "v3.16.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/markdown-extra.git", + "reference": "25f23c02936f8c7157a8413154c06a462c9c20d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/25f23c02936f8c7157a8413154c06a462c9c20d3", + "reference": "25f23c02936f8c7157a8413154c06a462c9c20d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "erusev/parsedown": "^1.7", + "league/commonmark": "^1.0|^2.0", + "league/html-to-markdown": "^4.8|^5.0", + "michelf/php-markdown": "^1.8|^2.0", + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Twig\\Extra\\Markdown\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for Markdown", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "markdown", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/markdown-extra/tree/v3.16.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-09-03T20:17:35+00:00" + }, + { + "name": "twig/twig", + "version": "v3.16.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "475ad2dc97d65d8631393e721e7e44fb544f0561" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/475ad2dc97d65d8631393e721e7e44fb544f0561", + "reference": "475ad2dc97d65d8631393e721e7e44fb544f0561", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.16.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-11-29T08:27:05+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@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/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-11-08T17:47:46+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.3.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "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.0-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.3.1" + }, + "time": "2024-10-08T18:51:32+00:00" + }, + { + "name": "nyholm/nsa", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/NSA.git", + "reference": "c264c17ed2aa8251c64ad289442ed53f64cdb283" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/NSA/zipball/c264c17ed2aa8251c64ad289442ed53f64cdb283", + "reference": "c264c17ed2aa8251c64ad289442ed53f64cdb283", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "webmozart/assert": "^1.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "description": "See everything and do whatever you want. No privacy rule will stop us. Used in tests, debugging and fixtures to access properties and methods.", + "homepage": "https://tnyholm.se", + "keywords": [ + "Fixture", + "debug", + "reflection", + "test" + ], + "support": { + "issues": "https://github.com/Nyholm/NSA/issues", + "source": "https://github.com/Nyholm/NSA/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2021-07-15T18:25:37+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.65.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "4983ec79b9dee926695ac324ea6e8d291935525d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/4983ec79b9dee926695ac324ea6e8d291935525d", + "reference": "4983ec79b9dee926695ac324ea6e8d291935525d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "replace": { + "friendsofphp/php-cs-fixer": "self.version" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer", + "php-cs-fixer.phar" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/PHP-CS-Fixer/shim/issues", + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.65.0" + }, + "time": "2024-11-25T00:39:41+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "46b4d3529b12178112d9008337beda0cc2a1a6b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46b4d3529b12178112d9008337beda0cc2a1a6b4", + "reference": "46b4d3529b12178112d9008337beda0cc2a1a6b4", + "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" + ], + "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": "2024-11-28T22:19:37+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.3.1", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.4.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T06:21:38+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0569902506a6c0878930b87ea79ec3b50ea563f7", + "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.7", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.1", + "sebastian/comparator": "^6.2.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.0", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.0" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-12-06T05:57:38+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", + "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:44:28+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-31T05:30:08+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:54:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-05T09:17:50+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-17T13:12:04+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "8d64d17e198082f8f198d023a6b634e7b5fdda94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/8d64d17e198082f8f198d023a6b634e7b5fdda94", + "reference": "8d64d17e198082f8f198d023a6b634e7b5fdda94", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/dom-crawler": "^6.4|^7.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/debug-bundle", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug-bundle.git", + "reference": "2dade0d1415c08b627379b5ec214ec8424cb2e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/2dade0d1415c08b627379b5ec214ec8424cb2e32", + "reference": "2dade0d1415c08b627379b5ec214ec8424cb2e32", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "php": ">=8.2", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/web-profiler-bundle": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\DebugBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug-bundle/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "696f418b0d722a4225e1c3d95489d262971ca924" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/696f418b0d722a4225e1c3d95489d262971ca924", + "reference": "696f418b0d722a4225e1c3d95489d262971ca924", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "8843019fa7140a4aa079f1a8d71fd010f61de5f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/8843019fa7140a4aa079f1a8d71fd010f61de5f2", + "reference": "8843019fa7140a4aa079f1a8d71fd010f61de5f2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "conflict": { + "symfony/form": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/serializer": "<7.2" + }, + "require-dev": { + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\WebProfilerBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a development tool that gives detailed information about the execution of any request", + "homepage": "https://symfony.com", + "keywords": [ + "dev" + ], + "support": { + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-19T10:12:55+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "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" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "php-llm/llm-chain-bundle": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.4", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "8.4.1" + }, + "plugin-api-version": "2.6.0" +} diff --git a/config/bundles.php b/config/bundles.php new file mode 100644 index 000000000..780d24bfd --- /dev/null +++ b/config/bundles.php @@ -0,0 +1,17 @@ + ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], + Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], + Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true], + Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true], + Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], + Symfony\UX\Turbo\TurboBundle::class => ['all' => true], + Symfony\UX\Icons\UXIconsBundle::class => ['all' => true], + PhpLlm\LlmChainBundle\LlmChainBundle::class => ['all' => true], + Symfony\UX\Typed\TypedBundle::class => ['all' => true], +]; diff --git a/config/packages/asset_mapper.yaml b/config/packages/asset_mapper.yaml new file mode 100644 index 000000000..f7653e97f --- /dev/null +++ b/config/packages/asset_mapper.yaml @@ -0,0 +1,11 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict + +when@prod: + framework: + asset_mapper: + missing_import_mode: warn diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml new file mode 100644 index 000000000..6899b7200 --- /dev/null +++ b/config/packages/cache.yaml @@ -0,0 +1,19 @@ +framework: + cache: + # Unique name of your app: used to compute stable namespaces for cache keys. + #prefix_seed: your_vendor_name/app_name + + # The "app" cache stores to the filesystem by default. + # The data in this cache should persist between deploys. + # Other options include: + + # Redis + #app: cache.adapter.redis + #default_redis_provider: redis://localhost + + # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) + #app: cache.adapter.apcu + + # Namespaced pools use the above "app" backend by default + #pools: + #my.dedicated.cache: null diff --git a/config/packages/chromadb.yaml b/config/packages/chromadb.yaml new file mode 100644 index 000000000..7a10c43b4 --- /dev/null +++ b/config/packages/chromadb.yaml @@ -0,0 +1,8 @@ +services: + Codewithkyrian\ChromaDB\Factory: + calls: + - withHost: ['%env(CHROMADB_HOST)%'] + + Codewithkyrian\ChromaDB\Client: + factory: ['@Codewithkyrian\ChromaDB\Factory', 'connect'] + lazy: true diff --git a/config/packages/debug.yaml b/config/packages/debug.yaml new file mode 100644 index 000000000..ad874afdd --- /dev/null +++ b/config/packages/debug.yaml @@ -0,0 +1,5 @@ +when@dev: + debug: + # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. + # See the "server:dump" command to start a new server. + dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml new file mode 100644 index 000000000..877eb25d1 --- /dev/null +++ b/config/packages/framework.yaml @@ -0,0 +1,16 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + #csrf_protection: true + + # Note that the session will be started ONLY if you read or write from it. + session: true + + #esi: true + #fragments: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/config/packages/llm_chain.yaml b/config/packages/llm_chain.yaml new file mode 100644 index 000000000..70a193f61 --- /dev/null +++ b/config/packages/llm_chain.yaml @@ -0,0 +1,40 @@ +llm_chain: + platform: + openai: + api_key: '%env(OPENAI_API_KEY)%' + chain: + rag: + model: + name: 'GPT' + version: 'gpt-4o-mini' + tools: + - 'PhpLlm\LlmChain\Chain\ToolBox\Tool\SimilaritySearch' + youtube: + model: + name: 'GPT' + version: 'gpt-4o-mini' + tools: [] + wikipedia: + model: + name: 'GPT' + version: 'gpt-4o-mini' + tools: + - 'PhpLlm\LlmChain\Chain\ToolBox\Tool\Wikipedia' + store: + chroma_db: + symfonycon: + host: '%env(CHROMADB_HOST)%' + collection: 'symfony_blog' + +services: + _defaults: + autowire: true + autoconfigure: true + + PhpLlm\LlmChain\Chain\ToolBox\Tool\Wikipedia: ~ + PhpLlm\LlmChain\Chain\ToolBox\Tool\SimilaritySearch: ~ + + # TODO: move to configuration + PhpLlm\LlmChain\Bridge\OpenAI\Embeddings: ~ + PhpLlm\LlmChain\Model\EmbeddingsModel: '@PhpLlm\LlmChain\Bridge\OpenAI\Embeddings' + diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml new file mode 100644 index 000000000..9db7d8a7f --- /dev/null +++ b/config/packages/monolog.yaml @@ -0,0 +1,62 @@ +monolog: + channels: + - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists + +when@dev: + monolog: + handlers: + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] + +when@test: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + channels: ["!event"] + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + +when@prod: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + buffer_size: 50 # How many messages should be saved? Prevent memory leaks + nested: + type: stream + path: php://stderr + level: debug + formatter: monolog.formatter.json + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine"] + deprecation: + type: stream + channels: [deprecation] + path: php://stderr + formatter: monolog.formatter.json diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml new file mode 100644 index 000000000..8166181c6 --- /dev/null +++ b/config/packages/routing.yaml @@ -0,0 +1,10 @@ +framework: + router: + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands + #default_uri: http://localhost + +when@prod: + framework: + router: + strict_requirements: null diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml new file mode 100644 index 000000000..3f795d921 --- /dev/null +++ b/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/config/packages/twig_component.yaml b/config/packages/twig_component.yaml new file mode 100644 index 000000000..fd17ac693 --- /dev/null +++ b/config/packages/twig_component.yaml @@ -0,0 +1,5 @@ +twig_component: + anonymous_template_directory: 'components/' + defaults: + # Namespace & directory for components + App\Twig\Components\: 'components/' diff --git a/config/packages/web_profiler.yaml b/config/packages/web_profiler.yaml new file mode 100644 index 000000000..b94611102 --- /dev/null +++ b/config/packages/web_profiler.yaml @@ -0,0 +1,17 @@ +when@dev: + web_profiler: + toolbar: true + intercept_redirects: false + + framework: + profiler: + only_exceptions: false + collect_serializer_data: true + +when@test: + web_profiler: + toolbar: false + intercept_redirects: false + + framework: + profiler: { collect: false } diff --git a/config/preload.php b/config/preload.php new file mode 100644 index 000000000..5ebcdb215 --- /dev/null +++ b/config/preload.php @@ -0,0 +1,5 @@ + null, +]; diff --git a/config/services.yaml b/config/services.yaml new file mode 100644 index 000000000..ceb2c1789 --- /dev/null +++ b/config/services.yaml @@ -0,0 +1,20 @@ +# This file is the entry point to configure your own services. +# Files in the packages/ subdirectory configure your dependencies. + +# Put parameters here that don't need to change on each machine where the app is deployed +# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration + +services: + # default configuration for services in *this* file + _defaults: + autowire: true # Automatically injects dependencies in your services. + autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. + + # makes classes in src/ available to be used as services + # this creates a service per class whose id is the fully-qualified class name + App\: + resource: '../src/' + exclude: + - '../src/DependencyInjection/' + - '../src/Entity/' + - '../src/Kernel.php' diff --git a/demo.png b/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..85fdbad35601bafc1a8480b0093e417fa3d1974b GIT binary patch literal 54356 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 literal 0 HcmV?d00001 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..1e6b36808 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,14 @@ +FROM unit:php8.3 + +RUN apt-get update \ + && apt-get install -y libpq-dev libicu-dev libonig-dev zip \ + && docker-php-ext-install intl mbstring + +COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer + +# application setup +COPY config.json /docker-entrypoint.d/config.json + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +EXPOSE 80 +CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] diff --git a/docker/config.json b/docker/config.json new file mode 100644 index 000000000..2b89107a1 --- /dev/null +++ b/docker/config.json @@ -0,0 +1,42 @@ +{ + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*" + ] + }, + "action": { + "pass": "applications/symfony/direct" + } + }, + { + "action": { + "share": "/var/www/app/public$uri", + "fallback": { + "pass": "applications/symfony/index" + } + } + } + ], + "applications": { + "symfony": { + "type": "php", + "targets": { + "direct": { + "root": "/var/www/app/public/" + }, + "index": { + "root": "/var/www/app/public/", + "script": "index.php" + } + } + } + } +} diff --git a/importmap.php b/importmap.php new file mode 100644 index 000000000..7a9b2550f --- /dev/null +++ b/importmap.php @@ -0,0 +1,44 @@ + [ + 'path' => './assets/app.js', + 'entrypoint' => true, + ], + '@symfony/ux-live-component' => [ + 'path' => './vendor/symfony/ux-live-component/assets/dist/live_controller.js', + ], + '@symfony/stimulus-bundle' => [ + 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', + ], + 'bootstrap' => [ + 'version' => '5.3.3', + ], + '@popperjs/core' => [ + 'version' => '2.11.8', + ], + 'bootstrap/dist/css/bootstrap.min.css' => [ + 'version' => '5.3.3', + 'type' => 'css', + ], + '@hotwired/stimulus' => [ + 'version' => '3.2.2', + ], + '@hotwired/turbo' => [ + 'version' => '8.0.4', + ], + 'typed.js' => [ + 'version' => '2.1.0', + ], +]; diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 000000000..5d757e15d --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,12 @@ +parameters: + level: 8 + paths: + - bin/ + - config/ + - public/ + - src/ + - tests/ + reportUnmatchedIgnoredErrors: false + typeAliases: + MessageArray: 'array{role: string, content: string}' + MessageList: 'list' diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 000000000..0eab86518 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + tests + + + + + + src + + + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8382adc6bb4fdff34e8c55891b3154c0e92121f0 GIT binary patch literal 15086 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 SaDC<`kjZV>x7N*-5PksJ&#cw} literal 0 HcmV?d00001 diff --git a/public/index.php b/public/index.php new file mode 100644 index 000000000..9982c218d --- /dev/null +++ b/public/index.php @@ -0,0 +1,9 @@ +o-Qjj_)i+gPQsGCs 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 P00000NkvXXu0mjfCM8$W literal 0 HcmV?d00001 diff --git a/src/Chat/Rag.php b/src/Chat/Rag.php new file mode 100644 index 000000000..22528ed3d --- /dev/null +++ b/src/Chat/Rag.php @@ -0,0 +1,63 @@ +requestStack->getSession()->get(self::SESSION_KEY, $messages); + } + + public function submitMessage(string $message): void + { + $messages = $this->loadMessages(); + + $messages[] = Message::ofUser($message); + $response = $this->chain->call($messages); + + assert($response instanceof TextResponse); + + $messages[] = Message::ofAssistant($response->getContent()); + + $this->saveMessages($messages); + } + + public function reset(): void + { + $this->requestStack->getSession()->remove(self::SESSION_KEY); + } + + private function saveMessages(MessageBag $messages): void + { + $this->requestStack->getSession()->set(self::SESSION_KEY, $messages); + } +} diff --git a/src/Chat/Wikipedia.php b/src/Chat/Wikipedia.php new file mode 100644 index 000000000..ca5f4d117 --- /dev/null +++ b/src/Chat/Wikipedia.php @@ -0,0 +1,56 @@ +requestStack->getSession()->get(self::SESSION_KEY, $default); + } + + public function submitMessage(string $message): void + { + $messages = $this->loadMessages(); + + $messages[] = Message::ofUser($message); + $response = $this->chain->call($messages); + + assert($response instanceof TextResponse); + + $messages[] = Message::ofAssistant($response->getContent()); + + $this->saveMessages($messages); + } + + public function reset(): void + { + $this->requestStack->getSession()->remove(self::SESSION_KEY); + } + + private function saveMessages(MessageBag $messages): void + { + $this->requestStack->getSession()->set(self::SESSION_KEY, $messages); + } +} diff --git a/src/Chat/YouTube.php b/src/Chat/YouTube.php new file mode 100644 index 000000000..ec731d3c6 --- /dev/null +++ b/src/Chat/YouTube.php @@ -0,0 +1,76 @@ +requestStack->getSession()->get(self::SESSION_KEY, new MessageBag()); + } + + public function start(string $videoId): void + { + $this->reset(); + $messages = $this->loadMessages(); + + $transcript = $this->transcriptFetcher->fetchTranscript($videoId); + $system = <<saveMessages($messages); + } + + public function submitMessage(string $message): void + { + $messages = $this->loadMessages(); + + $messages[] = Message::ofUser($message); + $response = $this->chain->call($messages); + + assert($response instanceof TextResponse); + + $messages[] = Message::ofAssistant($response->getContent()); + + $this->saveMessages($messages); + } + + public function reset(): void + { + $this->requestStack->getSession()->remove(self::SESSION_KEY); + } + + private function saveMessages(MessageBag $messages): void + { + $this->requestStack->getSession()->set(self::SESSION_KEY, $messages); + } +} diff --git a/src/Command/BlogEmbedCommand.php b/src/Command/BlogEmbedCommand.php new file mode 100644 index 000000000..ea5436e40 --- /dev/null +++ b/src/Command/BlogEmbedCommand.php @@ -0,0 +1,130 @@ +title('Loading RSS of Symfony blog as embeddings into ChromaDB'); + + $posts = $this->loadBlogPosts(); + $vectors = $this->createEmbeddings($posts); + $this->pushToChromaDB($posts, $vectors); + + $io->success('Symfony Blog Successfully Embedded!'); + + return Command::SUCCESS; + } + + /** + * @return list + */ + private function loadBlogPosts(): array + { + $response = $this->httpClient->request('GET', 'https://feeds.feedburner.com/symfony/blog'); + + $posts = []; + $crawler = new Crawler($response->getContent()); + $crawler->filter('item')->each(function (Crawler $node) use (&$posts) { + $title = $node->filter('title')->text(); + $posts[] = [ + 'id' => Uuid::v5(Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'), $title), + 'title' => $title, + 'link' => $node->filter('link')->text(), + 'description' => $node->filter('description')->text(), + 'content' => (new Crawler($node->filter('content\:encoded')->text()))->text(), + 'author' => $node->filter('dc\:creator')->text(), + 'date' => new \DateTimeImmutable($node->filter('pubDate')->text()), + ]; + }); + + return $posts; + } + + /** + * @param Post[] $posts + * + * @return Vector[] + */ + private function createEmbeddings(array $posts): array + { + $texts = []; + foreach ($posts as $post) { + $texts[] = <<format('Y-m-d')} + Description: {$post['description']} + {$post['content']} + TEXT; + } + + $response = $this->platform->request(new Embeddings(), $texts); + + assert($response instanceof AsyncResponse); + $response = $response->unwrap(); + assert($response instanceof VectorResponse); + + return $response->getContent(); + } + + /** + * @param Post[] $posts + * @param Vector[] $vectors + */ + private function pushToChromaDB(array $posts, array $vectors): void + { + $collection = $this->chromaClient->getOrCreateCollection('symfony_blog'); + + $ids = array_column($posts, 'id'); + $vectors = array_map(fn (Vector $vector) => $vector->getData(), $vectors); + + $collection->upsert($ids, $vectors, $posts); + } +} diff --git a/src/Command/ChromaTestCommand.php b/src/Command/ChromaTestCommand.php new file mode 100644 index 000000000..7ff37b504 --- /dev/null +++ b/src/Command/ChromaTestCommand.php @@ -0,0 +1,80 @@ +title('Testing Chroma DB Connection'); + + $io->comment('Connecting to Chroma DB ...'); + + // Check current ChromaDB version + $version = $this->chromaClient->version(); + + // Get WSC Collection + $collection = $this->chromaClient->getOrCreateCollection('symfony_blog'); + + $io->table(['Key', 'Value'], [ + ['ChromaDB Version', $version], + ['Collection Name', $collection->name], + ['Collection ID', $collection->id], + ['Total Documents', $collection->count()], + ]); + + $io->comment('Searching for content about "New Symfony Features" ...'); + + $platformResponse = $this->platform->request(new Embeddings(), 'New Symfony Features'); + assert($platformResponse instanceof AsyncResponse); + $platformResponse = $platformResponse->unwrap(); + assert($platformResponse instanceof VectorResponse); + $queryResponse = $collection->query( + queryEmbeddings: [$platformResponse->getContent()[0]->getData()], + nResults: 4, + ); + + if (1 === count($queryResponse->ids, COUNT_RECURSIVE)) { + $io->error('No results found!'); + + return Command::FAILURE; + } + + $io->table(['ID', 'Title'], [ + /* @phpstan-ignore-next-line */ + [$queryResponse->ids[0][0], $queryResponse->metadatas[0][0]['title']], + /* @phpstan-ignore-next-line */ + [$queryResponse->ids[0][1], $queryResponse->metadatas[0][1]['title']], + /* @phpstan-ignore-next-line */ + [$queryResponse->ids[0][2], $queryResponse->metadatas[0][2]['title']], + /* @phpstan-ignore-next-line */ + [$queryResponse->ids[0][3], $queryResponse->metadatas[0][3]['title']], + ]); + + $io->success('Chroma DB Connection & Similarity Search Test Successful!'); + + return Command::SUCCESS; + } +} diff --git a/src/Kernel.php b/src/Kernel.php new file mode 100644 index 000000000..779cd1f2b --- /dev/null +++ b/src/Kernel.php @@ -0,0 +1,11 @@ + 'onKernelResponse', + ]; + } + + public function onKernelResponse(ResponseEvent $event): void + { + if (!$this->kernel->isDebug()) { + return; + } + + $request = $event->getRequest(); + if (!$request->isXmlHttpRequest()) { + return; + } + + $response = $event->getResponse(); + $response->headers->set('Symfony-Debug-Toolbar-Replace', '1'); + } +} diff --git a/src/Twig/RagComponent.php b/src/Twig/RagComponent.php new file mode 100644 index 000000000..b64559b1d --- /dev/null +++ b/src/Twig/RagComponent.php @@ -0,0 +1,40 @@ +chat->loadMessages()->withoutSystemMessage(); + } + + #[LiveAction] + public function submit(#[LiveArg] string $message): void + { + $this->chat->submitMessage($message); + } + + #[LiveAction] + public function reset(): void + { + $this->chat->reset(); + } +} diff --git a/src/Twig/WikipediaComponent.php b/src/Twig/WikipediaComponent.php new file mode 100644 index 000000000..c7201badf --- /dev/null +++ b/src/Twig/WikipediaComponent.php @@ -0,0 +1,40 @@ +wikipedia->loadMessages()->withoutSystemMessage(); + } + + #[LiveAction] + public function submit(#[LiveArg] string $message): void + { + $this->wikipedia->submitMessage($message); + } + + #[LiveAction] + public function reset(): void + { + $this->wikipedia->reset(); + } +} diff --git a/src/Twig/YouTubeComponent.php b/src/Twig/YouTubeComponent.php new file mode 100644 index 000000000..c246bcfb0 --- /dev/null +++ b/src/Twig/YouTubeComponent.php @@ -0,0 +1,70 @@ +getVideoIdFromUrl($videoId); + } + + try { + $this->youTube->start($videoId); + } catch (\Exception $e) { + $this->logger->error('Unable to start YouTube chat.', ['exception' => $e]); + $this->youTube->reset(); + } + } + + public function getMessages(): MessageBag + { + return $this->youTube->loadMessages()->withoutSystemMessage(); + } + + #[LiveAction] + public function submit(#[LiveArg] string $message): void + { + $this->youTube->submitMessage($message); + } + + #[LiveAction] + public function reset(): void + { + $this->youTube->reset(); + } + + private function getVideoIdFromUrl(string $url): string + { + $query = parse_url($url, PHP_URL_QUERY); + + if (!$query) { + throw new \InvalidArgumentException('Unable to parse YouTube URL.'); + } + + return u($query)->after('v=')->before('&')->toString(); + } +} diff --git a/src/YouTube/TranscriptFetcher.php b/src/YouTube/TranscriptFetcher.php new file mode 100644 index 000000000..d99a7f7af --- /dev/null +++ b/src/YouTube/TranscriptFetcher.php @@ -0,0 +1,56 @@ +client->request('GET', 'https://youtube.com/watch?v='.$videoId); + $html = $htmlResponse->getContent(); + + // Use DomCrawler to parse the HTML + $crawler = new Crawler($html); + + // Extract the script containing the ytInitialPlayerResponse + $scriptContent = $crawler->filter('script')->reduce(function (Crawler $node) { + return str_contains($node->text(), 'var ytInitialPlayerResponse = {'); + })->text(); + + // Extract and parse the JSON data from the script + $start = strpos($scriptContent, 'var ytInitialPlayerResponse = ') + strlen('var ytInitialPlayerResponse = '); + $dataString = substr($scriptContent, $start); + $dataString = substr($dataString, 0, strrpos($dataString, ';') ?: null); + $data = json_decode(trim($dataString), true); + + // Extract the URL for the captions + if (!isset($data['captions']['playerCaptionsTracklistRenderer']['captionTracks'][0]['baseUrl'])) { + throw new \Exception('Captions are not available for this video.'); + } + $captionsUrl = $data['captions']['playerCaptionsTracklistRenderer']['captionTracks'][0]['baseUrl']; + + // Fetch and parse the captions XML + $xmlResponse = $this->client->request('GET', $captionsUrl); + $xmlContent = $xmlResponse->getContent(); + $xmlCrawler = new Crawler($xmlContent); + + // Collect all text elements from the captions + $transcript = $xmlCrawler->filter('text')->each(function (Crawler $node) { + return $node->text().' '; + }); + + // Combine all the text elements into one string + return implode(PHP_EOL, $transcript); + } +} diff --git a/symfony.lock b/symfony.lock new file mode 100644 index 000000000..0f3e85f4d --- /dev/null +++ b/symfony.lock @@ -0,0 +1,232 @@ +{ + "php-cs-fixer/shim": { + "version": "3.55", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.0", + "ref": "16422bf8eac6c3be42afe07d37e2abc89d2bdf6b" + }, + "files": [ + ".php-cs-fixer.dist.php" + ] + }, + "php-llm/llm-chain-bundle": { + "version": "dev-main" + }, + "phpstan/phpstan": { + "version": "1.10", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.0", + "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767" + }, + "files": [ + "phpstan.dist.neon" + ] + }, + "phpunit/phpunit": { + "version": "11.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "9.6", + "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326" + }, + "files": [ + ".env.test", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, + "symfony/asset-mapper": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a" + }, + "files": [ + "assets/app.js", + "assets/styles/app.css", + "config/packages/asset_mapper.yaml", + "importmap.php" + ] + }, + "symfony/console": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461" + }, + "files": [ + "bin/console" + ] + }, + "symfony/debug-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b" + }, + "files": [ + "config/packages/debug.yaml" + ] + }, + "symfony/flex": { + "version": "2.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "146251ae39e06a95be0fe3d13c807bcf3938b172" + }, + "files": [ + ".env" + ] + }, + "symfony/framework-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "6356c19b9ae08e7763e4ba2d9ae63043efc75db5" + }, + "files": [ + "config/packages/cache.yaml", + "config/packages/framework.yaml", + "config/preload.php", + "config/routes/framework.yaml", + "config/services.yaml", + "public/index.php", + "src/Controller/.gitignore", + "src/Kernel.php" + ] + }, + "symfony/monolog-bundle": { + "version": "3.10", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.7", + "ref": "aff23899c4440dd995907613c1dd709b6f59503f" + }, + "files": [ + "config/packages/monolog.yaml" + ] + }, + "symfony/routing": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "21b72649d5622d8f7da329ffb5afb232a023619d" + }, + "files": [ + "config/packages/routing.yaml", + "config/routes.yaml" + ] + }, + "symfony/stimulus-bundle": { + "version": "2.17", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.13", + "ref": "6acd9ff4f7fd5626d2962109bd4ebab351d43c43" + }, + "files": [ + "assets/bootstrap.js", + "assets/controllers.json", + "assets/controllers/chat_controller.js" + ] + }, + "symfony/twig-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877" + }, + "files": [ + "config/packages/twig.yaml", + "templates/base.html.twig" + ] + }, + "symfony/uid": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "0df5844274d871b37fc3816c57a768ffc60a43a5" + } + }, + "symfony/ux-icons": { + "version": "2.17", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.17", + "ref": "803a3bbd5893f9584969ab8670290cdfb6a0a5b5" + }, + "files": [ + "assets/icons/symfony.svg" + ] + }, + "symfony/ux-live-component": { + "version": "2.17", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.6", + "ref": "73e69baf18f47740d6f58688c5464b10cdacae06" + }, + "files": [ + "config/routes/ux_live_component.yaml" + ] + }, + "symfony/ux-turbo": { + "version": "v2.17.0" + }, + "symfony/ux-twig-component": { + "version": "2.17", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.13", + "ref": "67814b5f9794798b885cec9d3f48631424449a01" + }, + "files": [ + "config/packages/twig_component.yaml" + ] + }, + "symfony/ux-typed": { + "version": "v2.22.0" + }, + "symfony/web-profiler-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.1", + "ref": "e42b3f0177df239add25373083a564e5ead4e13a" + }, + "files": [ + "config/packages/web_profiler.yaml", + "config/routes/web_profiler.yaml" + ] + }, + "twig/extra-bundle": { + "version": "v3.9.3" + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig new file mode 100644 index 000000000..9af33983b --- /dev/null +++ b/templates/base.html.twig @@ -0,0 +1,51 @@ + + + + + {% block title %}LLM Chain Demo{% endblock %} + + + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% block importmap %}{{ importmap('app') }}{% endblock %} + {% endblock %} + + + +
+ {% block content %}{% endblock %} +
+ Symfony demo application for LLM Chain + • + Feel free to propose more examples on GitHub +
+
+ + diff --git a/templates/chat/rag.html.twig b/templates/chat/rag.html.twig new file mode 100644 index 000000000..6c154cf83 --- /dev/null +++ b/templates/chat/rag.html.twig @@ -0,0 +1,9 @@ +{% extends 'base.html.twig' %} + +{% block body_class 'chat rag' %} + +{% block content %} +
+ +
+{% endblock %} diff --git a/templates/chat/wikipedia.html.twig b/templates/chat/wikipedia.html.twig new file mode 100644 index 000000000..cc9498f8a --- /dev/null +++ b/templates/chat/wikipedia.html.twig @@ -0,0 +1,9 @@ +{% extends 'base.html.twig' %} + +{% block body_class 'chat wikipedia' %} + +{% block content %} +
+ +
+{% endblock %} diff --git a/templates/chat/youtube.html.twig b/templates/chat/youtube.html.twig new file mode 100644 index 000000000..f7c897cb1 --- /dev/null +++ b/templates/chat/youtube.html.twig @@ -0,0 +1,9 @@ +{% extends 'base.html.twig' %} + +{% block body_class 'chat youtube' %} + +{% block content %} +
+ +
+{% endblock %} diff --git a/templates/components/_message.html.twig b/templates/components/_message.html.twig new file mode 100644 index 000000000..daa222437 --- /dev/null +++ b/templates/components/_message.html.twig @@ -0,0 +1,47 @@ +{% if message.role.value == 'assistant' %} + {{ _self.bot(message.content, latest: latest) }} +{% else %} + {{ _self.user(message.content) }} +{% endif %} + +{% macro bot(content, loading = false, latest = false) %} +
+
+ {{ ux_icon('fluent:bot-24-filled', { height: '45px', width: '45px' }) }} +
+
+ {% if loading %} +
+ + {{ content }} +
+ {% else %} +
+ {% if latest %} + + {% else %} + {{ content|markdown_to_html }} + {% endif %} +
+ {% endif %} +
+
+{% endmacro %} + +{% macro user(content) %} +
+
+ {% for item in content %} +
{{ item.text }}
+ {% endfor %} +
+
+ {{ ux_icon('solar:user-bold', { width: '45px', height: '45px' }) }} +
+
+{% endmacro %} diff --git a/templates/components/rag.html.twig b/templates/components/rag.html.twig new file mode 100644 index 000000000..3b630e88d --- /dev/null +++ b/templates/components/rag.html.twig @@ -0,0 +1,30 @@ +{% import "components/_message.html.twig" as message %} + +
+
+ {{ ux_icon('fluent:bot-24-filled', { height: '32px', width: '32px' }) }} + Chat with RAG Chat Bot + +
+
+ {% for message in this.messages %} + {% include 'components/_message.html.twig' with { message, latest: loop.last } %} + {% else %} +
+ {{ ux_icon('entypo:chat', { height: '100px', width: '100px' }) }} +

Retrieval Augmented Generation with the Symfony blog

+ Please use the text input at the bottom to start chatting. +
+ {% endfor %} +
+ {{ message.user([{text:''}]) }} + {{ message.bot('Your Symfony bot is looking for an answer ...', true) }} +
+
+ +
diff --git a/templates/components/wikipedia.html.twig b/templates/components/wikipedia.html.twig new file mode 100644 index 000000000..231d074a4 --- /dev/null +++ b/templates/components/wikipedia.html.twig @@ -0,0 +1,30 @@ +{% import "components/_message.html.twig" as message %} + +
+
+ {{ ux_icon('mdi:wikipedia', { height: '32px', width: '32px' }) }} + Wikipedia Research Bot + +
+
+ {% for message in this.messages %} + {% include 'components/_message.html.twig' with { message, latest: loop.last } %} + {% else %} +
+ {{ ux_icon('mdi:wikipedia', { height: '100px', width: '100px' }) }} +

Wikipedia Research

+ Please provide the bot with a topic down below to start the research. +
+ {% endfor %} +
+ {{ message.user([{text:''}]) }} + {{ message.bot('Your Wikipedia bot is doing some research ...', true) }} +
+
+ +
diff --git a/templates/components/youtube.html.twig b/templates/components/youtube.html.twig new file mode 100644 index 000000000..29ba9c970 --- /dev/null +++ b/templates/components/youtube.html.twig @@ -0,0 +1,38 @@ +{% import "components/_message.html.twig" as message %} + +
+
+ {{ ux_icon('bi:youtube', { height: '32px', width: '32px' }) }} + Chat about a YouTube Video + +
+
+ {% set messages = this.messages %} + {% for message in messages %} + {% include 'components/_message.html.twig' with { message, latest: loop.last } %} + {% else %} +
+ {{ ux_icon('bi:youtube', { color: '#FF0000', height: '100px', width: '100px' }) }} +

Chat about a YouTube Video

+
+ +
+ https://youtube.com/watch?v= + + +
+
+
+ {% endfor %} +
+ {{ message.user([{text:''}]) }} + {{ message.bot('Your Symfony bot is looking for an answer ...', true) }} +
+
+ +
diff --git a/templates/index.html.twig b/templates/index.html.twig new file mode 100644 index 000000000..8ac30b214 --- /dev/null +++ b/templates/index.html.twig @@ -0,0 +1,66 @@ +{% extends 'base.html.twig' %} + +{% block body_class 'index' %} + +{% block content %} +
+

Welcome to the LLM Chain Demo

+

+ This is a small demo app that can be used to explore the capabilities of LLM Chain together with Symfony, + Symfony UX and Twig Live Components.
+ Central to this demo are three chatbot examples that are implemented in src/Chat/ and LLM Chain + configuration can be found in config/packages/llm_chain.yaml. +

+

Examples

+
+
+
+
+ {{ ux_icon('fluent:bot-24-filled', { height: '150px', width: '150px' }) }} +
+
+
RAG Chat Bot
+

Retrieval Augmented Generation based on Symfony's blog dumped to a vector store.

+ Try RAG Chat Bot +
+ +
+
+
+
+
+ {{ ux_icon('bi:youtube', { height: '150px', width: '150px' }) }} +
+
+
YouTube Transcript Bot
+

Question answering initialized with transcript of YouTube video.

+ Try YouTube Transcript Bot +
+ +
+
+
+
+
+ {{ ux_icon('mdi:wikipedia', { height: '150px', width: '150px' }) }} +
+
+
Wikipedia Research Bot
+

Some quick example text to build on the card title and make up the bulk of the card's content.

+ Try Wikipedia Research Bot +
+ +
+
+
+
+{% endblock %} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 000000000..f7dd7a5f5 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,14 @@ +bootEnv(dirname(__DIR__).'/.env'); +} From da78831fa948ea6277caa95f80a7d63b7ab4f700 Mon Sep 17 00:00:00 2001 From: Denis Zunke Date: Tue, 10 Dec 2024 12:42:47 +0100 Subject: [PATCH 02/31] docs: Mention the Requirement of an API Key (#1) As the demo is mainly configured to be utilized with OpenAI GPT and Embeddings, what is later in the Readme explicit mentioned, it should also be mentioned in the Requirements-Section of the Readme. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8956bce2e..4455dd0ab 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ What you need to run this demo: * [Git](https://git-scm.com/) & [GitHub Account](https://github.com) * [Docker](https://www.docker.com/) with [Docker Compose Plugin](https://docs.docker.com/compose/) * Your Favorite IDE or Editor +* An [OpenAI API Key](https://platform.openai.com/docs/api-reference/create-and-export-an-api-key) ## Technology From 2cef2a8113010e35feb3895929bedfd88a37ff54 Mon Sep 17 00:00:00 2001 From: Denis Zunke Date: Tue, 10 Dec 2024 12:43:53 +0100 Subject: [PATCH 03/31] docs: Update setup instructions to contain composer scripts (#2) With the first step one should, as a last thing, execute `docker compose run composer install --no-scripts` and then call https://localhost/ and see a chatbot UI. In fact there is an exception with the Symfony App Setup instructions that the assets are not installed. The only thing that happens during composer install is a warning to the docker container. ``` $ docker compose run composer install --no-scripts The repository at "/app" does not have the correct ownership and git refuses to use it: fatal: detected dubious ownership in repository at '/app' To add an exception for this directory, call: git config --global --add safe.directory /app ``` But this is not a problem. What is missing is the execution of the script. When i execute `docker compose run composer install` it works and there is no exception. What was your reason to not execute the scripts, that contain the import map stuff? --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4455dd0ab..5bd30c6db 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Checkout the repository, start the docker environment and install dependencies: git clone git@github.com:php-llm/symfony-demo.git cd symfony-demo docker compose up -d -docker compose run composer install --no-scripts +docker compose run composer install ``` Now you should be able to open https://localhost/ in your browser, From 9601478496738878010bde9dac1a76008902b7a1 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Tue, 10 Dec 2024 19:05:48 +0100 Subject: [PATCH 04/31] ci: add pipeline (#3) --- .github/workflows/pipeline.yml | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/pipeline.yml diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 000000000..aee5d81cd --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,45 @@ +name: pipeline +on: pull_request + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + coverage: none + + - name: Install Composer + uses: ramsey/composer-install@v3 + + - name: Composer Validation + run: composer validate --strict + + - name: Install PHP Dependencies + run: composer install --no-scripts + + - name: Lint PHP + run: php -l src/**/*.php + + - name: Lint Templates + run: bin/console lint:twig templates + + - name: Lint Config + run: bin/console lint:yaml config + + - name: Lint Container + run: bin/console lint:container + + - name: Code Style PHP + run: PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run + + - name: PHPStan + run: vendor/bin/phpstan analyse + + - name: Tests + run: vendor/bin/phpunit From 0657fb2bfc8027f16d6c31cc06564e4e4f9b2ca6 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Tue, 10 Dec 2024 21:57:35 +0100 Subject: [PATCH 05/31] ci: add test folder to php lintint --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index aee5d81cd..714f79d79 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -24,7 +24,7 @@ jobs: run: composer install --no-scripts - name: Lint PHP - run: php -l src/**/*.php + run: php -l src/**/*.php tests/**/*.php - name: Lint Templates run: bin/console lint:twig templates From 5ae129ba05fc522459f89d62f1846abee87bb6a1 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Tue, 10 Dec 2024 21:58:14 +0100 Subject: [PATCH 06/31] ci: add composer script for local pipeline runs --- composer.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9e20ab7fc..40b14cafc 100644 --- a/composer.json +++ b/composer.json @@ -106,6 +106,16 @@ "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", "importmap:install": "symfony-cmd" - } + }, + "pipeline": [ + "composer validate --strict", + "php -l src/**/*.php tests/**/*.php", + "bin/console lint:twig templates", + "bin/console lint:yaml config", + "bin/console lint:container", + "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run", + "phpstan analyse", + "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage" + ] } } From 8de854c27bead445bda95bb9c1d6ebbc7dde0dd3 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Tue, 10 Dec 2024 21:59:45 +0100 Subject: [PATCH 07/31] tests: add smoke, ajax profiler subscriber and transcript happy path test --- .gitignore | 1 + src/ProfilerSubscriber.php | 7 +- templates/index.html.twig | 33 ++++++---- tests/ProfilerSubscriberTest.php | 61 +++++++++++++++++ tests/SmokeTest.php | 55 ++++++++++++++++ tests/YouTube/TranscriptFetcherTest.php | 27 ++++++++ tests/YouTube/fixtures/transcript.xml | 66 +++++++++++++++++++ tests/YouTube/fixtures/video.html | 88 +++++++++++++++++++++++++ 8 files changed, 323 insertions(+), 15 deletions(-) create mode 100644 tests/ProfilerSubscriberTest.php create mode 100644 tests/SmokeTest.php create mode 100644 tests/YouTube/TranscriptFetcherTest.php create mode 100644 tests/YouTube/fixtures/transcript.xml create mode 100644 tests/YouTube/fixtures/video.html diff --git a/.gitignore b/.gitignore index c1d6b1c8f..a94a1b6c1 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ phpstan.neon ###< phpunit/phpunit ### chromadb +coverage diff --git a/src/ProfilerSubscriber.php b/src/ProfilerSubscriber.php index 49459c7f7..26d3d46fe 100644 --- a/src/ProfilerSubscriber.php +++ b/src/ProfilerSubscriber.php @@ -4,9 +4,9 @@ namespace App; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; -use Symfony\Component\HttpKernel\KernelInterface; /** * Can be deleted with Symfony 7.3, see https://github.com/symfony/symfony/pull/59123. @@ -14,7 +14,8 @@ final class ProfilerSubscriber implements EventSubscriberInterface { public function __construct( - private KernelInterface $kernel, + #[Autowire('kernel.debug')] + private readonly bool $debug, ) { } @@ -27,7 +28,7 @@ public static function getSubscribedEvents(): array public function onKernelResponse(ResponseEvent $event): void { - if (!$this->kernel->isDebug()) { + if (!$this->debug && !$event->isMainRequest()) { return; } diff --git a/templates/index.html.twig b/templates/index.html.twig index 8ac30b214..aa16a7b65 100644 --- a/templates/index.html.twig +++ b/templates/index.html.twig @@ -23,10 +23,13 @@

Retrieval Augmented Generation based on Symfony's blog dumped to a vector store.

Try RAG Chat Bot - + {# Profiler route only available in dev #} + {% if 'dev' == app.environment %} + + {% endif %}
@@ -39,10 +42,13 @@

Question answering initialized with transcript of YouTube video.

Try YouTube Transcript Bot
- + {# Profiler route only available in dev #} + {% if 'dev' == app.environment %} + + {% endif %}
@@ -55,10 +61,13 @@

Some quick example text to build on the card title and make up the bulk of the card's content.

Try Wikipedia Research Bot
- + {# Profiler route only available in dev #} + {% if 'dev' == app.environment %} + + {% endif %} diff --git a/tests/ProfilerSubscriberTest.php b/tests/ProfilerSubscriberTest.php new file mode 100644 index 000000000..8d02dff5a --- /dev/null +++ b/tests/ProfilerSubscriberTest.php @@ -0,0 +1,61 @@ +headers->set('X-Requested-With', 'XMLHttpRequest'); + } + + $response = new Response(); + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), $requestType, $response); + + $listener = new ProfilerSubscriber($debug); + $listener->onKernelResponse($event); + + $this->assertFalse($response->headers->has('Symfony-Debug-Toolbar-Replace')); + } + + /** + * @return iterable + */ + public static function provideInvalidRequests(): iterable + { + yield 'sub request, not debug, not XHR' => [HttpKernelInterface::SUB_REQUEST, false, false]; + yield 'sub request, not debug, XHR' => [HttpKernelInterface::SUB_REQUEST, false, true]; + yield 'sub request, debug, XHR' => [HttpKernelInterface::SUB_REQUEST, true, true]; + yield 'main request, not debug, not XHR' => [HttpKernelInterface::MAIN_REQUEST, false, false]; + yield 'main request, debug, not XHR' => [HttpKernelInterface::MAIN_REQUEST, true, false]; + } + + public function testAjaxReplaceHeaderOnEnabledAndXHR(): void + { + $request = new Request(); + $request->headers->set('X-Requested-With', 'XMLHttpRequest'); + $response = new Response(); + $event = new ResponseEvent($this->createMock(Kernel::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new ProfilerSubscriber(true); + $listener->onKernelResponse($event); + + $this->assertEquals('1', $response->headers->get('Symfony-Debug-Toolbar-Replace')); + } +} diff --git a/tests/SmokeTest.php b/tests/SmokeTest.php new file mode 100644 index 000000000..fbbb016ad --- /dev/null +++ b/tests/SmokeTest.php @@ -0,0 +1,55 @@ +request('GET', '/'); + + self::assertResponseIsSuccessful(); + self::assertSelectorTextContains('h1', 'Welcome'); + self::assertSelectorCount(3, '.card'); + } + + public function testRag(): void + { + $client = static::createClient(); + $client->request('GET', '/rag'); + + self::assertResponseIsSuccessful(); + self::assertSelectorTextContains('h4', 'Retrieval Augmented Generation with the Symfony blog'); + self::assertSelectorCount(1, '#chat-submit'); + } + + public function testYouTube(): void + { + $client = static::createClient(); + $client->request('GET', '/youtube'); + + self::assertResponseIsSuccessful(); + self::assertSelectorTextContains('h4', 'Chat about a YouTube Video'); + self::assertSelectorCount(1, '#chat-submit'); + } + + public function testWikipedia(): void + { + $client = static::createClient(); + $client->request('GET', '/wikipedia'); + + self::assertResponseIsSuccessful(); + self::assertSelectorTextContains('h4', 'Wikipedia Research'); + self::assertSelectorCount(1, '#chat-submit'); + } +} diff --git a/tests/YouTube/TranscriptFetcherTest.php b/tests/YouTube/TranscriptFetcherTest.php new file mode 100644 index 000000000..5a65b4a0e --- /dev/null +++ b/tests/YouTube/TranscriptFetcherTest.php @@ -0,0 +1,27 @@ +fetchTranscript('6uXW-ulpj0s'); + + self::assertStringContainsString('symphony is a PHP framework', $transcript); + } +} diff --git a/tests/YouTube/fixtures/transcript.xml b/tests/YouTube/fixtures/transcript.xml new file mode 100644 index 000000000..f93f51bb2 --- /dev/null +++ b/tests/YouTube/fixtures/transcript.xml @@ -0,0 +1,66 @@ + + + symphony is a PHP framework and this is + an advantage why because almost 80 + percent of all websites in the internet + use PHP as a server-side language eighty + percent this does mean that wherever you + are right now there is someone near you + probably searching to hire a PHP + developer Symphony will not only teach + you more PHP but also will teach you + different software architecture patterns + that you can use in different languages + software architecture patterns are so + much important in your skill sets + because they allow you to understand + complex software when you only know the + architecture that was used in that + software please let me tell you a story + that happened to me I had a PHP laravel + interview in a company and the interview + was successful so they invite me for a + test work day and that test work day + they asked me to build an application + using.net and Seashore they told me we + know you didn&#39;t had any previous + experience using C sharp but we want to + see how you can get along with different + programming language I was able to + Google and found out that the dotnet + framework is an MVC architect framework + the same framework is used in Symphony + and Bam I built the application in + c-sharp and I got a wonderful job offer + so yes learning Symphony will teach you + software architecture patterns that are + essential in your skill set symphony is + a full stack framework that mean you can + create deploy ready application you will + be using front-end Technologies like + HTML CSS and JavaScript and back-end or + server Technologies like PHP databases + that will process the user request all + in one place Symphony can integrate + easily with modern JavaScript Frameworks + like vue.js or react.js or even you can + set up different databases like MySQL + postgres or whatever you want Symphony + has a CLI tool that can help build and + speak and debug your application and is + one of the most advanced code generation + tool in the planner is relate in the + comment if you know anything that is + good finally documentation Symphony has + good documentation that will make it + easy for newcomers to learn and have fun + with the technology so that was my 6y2 + simple normally I don&#39;t do this but + right now go and check the description + see the comment and write me what you + think like the video and subscribe to my + channel then go to the channel check the + videos and like each one of them and + comment again and come back to this + video and watch it again thank you + diff --git a/tests/YouTube/fixtures/video.html b/tests/YouTube/fixtures/video.html new file mode 100644 index 000000000..9ecea3f21 --- /dev/null +++ b/tests/YouTube/fixtures/video.html @@ -0,0 +1,88 @@ +Learn Symfony in 2025, 6 reasons why - YouTube
InfoPresseUrheberrechtKontaktCreatorWerbenEntwicklerImpressumVerträge hier kündigenNutzungsbedingungenDatenschutzRichtlinien & SicherheitWie funktioniert YouTube?Neue Funktionen testen
\ No newline at end of file From 4911c5fc90d630df567ce904ff3a9045516dac16 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 11 Dec 2024 00:42:49 +0100 Subject: [PATCH 08/31] refactor: extract logic from blog embed command (#5) --- src/Blog/Embedder.php | 60 ++ src/Blog/Loader.php | 42 ++ src/Blog/Post.php | 55 ++ src/Command/BlogEmbedCommand.php | 102 +--- tests/Blog/EmbedderTest.php | 101 ++++ tests/Blog/LoaderTest.php | 46 ++ tests/Blog/PostTest.php | 62 ++ tests/Blog/fixtures/blog.rss | 996 +++++++++++++++++++++++++++++++ tests/ProfilerSubscriberTest.php | 18 +- 9 files changed, 1377 insertions(+), 105 deletions(-) create mode 100644 src/Blog/Embedder.php create mode 100644 src/Blog/Loader.php create mode 100644 src/Blog/Post.php create mode 100644 tests/Blog/EmbedderTest.php create mode 100644 tests/Blog/LoaderTest.php create mode 100644 tests/Blog/PostTest.php create mode 100644 tests/Blog/fixtures/blog.rss diff --git a/src/Blog/Embedder.php b/src/Blog/Embedder.php new file mode 100644 index 000000000..8cb8eec62 --- /dev/null +++ b/src/Blog/Embedder.php @@ -0,0 +1,60 @@ +loader->load(); + $vectors = $this->createEmbeddings($posts); + $this->pushToChromaDB($posts, $vectors); + } + + /** + * @param Post[] $posts + * + * @return Vector[] + */ + private function createEmbeddings(array $posts): array + { + $texts = array_map(fn (Post $post) => $post->toString(), $posts); + $response = $this->platform->request(new Embeddings(), $texts); + + assert($response instanceof AsyncResponse); + $response = $response->unwrap(); + assert($response instanceof VectorResponse); + + return $response->getContent(); + } + + /** + * @param Post[] $posts + * @param Vector[] $vectors + */ + private function pushToChromaDB(array $posts, array $vectors): void + { + $collection = $this->chromaClient->getOrCreateCollection('symfony_blog'); + + $ids = array_map(fn (Post $post) => $post->id, $posts); + $vectors = array_map(fn (Vector $vector) => $vector->getData(), $vectors); + + $collection->upsert($ids, $vectors, $posts); + } +} diff --git a/src/Blog/Loader.php b/src/Blog/Loader.php new file mode 100644 index 000000000..866e7cce1 --- /dev/null +++ b/src/Blog/Loader.php @@ -0,0 +1,42 @@ +httpClient->request('GET', 'https://feeds.feedburner.com/symfony/blog'); + + $posts = []; + $crawler = new Crawler($response->getContent()); + $crawler->filter('item')->each(function (Crawler $node) use (&$posts) { + $title = $node->filter('title')->text(); + $posts[] = new Post( + Uuid::v5(Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'), $title), + $title, + $node->filter('link')->text(), + $node->filter('description')->text(), + (new Crawler($node->filter('content\:encoded')->text()))->text(), + $node->filter('dc\:creator')->text(), + new \DateTimeImmutable($node->filter('pubDate')->text()), + ); + }); + + return $posts; + } +} diff --git a/src/Blog/Post.php b/src/Blog/Post.php new file mode 100644 index 000000000..6aff1ac76 --- /dev/null +++ b/src/Blog/Post.php @@ -0,0 +1,55 @@ +title} + From: {$this->author} on {$this->date->format('Y-m-d')} + Description: {$this->description} + {$this->content} + TEXT; + } + + /** + * @return array{ + * id: string, + * title: string, + * link: string, + * description: string, + * content: string, + * author: string, + * date: string, + * } + */ + public function toArray(): array + { + return [ + 'id' => $this->id->toRfc4122(), + 'title' => $this->title, + 'link' => $this->link, + 'description' => $this->description, + 'content' => $this->content, + 'author' => $this->author, + 'date' => $this->date->format('Y-m-d'), + ]; + } +} diff --git a/src/Command/BlogEmbedCommand.php b/src/Command/BlogEmbedCommand.php index ea5436e40..27affd902 100644 --- a/src/Command/BlogEmbedCommand.php +++ b/src/Command/BlogEmbedCommand.php @@ -4,39 +4,18 @@ namespace App\Command; -use Codewithkyrian\ChromaDB\Client; -use PhpLlm\LlmChain\Bridge\OpenAI\Embeddings; -use PhpLlm\LlmChain\Document\Vector; -use PhpLlm\LlmChain\Model\Response\AsyncResponse; -use PhpLlm\LlmChain\Model\Response\VectorResponse; -use PhpLlm\LlmChain\PlatformInterface; +use App\Blog\Embedder; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\DomCrawler\Crawler; -use Symfony\Component\Uid\Uuid; -use Symfony\Contracts\HttpClient\HttpClientInterface; -/** - * @phpstan-type Post array{ - * id: Uuid, - * title: string, - * link: string, - * description: string, - * content: string, - * author: string, - * date: \DateTimeImmutable, - * } - */ #[AsCommand('app:blog:embed', description: 'Create embeddings for Symfony blog and push to ChromaDB.')] final class BlogEmbedCommand extends Command { public function __construct( - private readonly HttpClientInterface $httpClient, - private readonly PlatformInterface $platform, - private readonly Client $chromaClient, + private readonly Embedder $embedder, ) { parent::__construct(); } @@ -46,85 +25,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $io->title('Loading RSS of Symfony blog as embeddings into ChromaDB'); - $posts = $this->loadBlogPosts(); - $vectors = $this->createEmbeddings($posts); - $this->pushToChromaDB($posts, $vectors); + $this->embedder->embedBlog(); $io->success('Symfony Blog Successfully Embedded!'); return Command::SUCCESS; } - - /** - * @return list - */ - private function loadBlogPosts(): array - { - $response = $this->httpClient->request('GET', 'https://feeds.feedburner.com/symfony/blog'); - - $posts = []; - $crawler = new Crawler($response->getContent()); - $crawler->filter('item')->each(function (Crawler $node) use (&$posts) { - $title = $node->filter('title')->text(); - $posts[] = [ - 'id' => Uuid::v5(Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'), $title), - 'title' => $title, - 'link' => $node->filter('link')->text(), - 'description' => $node->filter('description')->text(), - 'content' => (new Crawler($node->filter('content\:encoded')->text()))->text(), - 'author' => $node->filter('dc\:creator')->text(), - 'date' => new \DateTimeImmutable($node->filter('pubDate')->text()), - ]; - }); - - return $posts; - } - - /** - * @param Post[] $posts - * - * @return Vector[] - */ - private function createEmbeddings(array $posts): array - { - $texts = []; - foreach ($posts as $post) { - $texts[] = <<format('Y-m-d')} - Description: {$post['description']} - {$post['content']} - TEXT; - } - - $response = $this->platform->request(new Embeddings(), $texts); - - assert($response instanceof AsyncResponse); - $response = $response->unwrap(); - assert($response instanceof VectorResponse); - - return $response->getContent(); - } - - /** - * @param Post[] $posts - * @param Vector[] $vectors - */ - private function pushToChromaDB(array $posts, array $vectors): void - { - $collection = $this->chromaClient->getOrCreateCollection('symfony_blog'); - - $ids = array_column($posts, 'id'); - $vectors = array_map(fn (Vector $vector) => $vector->getData(), $vectors); - - $collection->upsert($ids, $vectors, $posts); - } } diff --git a/tests/Blog/EmbedderTest.php b/tests/Blog/EmbedderTest.php new file mode 100644 index 000000000..51652be68 --- /dev/null +++ b/tests/Blog/EmbedderTest.php @@ -0,0 +1,101 @@ +createMock(PlatformInterface::class); + $chromaClient = $this->createMock(Client::class); + $posts = $loader->load(); + $vectors = [ + new Vector([0.1, 0.2, 0.3]), + new Vector([0.4, 0.5, 0.6]), + new Vector([0.7, 0.8, 0.9]), + new Vector([1.0, 1.1, 1.2]), + new Vector([1.3, 1.4, 1.5]), + new Vector([1.6, 1.7, 1.8]), + new Vector([1.9, 2.0, 2.1]), + new Vector([2.2, 2.3, 2.4]), + new Vector([2.5, 2.6, 2.7]), + new Vector([2.8, 2.9, 3.0]), + ]; + $platform + ->method('request') + ->willReturn($this->createAsyncResponse($vectors)); + + $collection = $this->createMock(CollectionResource::class); + $chromaClient + ->expects($this->once()) + ->method('getOrCreateCollection') + ->with('symfony_blog') + ->willReturn($collection); + + $collection + ->expects($this->once()) + ->method('upsert') + ->with( + array_map(fn (Post $post) => $post->id, $posts), + array_map(fn (Vector $vector) => $vector->getData(), $vectors), + $posts, + ); + + $embedder = new Embedder($loader, $platform, $chromaClient); + $embedder->embedBlog(); + } + + /** + * @param Vector[] $vectors + */ + private function createAsyncResponse(array $vectors): AsyncResponse + { + $converter = new class($vectors) implements ResponseConverter { + /** + * @param Vector[] $vectors + */ + public function __construct(private readonly array $vectors) + { + } + + public function supports(Model $model, object|array|string $input): bool + { + return true; + } + + public function convert(HttpResponse $response, array $options = []): LlmResponse + { + return new VectorResponse(...$this->vectors); + } + }; + + return new AsyncResponse($converter, new MockResponse()); + } +} diff --git a/tests/Blog/LoaderTest.php b/tests/Blog/LoaderTest.php new file mode 100644 index 000000000..31f42f091 --- /dev/null +++ b/tests/Blog/LoaderTest.php @@ -0,0 +1,46 @@ +load(); + + self::assertCount(10, $posts); + + self::assertSame('A Week of Symfony #936 (2-8 December 2024)', $posts[0]->title); + self::assertSame('https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed', $posts[0]->link); + self::assertStringContainsString('This week, Symfony celebrated the SymfonyCon 2024 Vienna conference with great success.', $posts[0]->description); + self::assertStringContainsString('Select a track for a guided path through 100+ video tutorial courses about Symfony', $posts[0]->content); + self::assertSame('Javier Eguiluz', $posts[0]->author); + self::assertEquals(new \DateTimeImmutable('8.12.2024 09:39:00 +0100'), $posts[0]->date); + + self::assertSame('A Week of Symfony #935 (25 November - 1 December 2024)', $posts[1]->title); + self::assertSame('Symfony 7.2 curated new features', $posts[2]->title); + self::assertSame('Symfony 7.2.0 released', $posts[3]->title); + self::assertSame('Symfony 5.4.49 released', $posts[4]->title); + self::assertSame('SymfonyCon Vienna 2024: See you next week!', $posts[5]->title); + self::assertSame('New in Symfony 7.2: Misc. Improvements (Part 2)', $posts[6]->title); + self::assertSame('Symfony 7.1.9 released', $posts[7]->title); + self::assertSame('Symfony 6.4.16 released', $posts[8]->title); + self::assertSame('Symfony 5.4.48 released', $posts[9]->title); + } +} diff --git a/tests/Blog/PostTest.php b/tests/Blog/PostTest.php new file mode 100644 index 000000000..866f3f2fa --- /dev/null +++ b/tests/Blog/PostTest.php @@ -0,0 +1,62 @@ +toString()); + } + + public function testPostToArray(): void + { + $id = Uuid::v4(); + $post = new Post( + $id, + 'Hello, World!', + 'https://example.com/hello-world', + 'This is a test description.', + 'This is a test post.', + 'John Doe', + new \DateTimeImmutable('2024-12-08 09:39:00'), + ); + + $expected = [ + 'id' => $id->toRfc4122(), + 'title' => 'Hello, World!', + 'link' => 'https://example.com/hello-world', + 'description' => 'This is a test description.', + 'content' => 'This is a test post.', + 'author' => 'John Doe', + 'date' => '2024-12-08', + ]; + + self::assertSame($expected, $post->toArray()); + } +} diff --git a/tests/Blog/fixtures/blog.rss b/tests/Blog/fixtures/blog.rss new file mode 100644 index 000000000..9fc7c51d5 --- /dev/null +++ b/tests/Blog/fixtures/blog.rss @@ -0,0 +1,996 @@ + + + + Symfony Blog + + https://symfony.com/blog/ + Most recent posts published on the Symfony project blog + Tue, 10 Dec 2024 23:56:55 +0100 + Sun, 08 Dec 2024 09:39:00 +0100 + en + + <![CDATA[A Week of Symfony #936 (2-8 December 2024)]]> + https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + This week, Symfony celebrated the SymfonyCon 2024 Vienna conference with great success. This annual event brought together the global Symfony community to exchange ideas, learn new things, and collaborate on contributions to the Symfony project. In addition,… + This week, Symfony celebrated the SymfonyCon 2024 Vienna conference with great success. This annual event brought together the global Symfony community to exchange ideas, learn new things, and collaborate on contributions to the Symfony project. In addition, the upcoming Symfony 7.3 version introduced support for pre-compressing web assets and a new userIsGranted() security method to test user authorization without relying on the session.

+ +

Symfony development highlights

+ +

This week, 57 pull requests were merged (28 in code and 29 in docs) and 28 issues were closed (22 in code and 6 in docs). Excluding merges, 35 authors made 2,784 additions and 954 deletions. See details for code and docs.

+ +

6.4 changelog:

+ +
    +
  • 8c8bab2: [TwigBridge] fix Twig 3.17 compatibility
  • +
  • ffc4dc6: [HttpClient] always set CURLOPT_CUSTOMREQUEST to the correct HTTP method in CurlHttpClient
  • +
  • 74e9163: [PropertyInfo] evaluate access flags for properties with asymmetric visibility
  • +
  • 51d824a: [Console] fix division by 0 error
  • +
  • caa1be7: [ErrorHandler] fix error message in test with PHP 8.5
  • +
  • e25242a: [TwigBridge] add tests covering trans_default_domain with dynamic expressions
  • +
  • 91835ef: [FrameworkBundle] fix notifier push channel bus abstract arg
  • +
+ +

7.1 changelog:

+ +
    +
  • 2d9f2a5: [Scheduler] remove unused code
  • +
+ +

7.2 changelog:

+ +
    +
  • cc1802d: [TwigBridge] generate conflict-free variable names
  • +
  • 6076b87, dcf824a: [Mailer] fix null check on region in Sendgrid mailer
  • +
  • f24ac9e: [FrameworkBundle] make uri_signer lazy and improve error when kernel.secret is empty
  • +
  • 27c0f65: [Notifier] fix desktop channel bus abstract arg
  • +
+ +

7.3 changelog:

+ +
    +
  • 2d1838a: [VarDumper] add caster for Socket instances
  • +
  • 3a804f5: [Mailer, Notifier] add webhooks signature verification on Sweego bridges
  • +
  • 12e4e53: [AssetMapper] add support for assets pre-compression
  • +
  • b6d6adf: [FrameworkBundle] rename TranslationUpdateCommand to TranslationExtract command to match the command name
  • +
  • b7ed0a4: [ErrorHandler] support non-empty-string/non-empty-list when patching return types
  • +
  • 12ff1bf: [Uid] add @return non-empty-string annotations to AbstractUid and relevant functions
  • +
  • 4612ff2: [Security, SecurityBundle] add a userIsGranted() method to test user authorization without relying on the session
  • +
+ +

Newest issues and pull requests

+ + + +

Symfony Jobs

+ +

These are some of the most recent Symfony job offers:

+ +
    +
  • Symfony Developer at Adria Solutions
    +Full-time - £45,000 – £60,000 / year
    +Remote + part-time onsite (Cardiff, United Kingdom)
    +View details
  • +
  • Backend Symfony Developer at Bold Company
    +Full-time - €4,200 / month
    +Remote + part-time onsite (Rotterdam, Netherlands)
    +View details
  • +
  • Symfony Developer at Kennisnet
    +Full-time - €4,104 – €5,673 / month
    +Remote + part-time onsite (Zoetermeer, Netherlands)
    +View details
  • +
+ +

You can publish a Symfony job offer for free on symfony.com.

+ +

SymfonyCasts Updates

+ +

SymfonyCasts is the official way to learn Symfony. +Select a track for a guided path through 100+ video tutorial courses about +Symfony, PHP and JavaScript.

+ +

This week, SymfonyCasts published the following updates:

+ + + +

They talked about us

+ + + +

Call to Action

+ + + +
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Sun, 08 Dec 2024 09:39:00 +0100 + https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[A Week of Symfony #935 (25 November - 1 December 2024)]]> + https://symfony.com/blog/a-week-of-symfony-935-25-november-1-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + This week, the stable Symfony 7.2.0 version was released, featuring tens of new additions. Additionally, we announced the Black Friday Symfony promotions. Furthermore, the maintenance releases for Symfony 5.4.48, 6.4.16, and 7.1.9 are now available. Finally,… + This week, the stable Symfony 7.2.0 version was released, featuring tens of new additions. Additionally, we announced the Black Friday Symfony promotions. Furthermore, the maintenance releases for Symfony 5.4.48, 6.4.16, and 7.1.9 are now available. Finally, next week, the global Symfony community will gather in Vienna for the SymfonyCon 2024 conference.

+ +

Symfony development highlights

+ +

This week, 39 pull requests were merged (28 in code and 11 in docs) and 21 issues were closed (19 in code and 2 in docs). Excluding merges, 21 authors made 1,033 additions and 332 deletions. See details for code and docs.

+ +

5.4 changelog:

+ +
    +
  • 5cbdb87: [HttpClient] various cleanups after recent changes
  • +
  • 520e31b: [PropertyInfo] consider write property visibility to decide whether a property is writable
  • +
  • 294a39f: [Translation] fix empty keys array in PUT, DELETE requests causing Lokalise API error
  • +
  • ec691c8: [PropertyInfo] fix write visibility for Asymmetric Visibility and Virtual Properties
  • +
  • 1b7d8b3: [Dotenv] read runtime config from composer.json in debug dotenv command
  • +
  • 9eea677: [HttpClient] close gracefull when the server closes the connection abruptly
  • +
  • 4677f32: [HttpClient] fix checking for private IPs before connecting
  • +
  • 7a2d66a: [HttpClient] fix streaming and redirecting with NoPrivateNetworkHttpClient
  • +
+ +

6.4 changelog:

+ +
    +
  • 76df983: [DoctrineBridge] fix Connection::createSchemaManager() for Doctrine DBAL v2
  • +
  • 9c6b5d5: [HttpClient] more consistency cleanups
  • +
  • a59ff05: [Messenger] fix Envelope::all() conditional return docblock
  • +
+ +

7.2 changelog:

+ +
    +
  • e128d76: [HttpClient] fix amphp/http-client 5 support
  • +
  • 59ceb58: [Form] allow integer for the calendar option of DateType
  • +
+ +

7.3 changelog:

+ +
    +
  • baf98b9: [VarDumper] add caster for AddressInfo objects
  • +
+ +

Newest issues and pull requests

+ + + +

Symfony CLI

+ +

Symfony CLI is a must-have tool when developing +Symfony applications on your local machine. It includes the +Symfony Local Server, +the best way to run local Symfony applications. This week Symfony CLI released +its new 5.10.5, +version with the following changes:

+ +
    +
  • Update fixtures (@fabpot)
  • +
  • Update check-requirements.php script to v2.0.3 (@fabpot)
  • +
  • Fix path on local envs (@fabpot)
  • +
  • Add a warning about the listening IP change in 5.10.3 (@tucksaun)
  • +
+ +

Symfony Jobs

+ +

These are some of the most recent Symfony job offers:

+ +
    +
  • Symfony Developer at Tactiplan
    +Full-time - €4,500 – €8,000 / month
    +Full remote
    +View details
  • +
  • Backend Symfony Developer at 2beGROUP
    +Full-time - €48,000 – €72,000 / year
    +Remote + part-time onsite (Leiderdorp, Netherlands)
    +View details
  • +
  • Symfony Developer at ProcurePro
    +Full-time - A$120,000 – A$150,000 / year
    +Full remote
    +View details
  • +
+ +

You can publish a Symfony job offer for free on symfony.com.

+ +

SymfonyCasts Updates

+ +

SymfonyCasts is the official way to learn Symfony. +Select a track for a guided path through 100+ video tutorial courses about +Symfony, PHP and JavaScript.

+ +

This week, SymfonyCasts published the following updates:

+ + + +

They talked about us

+ + + +

Call to Action

+ + + +
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/a-week-of-symfony-935-25-november-1-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Sun, 01 Dec 2024 09:54:00 +0100 + https://symfony.com/blog/a-week-of-symfony-935-25-november-1-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[Symfony 7.2 curated new features]]> + https://symfony.com/blog/symfony-7-2-curated-new-features?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + Symfony 7.2.0 has been released. As for any other Symfony release, our backward compatibility promise applies and this means that you should be able to upgrade easily to 7.2 without changing anything in your code. + +During the last couple of months, we've… + Symfony 7.2.0 has been released. As for any other Symfony release, our backward compatibility promise applies and this means that you should be able to upgrade easily to 7.2 without changing anything in your code.

+ +

During the last couple of months, we've blogged about the great 7.2 new features. I highly recommend you to read these articles about Symfony 7.2 as they contain the major changes for this new version:

+ +
    +
  • Week, WordCount and Yaml Constraints: Symfony 7.2 introduces three new constraints: one to validate week numbers, another to check word count, and a third to validate YAML syntax.
  • +
  • Silent Verbosity: Symfony 7.2 introduces a new silent verbosity to supress all output, including errors.
  • +
  • Expression Language Improvements: Symfony 7.2 improves the ExpressionLanguage component with new bitwise and logical operators, easier registration of custom providers and support for comments.
  • +
  • AsMessage Attribute: Symfony 7.2 introduces a new AsMessage attribute, allowing you to configure the transport(s) directly within the message class
  • +
  • Named Serializers: Symfony 7.2 allows you to configure multiple serializer instances with different default contexts, name converters, and sets of normalizers and encoders.
  • +
  • Translations Linter: Symfony 7.2 includes a new lint:translations command to check the validity of your translation contents.
  • +
  • WhenNot Attribute: Symfony 7.2 introduces the WhenNot attribute to exclude a service from certain environments.
  • +
  • Lazy Choice Loader: Symfony 7.2 introduces a new lazy choice loader to improve performance of choice fields with lots of options.
  • +
  • String Component Improvements: Symfony 7.2 improves the String component with a new kebab-case method, new truncation modes and a Spanish inflector.
  • +
  • Compound Constraint Improvements: In Symfony 7.2, Compound constraints are easier to test and can define the validation groups and payload via the constructor.
  • +
  • Mailer and Notifier Integrations: Symfony 7.2 adds some new integrations to the Mailer and Notifier components, adding to the tens of integrations already available.
  • +
  • Improved Translation Extractor: Symfony 7.2 improves the translation extractor command, allowing customization of prefixes, modification of update behavior, and sorting of content.
  • +
  • Desktop Notifications: Symfony 7.2 allows to send notifications directly to your local desktop using the new desktop channel in the Notifier component.
  • +
  • Template DX Improvements: In Symfony 7.2, you can set HTTP headers in static pages and render specific Twig blocks using attributes.
  • +
  • Non-Empty Container Parameters: Symfony 7.2 introduces a new utility to require that some parameters exist and have non-empty values.
  • +
  • Keepalive Messenger Transports: Symfony 7.2 introduces the keepalive feature for Messenger transports, preventing timeouts when processing messages.
  • +
  • Mime Improvements: In Symfony 7.2, the Mime component adds support for custom encoders and Unicode email addresses.
  • +
  • Console Finished Indicator: Symfony 7.2 allows customizing the indicator displayed when a Console command completes.
  • +
  • Constraint Improvements: Symfony 7.2 adds a validation mode for BIC constraint, an errorPath for Unique constraint, format options for Ulid constraint, and context support for When constraint.
  • +
  • Simpler Trusted Proxies Configuration: Symfony 7.2 simplifies trusted proxy configuration with a private subnet shortcut and new environment variables.
  • +
  • Simpler Single-File Symfony Applications: In Symfony 7.2, single-file applications are now simpler and require less configuration.
  • +
  • New Command Options: Symfony 7.2 introduces new command options to lint container env vars, format messenger stats output, and filter assets during debugging.
  • +
  • Redesigned TypeInfo Component: Symfony 7.2 redesigns the TypeInfo component and makes it stable.
  • +
  • Serializer Improvements: Symfony 7.2 enhances the Serializer with support for DateTime subclasses, a new SnakeCaseToCamelCase name converter, updated UUID constants, and optional Webhook integration.
  • +
  • Stateless CSRF: Symfony 7.2 introduces stateless CSRF protection, enabling secure token validation without relying on server-side sessions.
  • +
  • Deprecations: Symfony 7.2 deprecates several features, including session config options, empty user identifiers, and the !tagged tag.
  • +
  • Optional Secret: Symfony 7.2 simplifies application setup by making the secret optional, enhancing security and developer experience.
  • +
  • Misc. Improvements (Part 1): Symfony 7.2 introduces features like custom retry delays for Messenger, improved null-coalesce support in expressions, custom attributes for user login passports, and enhanced VarDumper support for PHP 8.4 property hooks.
  • +
  • Misc. Improvements (Part 2): Symfony 7.2 adds password strength estimation, simpler RequestStack testing, nullable boolean configuration, improved IP anonymization, and Security Profiler upgrades.
  • +
+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfony-7-2-curated-new-features?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Fri, 29 Nov 2024 09:52:00 +0100 + https://symfony.com/blog/symfony-7-2-curated-new-features?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[Symfony 7.2.0 released]]> + https://symfony.com/blog/symfony-7-2-0-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + Symfony 7.2.0 has just been released. +Check the Living on the Edge +category on this blog to learn about the main features of this new stable release; +or check the release announcement of BETA1 +to get the list of all new features. +Here is the list of the most… + Symfony 7.2.0 has just been released.

+

Check the Living on the Edge +category on this blog to learn about the main features of this new stable release; +or check the release announcement of BETA1 +to get the list of all new features.

+

Here is the list of the most important changes since 7.2.0-RC1:

+
    +
  • bug #59023 [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (@nicolas-grekas)
  • +
  • bug #59014 [Form] Allow integer for the calendar option of DateType (@alexandre-daubois)
  • +
  • bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)
  • +
  • bug #58562 [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)
  • +
  • bug #59007 [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)
  • +
  • bug #58963 [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)
  • +
  • bug #58983 [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)
  • +
  • bug #58956 [DoctrineBridge] Fix Connection::createSchemaManager() for Doctrine DBAL v2 (@neodevcode)
  • +
  • bug #58959 [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)
  • +
  • bug #58964 [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)
  • +
  • bug #58950 [FrameworkBundle] Revert " Deprecate making cache.app adapter taggable" (@keulinho)
  • +
  • bug #58952 [Cache] silence warnings issued by Redis Sentinel on connection issues (@xabbuh)
  • +
  • bug #58953 [HttpClient] Fix computing stats for PUSH with Amp (@nicolas-grekas)
  • +
  • bug #58943 [FrameworkBundle] Revert " Don't auto-register form/csrf when the corresponding components are not installed" (@nicolas-grekas)
  • +
  • bug #58937 [FrameworkBundle] Don't auto-register form/csrf when the corresponding components are not installed (@nicolas-grekas)
  • +
  • bug #58859 [AssetMapper] ignore missing directory in isVendor() (@alexislefebvre)
  • +
  • bug #58917 [OptionsResolver] Allow Union/Intersection Types in Resolved Closures (@zanbaldwin)
  • +
  • bug #58822 [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)
  • +
  • bug #58865 Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)
  • +
  • bug #58921 [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)
  • +
  • bug #58908 [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)
  • +
  • bug #58938 [Cache] make RelayProxyTrait compatible with relay extension 0.9.0 (@xabbuh)
  • +
  • bug #58924 [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)
  • +
  • bug #58915 [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58919 [WebProfilerBundle] Twig deprecations (@mazodude)
  • +
  • bug #58914 [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58888 [Mailer][Notifier] Sweego is backing their bridges, thanks to them! (@nicolas-grekas)
  • +
  • bug #58885 [PropertyInfo][Serializer][TypeInfo][Validator] TypeInfo 7.1 compatibility (@mtarld)
  • +
  • bug #58870 [Serializer][Validator] prevent failures around not existing TypeInfo classes (@xabbuh)
  • +
  • bug #58872 [PropertyInfo][Serializer][Validator] TypeInfo 7.2 compatibility (@mtarld)
  • +
  • bug #58875 [HttpClient] Removed body size limit (Carl Julian Sauter)
  • +
  • bug #58866 [Validator] fix compatibility with PHP < 8.2.4 (@xabbuh)
  • +
  • bug #58862 [Notifier] Fix GoIpTransport (@nicolas-grekas)
  • +
  • bug #58860 [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)
  • +
  • bug #58834 [FrameworkBundle] ensure validator.translation_domain parameter is always set (@xabbuh)
  • +
  • bug #58836 Work around parse_url() bug (bis) (@nicolas-grekas)
  • +
  • bug #58818 [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)
  • +
  • bug #58828 [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)
  • +
  • bug #58842 [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)
  • +
  • bug #58850 [HttpClient] fix PHP 7.2 compatibility (@xabbuh)
  • +
+

Want to upgrade to this new release? Because Symfony protects +backwards-compatibility very closely, this should be quite easy. Use +SymfonyInsight upgrade reports +to detect the code you will need to change in your project and +read our upgrade +documentation to learn more.

+

Want to be notified whenever a new Symfony release is published? Or when a +version is not maintained anymore? Or only when a security issue is fixed? +Consider subscribing to the Symfony Roadmap Notifications.

+
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfony-7-2-0-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Fri, 29 Nov 2024 09:46:04 +0100 + https://symfony.com/blog/symfony-7-2-0-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[Symfony 5.4.49 released]]> + https://symfony.com/blog/symfony-5-4-49-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + Symfony 5.4.49 has just been released. +Here is the list of the most important changes since 5.4.48: + + bug #59023 [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (@nicolas-grekas) + +WARNING: 5.4.49 is the last version for the Symfony… + Symfony 5.4.49 has just been released. +Here is the list of the most important changes since 5.4.48:

+
    +
  • bug #59023 [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (@nicolas-grekas)
  • +
+

WARNING: 5.4.49 is the last version for the Symfony 5.4 branch. If some +of your projects are still using this version, consider upgrading as soon as +possible. However, if you can't upgrade soon, note that we still provide +security issue releases according to our release policy.

+

Want to upgrade to this new release? Because Symfony protects +backwards-compatibility very closely, this should be quite easy. Use +SymfonyInsight upgrade reports +to detect the code you will need to change in your project and +read our upgrade +documentation to learn more.

+

Want to be notified whenever a new Symfony release is published? Or when a +version is not maintained anymore? Or only when a security issue is fixed? +Consider subscribing to the Symfony Roadmap Notifications.

+
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfony-5-4-49-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Fri, 29 Nov 2024 09:39:54 +0100 + https://symfony.com/blog/symfony-5-4-49-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[SymfonyCon Vienna 2024: See you next week!]]> + https://symfony.com/blog/symfonycon-vienna-2024-see-you-next-week?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + + +SymfonyCon Vienna is just around the corner! 🎉 Next week, we’ll come together for an exciting event featuring brand-new talks, inspiring speakers, and everything you need to make the most of this gathering with the Symfony and PHP community. + +💡Pro… + + Sfconvienna2024 Blog + +SymfonyCon Vienna is just around the corner! 🎉 Next week, we’ll come together for an exciting event featuring brand-new talks, inspiring speakers, and everything you need to make the most of this gathering with the Symfony and PHP community.

+ +

💡Pro tip: Use the business meeting feature in your SymfonyLive profile to schedule meetings with sponsors ahead of time!

+ +

Not registered yet? Don’t miss out—there’s still time to grab your tickets for:

+ +
    +
  • December 3-4: Workshop Days – Choose from a variety of 1-day training sessions. Spots are filling fast!
  • +
  • December 5-6: Conference Days – Dive into 3 parallel tracks plus an unconference track, all in English.
  • +
+ +
+ +

🆕What's new in the schedule

+ + + +

🔎 Explore the great lineup of talks

+ +

In just a few days, you'll meet our inspiring experts speakers as Fabien Potencier, Viktor Pikaev, Andreas Braun, Michelle Sanver, , Ondřej Mirtes, Alexander M. Turek, David Buchmann, Marie Minasyan, Nils Adermann, Anne-Julia Seitz, Thibault Milan, Romain Ruaud, Juliette Reinders Folmer, Dave Liddament, Rob Allen, Tugdual Saunier, Simon André, Mathias Arlaud, Adrien Roches, Hubert Lenoir, Antoine Bluchet, Alexander Schranz, Alexandre Salomé, Kévin Dunglas, Nicolas Grekas, Sebastian Plagemann, Stephan Hochdörfer, Raphaël Geffroy, Peter Dietrich, Céline Deis, Johannes Wachter, Matheo Daninos, Robin Chalas, Paul Dragoonis, Antonis Kalipetis, Guillaume Moigneu, Florent Huck, Augustin Delaporte, Celeste Van Der Watt, Greg Qualls, Thomas di Luccio, Nigel Kersten, Moritz Schuh, Sebastian Seggewiß, Haylee Millar and, Kemi Elizabeth Ojogbede.

+ +

Read the detailed content of talks here.

+ +

🧑‍💻Unlock new skills and level up your expertise with our workshops!

+ +

Held on December 3-4, 2024, these workshops are crafted for developers eager to dive deep into Symfony, PHP, and modern coding practices. Here’s what you can look forward to:

+ + + +

Whether you're new to Symfony or looking to master advanced techniques, there’s something here for everyone. Don't miss the chance to learn directly from Symfony experts and apply your skills to real-world projects. Secure your spot now and get ready to accelerate your Symfony journey!

+ +

🎟️ Select the ticket of your choice

+ +

Register by clicking on Buy ticket and choose your ticket:

+ +
    +
  • "Workshops only", December 3-4
  • +
  • "Conference only", December 5-6
  • +
  • Combo ticket "Conference + Workshops" to live a full Symfony week experience!
  • +
+ +

🫵 Participate in the Unconference track

+ +

The Unconference track is a participant-driven format where attendees shape the content and discussions in real-time. Have a topic you're passionate about? Claim your slot by emailing us at events@symfony.com and set the stage for an unforgettable experience.

+ +

Each unconference talk lasts 20 minutes with a screen and projector available on both days.

+ +

🧳 Plan your participation

+ +
    +
  • Use the schedule to organize your visit.

  • +
  • Read our attendee guide for venue, accommodation, and transportation details.

  • +
  • Use the business meeting feature in your Symfony Live profile to schedule meetings with sponsors ahead of time!

  • +
+ +

🎉 Plan to attend the community evening on Thursday, December 5

+ +

Join us for a "Night at the Museum" at one of Vienna's most iconic place: Naturhistorisches Museum Wien (20 minutes by public transport from the conference). From 7:30-10:30 pm. Drinks, music & access to parts of permanent exhibition included!

+ +

💻 Save the date for the Symfony hackathon on Saturday, December 7

+ +

Everyone is welcome to join the hackday! Whether you're an experienced contributor or new to the community, your participation is highly valued as it brings a fresh perspective! More details are available here. Address: Stockwerk, Pater-Schwartz-Gasse 11A, 1150 Wien - Map

+ +

💡 Follow the "conferences" blog posts to stay updated!

+ +

We can't wait to meet you in person to learn and share the latest about Symfony. Join us and be part of the @symfony community! 🫶

+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfonycon-vienna-2024-see-you-next-week?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Thu, 28 Nov 2024 15:45:00 +0100 + https://symfony.com/blog/symfonycon-vienna-2024-see-you-next-week?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[New in Symfony 7.2: Misc. Improvements (Part 2)]]> + https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-2?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + +Access to the Estimated Password Strength + + + + + + + + Contributed by + Yannick + in + #54881… + +

Access to the Estimated Password Strength

+
+
+ + Yannick + +
+
+ Contributed by + Yannick + in + #54881 + +
+
+

The PasswordStrength constraint validates that the given password has reached +a minimum strength configured in the constraint. In Symfony 7.2, we've changed +the visibility of the estimateStrength() validator method from private to public.

+

This allows you to access the estimated password strength and display it, for +example, in the interface, so users can better understand the quality of their +passwords.

+ +
+

Simpler RequestStack Unit Testing

+
+
+ + Alexander Schranz + +
+
+ Contributed by + Alexander Schranz + in + #57909 + +
+
+

When using the RequestStack in unit tests, you previously needed code like +this to configure the requests:

+
+
+ +
$requestStack = new RequestStack();
+$requestStack->push(Request::create('/'));
+$someCustomClass = new MyCustomClass($requestStack);
+
+
+

In Symfony 7.2, we've simplified this by adding a constructor to RequestStack +that accepts an array of requests:

+
+
+ +
$someCustomClass = new MyCustomClass(new RequestStack([
+    Request::create('/'),
+]));
+
+
+
+
+

Default Action in the HTML Sanitizer

+
+
+ + Jordi Boggiano + +
+
+ Contributed by + Jordi Boggiano + in + #57399 + +
+
+

In Symfony 7.2, we've added a new defaultAction() method in the HtmlSanitizer component. +This method sets the default action for elements that are not explicitly allowed +or blocked:

+
+
+ +
use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
+use Symfony\Component\HtmlSanitizer\HtmlSanitizerAction;
+
+$config = (new HtmlSanitizerConfig())
+    ->defaultAction(HtmlSanitizerAction::Block)
+    ->allowElement('p');
+
+$sanitizer = new HtmlSanitizer($config);
+
+
+

HtmlSanitizerAction is a PHP enum with three cases: Drop (removes the element +and its children); Block (removes the element but keeps its children); and Allow +(keeps the element).

+
+
+

Allow Using defaultNull() on Boolean Nodes

+
+
+ + Alexandre Daubois + +
+
+ Contributed by + Alexandre Daubois + in + #58490 + +
+
+

The current defaultNull() of BooleanNode, used when +defining and processing configuration values, casts null values to true. +In Symfony 7.2, we've updated this method so you can define nullable boolean +values properly:

+
+
+ +
->booleanNode('enabled')->defaultNull()->end()
+
+
+
+
+

Better IP Address Anonymization

+
+
+ + Alexandre Daubois + +
+
+ Contributed by + Alexandre Daubois + in + #58038 + +
+
+

The IpUtils class includes an anonymize() method to obscure part of the IP +address for user privacy. In Symfony 7.2, we've added two new arguments to this +method so you can specify how many bytes to anonymize:

+
+
+ +
use Symfony\Component\HttpFoundation\IpUtils;
+
+$ipv4 = '123.234.235.236';
+// for IPv4 addresses, you can hide 0 to 4 bytes
+$anonymousIpv4 = IpUtils::anonymize($ipv4, 3);
+// $anonymousIpv4 = '123.0.0.0'
+
+$ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
+// for IPv6 addresses, you can hide 0 to 16 bytes
+// (you must define the second argument (bytes to anonymize in IPv4 addresses)
+// even when you are only anonymizing IPv6 addresses)
+$anonymousIpv6 = IpUtils::anonymize($ipv6, 3, 10);
+// $anonymousIpv6 = '2a01:198:603::'
+
+
+
+
+

String Configuration Node

+
+
+ + Raffaele Carelle + +
+
+ Contributed by + Raffaele Carelle + in + #58428 + +
+
+

When defining a configuration tree, you can use many node types for +configuration values (boolean, integers, floats, enums, arrays, etc.). However, +you couldn't define string values directly; they were specified as scalar nodes.

+

In Symfony 7.2, we've added a string node type and a stringNode() method, +allowing you to define configuration values as strings explicitly:

+
+
+ +
$rootNode
+    ->children()
+        // ...
+        ->stringNode('username')
+            ->defaultValue('root')
+        ->end()
+        ->stringNode('password')
+            ->defaultValue('root')
+        ->end()
+    ->end()
+;
+
+
+
+
+

Security Profiler Improvements

+
+
+ + Mathieu + +
+
+ Contributed by + Mathieu + in + #57525 + , #57369 + and #57692 + +
+
+

In Symfony 7.2, the security panel of the Symfony Profiler has been improved +with several new features. First, the authenticators tab has been updated. +Previously, authenticators that didn't support the request were not shown:

+
+ Symfony security profiler panel with no authenticator shown +
+

Now, to make debugging easier, you can see all the application's authenticators. +If an authenticator doesn't support the request, it will be labeled as "not supported":

+
+ Symfony 7.2 security profiler panel with all authenticators shown +
+

When using a stateful firewall, the token tab of de-authenticated users now +includes a link to the request that contained the previously authenticated user:

+
+ Symfony 7.2 security profiler panel with a link to previous authenticated user +
+

The authenticators tab has also been redesigned to display information more +clearly. It now also shows whether an authenticator is lazy and includes any exception +passed to the onAuthenticationFailure() method:

+
+ Symfony 7.2 security profiler panel with more and redesigned information in the authenticators tab +
+
+

This is the final blog post in the New in Symfony 7.2 series. We hope you +enjoyed it and discovered some of the great new features introduced in Symfony 7.2. +Check out the Symfony minor version upgrade guide to learn how to upgrade to 7.2 +from other 7.x versions. Meanwhile, we've already started working on Symfony 7.3, +which will be released at the end of May 2025.

+
+
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-2?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Thu, 28 Nov 2024 08:40:00 +0100 + https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-2?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[Symfony 7.1.9 released]]> + https://symfony.com/blog/symfony-7-1-9-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + Symfony 7.1.9 has just been released. +Here is the list of the most important changes since 7.1.8: + + bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas) +bug #58562 [HttpClient] Close gracefull when the server closes… + Symfony 7.1.9 has just been released. +Here is the list of the most important changes since 7.1.8:

+
    +
  • bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)
  • +
  • bug #58562 [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)
  • +
  • bug #59007 [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)
  • +
  • bug #58963 [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)
  • +
  • bug #58983 [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)
  • +
  • bug #58956 [DoctrineBridge] Fix Connection::createSchemaManager() for Doctrine DBAL v2 (@neodevcode)
  • +
  • bug #58959 [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)
  • +
  • bug #58964 [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)
  • +
  • bug #58952 [Cache] silence warnings issued by Redis Sentinel on connection issues (@xabbuh)
  • +
  • bug #58859 [AssetMapper] ignore missing directory in isVendor() (@alexislefebvre)
  • +
  • bug #58917 [OptionsResolver] Allow Union/Intersection Types in Resolved Closures (@zanbaldwin)
  • +
  • bug #58822 [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)
  • +
  • bug #58865 Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)
  • +
  • bug #58921 [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)
  • +
  • bug #58908 [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)
  • +
  • bug #58938 [Cache] make RelayProxyTrait compatible with relay extension 0.9.0 (@xabbuh)
  • +
  • bug #58924 [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)
  • +
  • bug #58915 [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58919 [WebProfilerBundle] Twig deprecations (@mazodude)
  • +
  • bug #58914 [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58870 [Serializer][Validator] prevent failures around not existing TypeInfo classes (@xabbuh)
  • +
  • bug #58872 [PropertyInfo][Serializer][Validator] TypeInfo 7.2 compatibility (@mtarld)
  • +
  • bug #58875 [HttpClient] Removed body size limit (Carl Julian Sauter)
  • +
  • bug #58866 [Validator] fix compatibility with PHP < 8.2.4 (@xabbuh)
  • +
  • bug #58862 [Notifier] Fix GoIpTransport (@nicolas-grekas)
  • +
  • bug #58860 [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)
  • +
  • bug #58836 Work around parse_url() bug (bis) (@nicolas-grekas)
  • +
  • bug #58818 [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)
  • +
  • bug #58828 [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)
  • +
  • bug #58842 [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)
  • +
  • bug #58850 [HttpClient] fix PHP 7.2 compatibility (@xabbuh)
  • +
+

Want to upgrade to this new release? Because Symfony protects +backwards-compatibility very closely, this should be quite easy. Use +SymfonyInsight upgrade reports +to detect the code you will need to change in your project and +read our upgrade +documentation to learn more.

+

Want to be notified whenever a new Symfony release is published? Or when a +version is not maintained anymore? Or only when a security issue is fixed? +Consider subscribing to the Symfony Roadmap Notifications.

+
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfony-7-1-9-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Wed, 27 Nov 2024 14:02:38 +0100 + https://symfony.com/blog/symfony-7-1-9-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[Symfony 6.4.16 released]]> + https://symfony.com/blog/symfony-6-4-16-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + Symfony 6.4.16 has just been released. +Here is the list of the most important changes since 6.4.15: + + bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas) +bug #58562 [HttpClient] Close gracefull when the server closes… + Symfony 6.4.16 has just been released. +Here is the list of the most important changes since 6.4.15:

+
    +
  • bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)
  • +
  • bug #58562 [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)
  • +
  • bug #59007 [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)
  • +
  • bug #58963 [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)
  • +
  • bug #58983 [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)
  • +
  • bug #58956 [DoctrineBridge] Fix Connection::createSchemaManager() for Doctrine DBAL v2 (@neodevcode)
  • +
  • bug #58959 [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)
  • +
  • bug #58964 [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)
  • +
  • bug #58952 [Cache] silence warnings issued by Redis Sentinel on connection issues (@xabbuh)
  • +
  • bug #58859 [AssetMapper] ignore missing directory in isVendor() (@alexislefebvre)
  • +
  • bug #58917 [OptionsResolver] Allow Union/Intersection Types in Resolved Closures (@zanbaldwin)
  • +
  • bug #58822 [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)
  • +
  • bug #58865 Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)
  • +
  • bug #58921 [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)
  • +
  • bug #58908 [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)
  • +
  • bug #58938 [Cache] make RelayProxyTrait compatible with relay extension 0.9.0 (@xabbuh)
  • +
  • bug #58924 [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)
  • +
  • bug #58915 [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58919 [WebProfilerBundle] Twig deprecations (@mazodude)
  • +
  • bug #58914 [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58875 [HttpClient] Removed body size limit (Carl Julian Sauter)
  • +
  • bug #58862 [Notifier] Fix GoIpTransport (@nicolas-grekas)
  • +
  • bug #58860 [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)
  • +
  • bug #58836 Work around parse_url() bug (bis) (@nicolas-grekas)
  • +
  • bug #58818 [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)
  • +
  • bug #58828 [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)
  • +
  • bug #58842 [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)
  • +
  • bug #58850 [HttpClient] fix PHP 7.2 compatibility (@xabbuh)
  • +
+

Want to upgrade to this new release? Because Symfony protects +backwards-compatibility very closely, this should be quite easy. Use +SymfonyInsight upgrade reports +to detect the code you will need to change in your project and +read our upgrade +documentation to learn more.

+

Want to be notified whenever a new Symfony release is published? Or when a +version is not maintained anymore? Or only when a security issue is fixed? +Consider subscribing to the Symfony Roadmap Notifications.

+
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfony-6-4-16-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Wed, 27 Nov 2024 13:54:27 +0100 + https://symfony.com/blog/symfony-6-4-16-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[Symfony 5.4.48 released]]> + https://symfony.com/blog/symfony-5-4-48-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + Symfony 5.4.48 has just been released. +Here is the list of the most important changes since 5.4.47: + + bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas) +bug #58562 [HttpClient] Close gracefull when the server closes… + Symfony 5.4.48 has just been released. +Here is the list of the most important changes since 5.4.47:

+
    +
  • bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)
  • +
  • bug #58562 [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)
  • +
  • bug #59007 [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)
  • +
  • bug #58963 [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)
  • +
  • bug #58983 [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)
  • +
  • bug #58959 [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)
  • +
  • bug #58964 [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)
  • +
  • bug #58822 [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)
  • +
  • bug #58865 Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)
  • +
  • bug #58921 [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)
  • +
  • bug #58908 [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)
  • +
  • bug #58924 [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)
  • +
  • bug #58915 [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58919 [WebProfilerBundle] Twig deprecations (@mazodude)
  • +
  • bug #58914 [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)
  • +
  • bug #58875 [HttpClient] Removed body size limit (Carl Julian Sauter)
  • +
  • bug #58860 [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)
  • +
  • bug #58836 Work around parse_url() bug (bis) (@nicolas-grekas)
  • +
  • bug #58818 [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)
  • +
  • bug #58828 [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)
  • +
  • bug #58842 [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)
  • +
  • bug #58850 [HttpClient] fix PHP 7.2 compatibility (@xabbuh)
  • +
+

Want to upgrade to this new release? Because Symfony protects +backwards-compatibility very closely, this should be quite easy. Use +SymfonyInsight upgrade reports +to detect the code you will need to change in your project and +read our upgrade +documentation to learn more.

+

Want to be notified whenever a new Symfony release is published? Or when a +version is not maintained anymore? Or only when a security issue is fixed? +Consider subscribing to the Symfony Roadmap Notifications.

+
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfony-5-4-48-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Wed, 27 Nov 2024 13:48:58 +0100 + https://symfony.com/blog/symfony-5-4-48-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+
+
diff --git a/tests/ProfilerSubscriberTest.php b/tests/ProfilerSubscriberTest.php index 8d02dff5a..8ada6fdd4 100644 --- a/tests/ProfilerSubscriberTest.php +++ b/tests/ProfilerSubscriberTest.php @@ -28,10 +28,10 @@ public function testAjaxReplaceHeaderNotSet(int $requestType, bool $debug, bool $response = new Response(); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), $requestType, $response); - $listener = new ProfilerSubscriber($debug); - $listener->onKernelResponse($event); + $subscriber = new ProfilerSubscriber($debug); + $subscriber->onKernelResponse($event); - $this->assertFalse($response->headers->has('Symfony-Debug-Toolbar-Replace')); + self::assertFalse($response->headers->has('Symfony-Debug-Toolbar-Replace')); } /** @@ -53,9 +53,15 @@ public function testAjaxReplaceHeaderOnEnabledAndXHR(): void $response = new Response(); $event = new ResponseEvent($this->createMock(Kernel::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); - $listener = new ProfilerSubscriber(true); - $listener->onKernelResponse($event); + $subscriber = new ProfilerSubscriber(true); + $subscriber->onKernelResponse($event); - $this->assertEquals('1', $response->headers->get('Symfony-Debug-Toolbar-Replace')); + self::assertEquals('1', $response->headers->get('Symfony-Debug-Toolbar-Replace')); + } + + public function testSubscriberIsSubscribedToResponseEvent(): void + { + self::assertArrayHasKey(ResponseEvent::class, ProfilerSubscriber::getSubscribedEvents()); + self::assertIsString(ProfilerSubscriber::getSubscribedEvents()[ResponseEvent::class]); } } From f259ab5e20a2d3115afb61cc61f217af335e6161 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Thu, 12 Dec 2024 23:12:56 +0100 Subject: [PATCH 09/31] chore: dependency update - symfony, twig, phpunit (#6) --- composer.lock | 199 ++++++++++++++++++++++++-------------------------- 1 file changed, 97 insertions(+), 102 deletions(-) diff --git a/composer.lock b/composer.lock index 93b8b63c8..2021d23ed 100644 --- a/composer.lock +++ b/composer.lock @@ -2102,16 +2102,16 @@ }, { "name": "symfony/cache", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a" + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a", - "reference": "2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a", + "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20", "shasum": "" }, "require": { @@ -2180,7 +2180,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.2.0" + "source": "https://github.com/symfony/cache/tree/v7.2.1" }, "funding": [ { @@ -2196,7 +2196,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T15:21:05+00:00" + "time": "2024-12-07T08:08:50+00:00" }, { "name": "symfony/cache-contracts", @@ -2425,16 +2425,16 @@ }, { "name": "symfony/console", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -2498,7 +2498,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.0" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -2514,7 +2514,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/css-selector", @@ -2871,16 +2871,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe" + "reference": "6150b89186573046167796fa5f3f76601d5145f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/672b3dd1ef8b87119b446d67c58c106c43f965fe", - "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", + "reference": "6150b89186573046167796fa5f3f76601d5145f8", "shasum": "" }, "require": { @@ -2926,7 +2926,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.0" + "source": "https://github.com/symfony/error-handler/tree/v7.2.1" }, "funding": [ { @@ -2942,7 +2942,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:35:02+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/event-dispatcher", @@ -3300,16 +3300,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "a8d0da4110fe643ab3cde7c938a03e222fe787c6" + "reference": "1c630f4697c9bd87b342e8090cc9022071af4d77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a8d0da4110fe643ab3cde7c938a03e222fe787c6", - "reference": "a8d0da4110fe643ab3cde7c938a03e222fe787c6", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/1c630f4697c9bd87b342e8090cc9022071af4d77", + "reference": "1c630f4697c9bd87b342e8090cc9022071af4d77", "shasum": "" }, "require": { @@ -3430,7 +3430,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.2.0" + "source": "https://github.com/symfony/framework-bundle/tree/v7.2.1" }, "funding": [ { @@ -3446,27 +3446,27 @@ "type": "tidelift" } ], - "time": "2024-11-20T16:27:35+00:00" + "time": "2024-12-07T13:24:01+00:00" }, { "name": "symfony/http-client", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "955e43336aff03df1e8a8e17daefabb0127a313b" + "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/955e43336aff03df1e8a8e17daefabb0127a313b", - "reference": "955e43336aff03df1e8a8e17daefabb0127a313b", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.3|^3.5.1", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -3525,7 +3525,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.0" + "source": "https://github.com/symfony/http-client/tree/v7.2.1" }, "funding": [ { @@ -3541,20 +3541,20 @@ "type": "tidelift" } ], - "time": "2024-11-29T08:22:02+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.1", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9" + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", "shasum": "" }, "require": { @@ -3603,7 +3603,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" }, "funding": [ { @@ -3619,7 +3619,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T12:02:18+00:00" + "time": "2024-12-07T08:49:48+00:00" }, { "name": "symfony/http-foundation", @@ -3701,16 +3701,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d" + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", - "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d8ae58eecae44c8e66833e76cc50a4ad3c002d97", + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97", "shasum": "" }, "require": { @@ -3795,7 +3795,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.1" }, "funding": [ { @@ -3811,7 +3811,7 @@ "type": "tidelift" } ], - "time": "2024-11-29T08:42:40+00:00" + "time": "2024-12-11T12:09:10+00:00" }, { "name": "symfony/monolog-bridge", @@ -4288,16 +4288,16 @@ }, { "name": "symfony/property-info", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "b00580d9d7c9654e1df95df85105d0da67418b3f" + "reference": "65fb9be15380f949d72ff405473cce733364b8b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/b00580d9d7c9654e1df95df85105d0da67418b3f", - "reference": "b00580d9d7c9654e1df95df85105d0da67418b3f", + "url": "https://api.github.com/repos/symfony/property-info/zipball/65fb9be15380f949d72ff405473cce733364b8b4", + "reference": "65fb9be15380f949d72ff405473cce733364b8b4", "shasum": "" }, "require": { @@ -4351,7 +4351,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.2.0" + "source": "https://github.com/symfony/property-info/tree/v7.2.1" }, "funding": [ { @@ -4367,7 +4367,7 @@ "type": "tidelift" } ], - "time": "2024-11-27T09:50:52+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/routing", @@ -4946,16 +4946,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "9958f5a5b6640734fe4b24c18897191f77a02c61" + "reference": "d5cdf4d59da5ab44ebd7503480c22d8235887de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9958f5a5b6640734fe4b24c18897191f77a02c61", - "reference": "9958f5a5b6640734fe4b24c18897191f77a02c61", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d5cdf4d59da5ab44ebd7503480c22d8235887de0", + "reference": "d5cdf4d59da5ab44ebd7503480c22d8235887de0", "shasum": "" }, "require": { @@ -5036,7 +5036,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.2.0" + "source": "https://github.com/symfony/twig-bridge/tree/v7.2.1" }, "funding": [ { @@ -5052,7 +5052,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T14:26:33+00:00" + "time": "2024-12-07T09:50:32+00:00" }, { "name": "symfony/twig-bundle", @@ -5140,29 +5140,24 @@ }, { "name": "symfony/type-info", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "e0bfd95bceb3886c59487828537691aecb7d9c6b" + "reference": "4f402070b08ad0b87e9cadbb07b87fb36061e6e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/e0bfd95bceb3886c59487828537691aecb7d9c6b", - "reference": "e0bfd95bceb3886c59487828537691aecb7d9c6b", + "url": "https://api.github.com/repos/symfony/type-info/zipball/4f402070b08ad0b87e9cadbb07b87fb36061e6e4", + "reference": "4f402070b08ad0b87e9cadbb07b87fb36061e6e4", "shasum": "" }, "require": { "php": ">=8.2", "psr/container": "^1.1|^2.0" }, - "conflict": { - "phpstan/phpdoc-parser": "<1.0", - "symfony/dependency-injection": "<6.4" - }, "require-dev": { - "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0" + "phpstan/phpdoc-parser": "^1.0|^2.0" }, "type": "library", "autoload": { @@ -5200,7 +5195,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.2.0" + "source": "https://github.com/symfony/type-info/tree/v7.2.1" }, "funding": [ { @@ -5216,7 +5211,7 @@ "type": "tidelift" } ], - "time": "2024-11-18T09:51:31+00:00" + "time": "2024-12-11T07:49:41+00:00" }, { "name": "symfony/uid", @@ -5956,7 +5951,7 @@ }, { "name": "twig/extra-bundle", - "version": "v3.16.0", + "version": "v3.17.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", @@ -6014,7 +6009,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.16.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.17.0" }, "funding": [ { @@ -6030,16 +6025,16 @@ }, { "name": "twig/markdown-extra", - "version": "v3.16.0", + "version": "v3.17.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "25f23c02936f8c7157a8413154c06a462c9c20d3" + "reference": "76219b06e104a706879752e6e4d79f63ef7e9f23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/25f23c02936f8c7157a8413154c06a462c9c20d3", - "reference": "25f23c02936f8c7157a8413154c06a462c9c20d3", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/76219b06e104a706879752e6e4d79f63ef7e9f23", + "reference": "76219b06e104a706879752e6e4d79f63ef7e9f23", "shasum": "" }, "require": { @@ -6048,7 +6043,7 @@ "twig/twig": "^3.13|^4.0" }, "require-dev": { - "erusev/parsedown": "^1.7", + "erusev/parsedown": "dev-master as 1.x-dev", "league/commonmark": "^1.0|^2.0", "league/html-to-markdown": "^4.8|^5.0", "michelf/php-markdown": "^1.8|^2.0", @@ -6086,7 +6081,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.16.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.17.0" }, "funding": [ { @@ -6098,20 +6093,20 @@ "type": "tidelift" } ], - "time": "2024-09-03T20:17:35+00:00" + "time": "2024-12-02T08:57:02+00:00" }, { "name": "twig/twig", - "version": "v3.16.0", + "version": "v3.17.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "475ad2dc97d65d8631393e721e7e44fb544f0561" + "reference": "677ef8da6497a03048192aeeb5aa3018e379ac71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/475ad2dc97d65d8631393e721e7e44fb544f0561", - "reference": "475ad2dc97d65d8631393e721e7e44fb544f0561", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/677ef8da6497a03048192aeeb5aa3018e379ac71", + "reference": "677ef8da6497a03048192aeeb5aa3018e379ac71", "shasum": "" }, "require": { @@ -6166,7 +6161,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.16.0" + "source": "https://github.com/twigphp/Twig/tree/v3.17.1" }, "funding": [ { @@ -6178,7 +6173,7 @@ "type": "tidelift" } ], - "time": "2024-11-29T08:27:05+00:00" + "time": "2024-12-12T09:58:10+00:00" }, { "name": "webmozart/assert", @@ -6645,16 +6640,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.7", + "version": "11.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { @@ -6673,7 +6668,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.4.1" + "phpunit/phpunit": "^11.5.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -6711,7 +6706,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -6719,7 +6714,7 @@ "type": "github" } ], - "time": "2024-10-09T06:21:38+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", @@ -6968,16 +6963,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.0", + "version": "11.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7" + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0569902506a6c0878930b87ea79ec3b50ea563f7", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2b94d4f2450b9869fa64a46fd8a6a41997aef56a", + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a", "shasum": "" }, "require": { @@ -7049,7 +7044,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.0" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.1" }, "funding": [ { @@ -7065,7 +7060,7 @@ "type": "tidelift" } ], - "time": "2024-12-06T05:57:38+00:00" + "time": "2024-12-11T10:52:48+00:00" }, { "name": "sebastian/cli-parser", @@ -7126,23 +7121,23 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { @@ -7171,7 +7166,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" }, "funding": [ { @@ -7179,7 +7174,7 @@ "type": "github" } ], - "time": "2024-07-03T04:44:28+00:00" + "time": "2024-12-12T09:59:06+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", From 8c096f0b5ce229e04df25df7432f3ae576852476 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Fri, 13 Dec 2024 00:56:16 +0100 Subject: [PATCH 10/31] chore: clean up after bundle 0.9 release (#7) --- assets/styles/app.css | 8 +++ composer.json | 4 +- composer.lock | 24 ++++---- config/packages/llm_chain.yaml | 35 ++++++++++-- src/Blog/Embedder.php | 50 +++------------- tests/Blog/EmbedderTest.php | 101 --------------------------------- 6 files changed, 59 insertions(+), 163 deletions(-) delete mode 100644 tests/Blog/EmbedderTest.php diff --git a/assets/styles/app.css b/assets/styles/app.css index a3a7dcd3f..a546e8d5e 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -72,10 +72,18 @@ body { .rag & { background: #dc8b6e; + + a { + color: #f4e973; + } } .youtube & { background: #df3535; + + a { + color: #3e2926; + } } .wikipedia & { diff --git a/composer.json b/composer.json index 40b14cafc..bb07f8790 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "codewithkyrian/chromadb-php": "^0.3.0", "league/commonmark": "^2.6", "php-llm/llm-chain": "^0.9.3", - "php-llm/llm-chain-bundle": "dev-feat-prepare-0.8", + "php-llm/llm-chain-bundle": "^0.9", "phpdocumentor/reflection-docblock": "^5.5", "phpstan/phpdoc-parser": "^1.33", "runtime/frankenphp-symfony": "^0.2.0", @@ -93,8 +93,6 @@ "App\\Tests\\": "tests/" } }, - "minimum-stability": "stable", - "prefer-stable": true, "scripts": { "post-install-cmd": [ "@auto-scripts" diff --git a/composer.lock b/composer.lock index 2021d23ed..2a2d67dbe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b3492f124ff5c8349f283273cd9e42fc", + "content-hash": "18c7e48ce770bdde7cc49cde13bb84d2", "packages": [ { "name": "codewithkyrian/chromadb-php", @@ -1177,16 +1177,16 @@ }, { "name": "php-llm/llm-chain-bundle", - "version": "dev-feat-prepare-0.8", + "version": "0.9", "source": { "type": "git", "url": "https://github.com/php-llm/llm-chain-bundle.git", - "reference": "5db2d4643339e7dce884a6caea61e39b55ebc23a" + "reference": "ca77abb0bf1fab8d551408a6c95bd8ad2b69a982" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-llm/llm-chain-bundle/zipball/5db2d4643339e7dce884a6caea61e39b55ebc23a", - "reference": "5db2d4643339e7dce884a6caea61e39b55ebc23a", + "url": "https://api.github.com/repos/php-llm/llm-chain-bundle/zipball/ca77abb0bf1fab8d551408a6c95bd8ad2b69a982", + "reference": "ca77abb0bf1fab8d551408a6c95bd8ad2b69a982", "shasum": "" }, "require": { @@ -1197,9 +1197,9 @@ "symfony/framework-bundle": "^6.4 || ^7.0" }, "require-dev": { - "php-cs-fixer/shim": "^3.64", + "php-cs-fixer/shim": "^3.65", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^11.5" }, "type": "symfony-bundle", "autoload": { @@ -1220,9 +1220,9 @@ "description": "Symfony integration bundle for php-llm/llm-chain", "support": { "issues": "https://github.com/php-llm/llm-chain-bundle/issues", - "source": "https://github.com/php-llm/llm-chain-bundle/tree/feat-prepare-0.8" + "source": "https://github.com/php-llm/llm-chain-bundle/tree/0.9" }, - "time": "2024-12-08T16:49:35+00:00" + "time": "2024-12-12T23:47:19+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -8376,10 +8376,8 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "php-llm/llm-chain-bundle": 20 - }, - "prefer-stable": true, + "stability-flags": {}, + "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.4", diff --git a/config/packages/llm_chain.yaml b/config/packages/llm_chain.yaml index 70a193f61..8b5bdc6e0 100644 --- a/config/packages/llm_chain.yaml +++ b/config/packages/llm_chain.yaml @@ -1,9 +1,23 @@ llm_chain: platform: + # anthropic: + # api_key: '%env(ANTHROPIC_API_KEY)%' + # azure: + # gpt_deployment: + # base_url: '%env(AZURE_GPT_BASE_URL)%' + # deployment: '%env(AZURE_GPT_DEPLOYMENT)%' + # api_key: '%env(AZURE_GPT_API_KEY)%' + # api_version: '%env(AZURE_GPT_VERSION)%' + # embeddings_deployment: + # base_url: '%env(AZURE_EMBEDDINGS_BASE_URL)%' + # deployment: '%env(AZURE_EMBEDDINGS_DEPLOYMENT)%' + # api_key: '%env(AZURE_EMBEDDINGS_API_KEY)%' + # api_version: '%env(AZURE_EMBEDDINGS_VERSION)%' openai: api_key: '%env(OPENAI_API_KEY)%' chain: rag: + # platform: 'llm_chain.platform.anthropic' model: name: 'GPT' version: 'gpt-4o-mini' @@ -18,23 +32,34 @@ llm_chain: model: name: 'GPT' version: 'gpt-4o-mini' + options: + temperature: 0.5 tools: - 'PhpLlm\LlmChain\Chain\ToolBox\Tool\Wikipedia' store: chroma_db: symfonycon: - host: '%env(CHROMADB_HOST)%' collection: 'symfony_blog' + # web_summer_camp: + # host: '%env(CHROMADB_HOST)%' + # collection: 'wsc_program' + embedder: + default: + # platform: 'llm_chain.platform.anthropic' + # store: 'llm_chain.store.chroma_db.symfonycon' + model: + name: 'Embeddings' + version: 'text-embedding-ada-002' services: _defaults: autowire: true autoconfigure: true + # PhpLlm\LlmChain\Chain\ToolBox\Tool\Clock: ~ + # PhpLlm\LlmChain\Chain\ToolBox\Tool\OpenMeteo: ~ + # PhpLlm\LlmChain\Chain\ToolBox\Tool\SerpApi: + # $apiKey: '%env(SERP_API_KEY)%' PhpLlm\LlmChain\Chain\ToolBox\Tool\Wikipedia: ~ PhpLlm\LlmChain\Chain\ToolBox\Tool\SimilaritySearch: ~ - # TODO: move to configuration - PhpLlm\LlmChain\Bridge\OpenAI\Embeddings: ~ - PhpLlm\LlmChain\Model\EmbeddingsModel: '@PhpLlm\LlmChain\Bridge\OpenAI\Embeddings' - diff --git a/src/Blog/Embedder.php b/src/Blog/Embedder.php index 8cb8eec62..86343fdc8 100644 --- a/src/Blog/Embedder.php +++ b/src/Blog/Embedder.php @@ -4,57 +4,25 @@ namespace App\Blog; -use Codewithkyrian\ChromaDB\Client; -use PhpLlm\LlmChain\Bridge\OpenAI\Embeddings; -use PhpLlm\LlmChain\Document\Vector; -use PhpLlm\LlmChain\Model\Response\AsyncResponse; -use PhpLlm\LlmChain\Model\Response\VectorResponse; -use PhpLlm\LlmChain\PlatformInterface; +use PhpLlm\LlmChain\Document\Metadata; +use PhpLlm\LlmChain\Document\TextDocument; +use PhpLlm\LlmChain\Embedder as LlmChainEmbedder; final readonly class Embedder { public function __construct( private Loader $loader, - private PlatformInterface $platform, - private Client $chromaClient, + private LlmChainEmbedder $embedder, ) { } public function embedBlog(): void { - $posts = $this->loader->load(); - $vectors = $this->createEmbeddings($posts); - $this->pushToChromaDB($posts, $vectors); - } - - /** - * @param Post[] $posts - * - * @return Vector[] - */ - private function createEmbeddings(array $posts): array - { - $texts = array_map(fn (Post $post) => $post->toString(), $posts); - $response = $this->platform->request(new Embeddings(), $texts); - - assert($response instanceof AsyncResponse); - $response = $response->unwrap(); - assert($response instanceof VectorResponse); - - return $response->getContent(); - } - - /** - * @param Post[] $posts - * @param Vector[] $vectors - */ - private function pushToChromaDB(array $posts, array $vectors): void - { - $collection = $this->chromaClient->getOrCreateCollection('symfony_blog'); - - $ids = array_map(fn (Post $post) => $post->id, $posts); - $vectors = array_map(fn (Vector $vector) => $vector->getData(), $vectors); + $documents = []; + foreach ($this->loader->load() as $post) { + $documents[] = new TextDocument($post->id, $post->toString(), new Metadata($post->toArray())); + } - $collection->upsert($ids, $vectors, $posts); + $this->embedder->embed($documents); } } diff --git a/tests/Blog/EmbedderTest.php b/tests/Blog/EmbedderTest.php deleted file mode 100644 index 51652be68..000000000 --- a/tests/Blog/EmbedderTest.php +++ /dev/null @@ -1,101 +0,0 @@ -createMock(PlatformInterface::class); - $chromaClient = $this->createMock(Client::class); - $posts = $loader->load(); - $vectors = [ - new Vector([0.1, 0.2, 0.3]), - new Vector([0.4, 0.5, 0.6]), - new Vector([0.7, 0.8, 0.9]), - new Vector([1.0, 1.1, 1.2]), - new Vector([1.3, 1.4, 1.5]), - new Vector([1.6, 1.7, 1.8]), - new Vector([1.9, 2.0, 2.1]), - new Vector([2.2, 2.3, 2.4]), - new Vector([2.5, 2.6, 2.7]), - new Vector([2.8, 2.9, 3.0]), - ]; - $platform - ->method('request') - ->willReturn($this->createAsyncResponse($vectors)); - - $collection = $this->createMock(CollectionResource::class); - $chromaClient - ->expects($this->once()) - ->method('getOrCreateCollection') - ->with('symfony_blog') - ->willReturn($collection); - - $collection - ->expects($this->once()) - ->method('upsert') - ->with( - array_map(fn (Post $post) => $post->id, $posts), - array_map(fn (Vector $vector) => $vector->getData(), $vectors), - $posts, - ); - - $embedder = new Embedder($loader, $platform, $chromaClient); - $embedder->embedBlog(); - } - - /** - * @param Vector[] $vectors - */ - private function createAsyncResponse(array $vectors): AsyncResponse - { - $converter = new class($vectors) implements ResponseConverter { - /** - * @param Vector[] $vectors - */ - public function __construct(private readonly array $vectors) - { - } - - public function supports(Model $model, object|array|string $input): bool - { - return true; - } - - public function convert(HttpResponse $response, array $options = []): LlmResponse - { - return new VectorResponse(...$this->vectors); - } - }; - - return new AsyncResponse($converter, new MockResponse()); - } -} From 2817e6606af80dbf5ef78d58bd8b0aa106c21df7 Mon Sep 17 00:00:00 2001 From: wickedOne Date: Sat, 14 Dec 2024 07:56:37 +0000 Subject: [PATCH 11/31] docs: minor readme improvements (#8) --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5bd30c6db..68377bd8c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Simple Symfony demo application on top of [LLM Chain](https://github.com/php-llm/llm-chain) and its [integration bundle](https://github.com/php-llm/llm-chain-bundle). ## Examples + ![demo.png](demo.png) ## Requirements @@ -34,6 +35,7 @@ The setup is split into three parts, the Symfony application, the OpenAI configu ### 1. Symfony App Checkout the repository, start the docker environment and install dependencies: + ```shell git clone git@github.com:php-llm/symfony-demo.git cd symfony-demo @@ -44,12 +46,20 @@ docker compose run composer install Now you should be able to open https://localhost/ in your browser, and the chatbot UI should be available for you to start chatting. +> [!NOTE] +> You might have to bypass the security warning of your browser with regard to self-signed certificates. + ### 2. OpenAI Configuration For using GPT and embedding models from OpenAI, you need to configure an OpenAI API key as environment variable. This requires you to have an OpenAI account, create a valid API key and set it as `OPENAI_API_KEY` in `.env.local` file. +```shell +echo "OPENAI_API_KEY='sk-...'" > .env.local +``` + Verify the success of this step by running the following command: + ```shell docker compose exec app bin/console debug:dotenv ``` @@ -58,23 +68,25 @@ You should be able to see the `OPENAI_API_KEY` in the list of environment variab ### 3. Chroma DB Initialization -The Chroma DB is a vector store that is used to store embeddings of the chatbot's context. +The [Chroma DB](https://www.trychroma.com/) is a vector store that is used to store embeddings of the chatbot's context. To initialize the Chroma DB, you need to run the following command: + ```shell docker compose exec app bin/console app:blog:embed -vv ``` Now you should be able to run the test command and get some results: + ```shell docker compose exec app bin/console app:chroma:test ``` -**Don't forget to set up the project in your favorite IDE or editor.** +**Don't forget to set up the project in your favorite IDE or editor.** ## Functionality * The chatbot application is a simple and small Symfony 7.2 application. -* The UI is coupled to a Twig LiveComponent, that integrates different `Chat` implementations on top of the user's session. +* The UI is coupled to a [Twig LiveComponent](https://symfony.com/bundles/ux-live-component/current/index.html), that integrates different `Chat` implementations on top of the user's session. * You can reset the chat context by hitting the `Reset` button in the top right corner. -* You find three different usage scenarios in the upper navbar. +* You find three different usage scenarios in the upper navbar. \ No newline at end of file From c0fcd55cf2d6ffc3324f57a87d08be7233ff0b80 Mon Sep 17 00:00:00 2001 From: wickedOne Date: Sat, 14 Dec 2024 13:38:33 +0000 Subject: [PATCH 12/31] refactor: moving classes to a more component like structure (#9) * moving classes to a more component like structure - add Wikipedia and YouTube namespace and moved applicable classes. this way the code is clustered by implementation making it easier to copy paste it to your own application. * rename rag to blog - rename add to blog to clarify the implementation --- src/{Chat/Rag.php => Blog/Chat/Blog.php} | 4 ++-- src/{ => Blog}/Command/BlogEmbedCommand.php | 2 +- .../RagComponent.php => Blog/Twig/BlogComponent.php} | 8 ++++---- src/{ => Wikipedia}/Chat/Wikipedia.php | 2 +- src/{ => Wikipedia}/Twig/WikipediaComponent.php | 4 ++-- src/{ => YouTube}/Chat/YouTube.php | 2 +- src/{ => YouTube}/Twig/YouTubeComponent.php | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename src/{Chat/Rag.php => Blog/Chat/Blog.php} (97%) rename src/{ => Blog}/Command/BlogEmbedCommand.php (97%) rename src/{Twig/RagComponent.php => Blog/Twig/BlogComponent.php} (87%) rename src/{ => Wikipedia}/Chat/Wikipedia.php (98%) rename src/{ => Wikipedia}/Twig/WikipediaComponent.php (92%) rename src/{ => YouTube}/Chat/YouTube.php (98%) rename src/{ => YouTube}/Twig/YouTubeComponent.php (96%) diff --git a/src/Chat/Rag.php b/src/Blog/Chat/Blog.php similarity index 97% rename from src/Chat/Rag.php rename to src/Blog/Chat/Blog.php index 22528ed3d..44b8c6902 100644 --- a/src/Chat/Rag.php +++ b/src/Blog/Chat/Blog.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Chat; +namespace App\Blog\Chat; use PhpLlm\LlmChain\ChainInterface; use PhpLlm\LlmChain\Model\Message\Message; @@ -11,7 +11,7 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\RequestStack; -final class Rag +final class Blog { private const SESSION_KEY = 'rag-chat'; diff --git a/src/Command/BlogEmbedCommand.php b/src/Blog/Command/BlogEmbedCommand.php similarity index 97% rename from src/Command/BlogEmbedCommand.php rename to src/Blog/Command/BlogEmbedCommand.php index 27affd902..81ec7aa23 100644 --- a/src/Command/BlogEmbedCommand.php +++ b/src/Blog/Command/BlogEmbedCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Command; +namespace App\Blog\Command; use App\Blog\Embedder; use Symfony\Component\Console\Attribute\AsCommand; diff --git a/src/Twig/RagComponent.php b/src/Blog/Twig/BlogComponent.php similarity index 87% rename from src/Twig/RagComponent.php rename to src/Blog/Twig/BlogComponent.php index b64559b1d..ae92d6c6e 100644 --- a/src/Twig/RagComponent.php +++ b/src/Blog/Twig/BlogComponent.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Twig; +namespace App\Blog\Twig; -use App\Chat\Rag; +use App\Blog\Chat\Blog; use PhpLlm\LlmChain\Model\Message\MessageBag; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveAction; @@ -12,12 +12,12 @@ use Symfony\UX\LiveComponent\DefaultActionTrait; #[AsLiveComponent('rag')] -final class RagComponent +final class BlogComponent { use DefaultActionTrait; public function __construct( - private readonly Rag $chat, + private readonly Blog $chat, ) { } diff --git a/src/Chat/Wikipedia.php b/src/Wikipedia/Chat/Wikipedia.php similarity index 98% rename from src/Chat/Wikipedia.php rename to src/Wikipedia/Chat/Wikipedia.php index ca5f4d117..10734ef46 100644 --- a/src/Chat/Wikipedia.php +++ b/src/Wikipedia/Chat/Wikipedia.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Chat; +namespace App\Wikipedia\Chat; use PhpLlm\LlmChain\ChainInterface; use PhpLlm\LlmChain\Model\Message\Message; diff --git a/src/Twig/WikipediaComponent.php b/src/Wikipedia/Twig/WikipediaComponent.php similarity index 92% rename from src/Twig/WikipediaComponent.php rename to src/Wikipedia/Twig/WikipediaComponent.php index c7201badf..0c0489c58 100644 --- a/src/Twig/WikipediaComponent.php +++ b/src/Wikipedia/Twig/WikipediaComponent.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Twig; +namespace App\Wikipedia\Twig; -use App\Chat\Wikipedia; +use App\Wikipedia\Chat\Wikipedia; use PhpLlm\LlmChain\Model\Message\MessageBag; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveAction; diff --git a/src/Chat/YouTube.php b/src/YouTube/Chat/YouTube.php similarity index 98% rename from src/Chat/YouTube.php rename to src/YouTube/Chat/YouTube.php index ec731d3c6..ea69f4e9d 100644 --- a/src/Chat/YouTube.php +++ b/src/YouTube/Chat/YouTube.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Chat; +namespace App\YouTube\Chat; use App\YouTube\TranscriptFetcher; use PhpLlm\LlmChain\ChainInterface; diff --git a/src/Twig/YouTubeComponent.php b/src/YouTube/Twig/YouTubeComponent.php similarity index 96% rename from src/Twig/YouTubeComponent.php rename to src/YouTube/Twig/YouTubeComponent.php index c246bcfb0..dd09894fa 100644 --- a/src/Twig/YouTubeComponent.php +++ b/src/YouTube/Twig/YouTubeComponent.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Twig; +namespace App\YouTube\Twig; -use App\Chat\YouTube; +use App\YouTube\Chat\YouTube; use PhpLlm\LlmChain\Model\Message\MessageBag; use Psr\Log\LoggerInterface; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; From 7cb6f6df9ee60674adb9f414b268d24ae770e991 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Sat, 14 Dec 2024 14:39:16 +0100 Subject: [PATCH 13/31] fix: typed animation only on xhr responses (#10) --- templates/components/_message.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/components/_message.html.twig b/templates/components/_message.html.twig index daa222437..4583ade90 100644 --- a/templates/components/_message.html.twig +++ b/templates/components/_message.html.twig @@ -17,7 +17,7 @@ {% else %}
- {% if latest %} + {% if latest and app.request.xmlHttpRequest %} Date: Fri, 20 Dec 2024 23:52:21 +0100 Subject: [PATCH 14/31] refactor: follow up on example structure to have them cleaner and more separated (#11) --- README.md | 4 +- assets/app.js | 3 + assets/icons/entypo/chat.svg | 1 - assets/icons/mdi/symfony.svg | 1 + assets/icons/mingcute/ai-fill.svg | 1 - assets/styles/app.css | 106 +----------------- assets/styles/blog.css | 61 ++++++++++ assets/styles/wikipedia.css | 31 +++++ assets/styles/youtube.css | 49 ++++++++ config/packages/llm_chain.yaml | 2 +- config/routes.yaml | 13 ++- demo.png | Bin 54356 -> 66632 bytes src/Blog/{Chat/Blog.php => Chat.php} | 8 +- ...{BlogEmbedCommand.php => EmbedCommand.php} | 2 +- .../Command/QueryCommand.php} | 32 ++---- src/Blog/Embedder.php | 2 +- src/Blog/{Loader.php => FeedLoader.php} | 2 +- .../BlogComponent.php => TwigComponent.php} | 9 +- .../{Chat/Wikipedia.php => Chat.php} | 4 +- ...kipediaComponent.php => TwigComponent.php} | 7 +- src/YouTube/{Chat/YouTube.php => Chat.php} | 5 +- ...YouTubeComponent.php => TwigComponent.php} | 7 +- templates/{components => }/_message.html.twig | 2 +- templates/base.html.twig | 4 +- .../{chat/rag.html.twig => chat.html.twig} | 4 +- templates/chat/wikipedia.html.twig | 9 -- templates/chat/youtube.html.twig | 9 -- .../{rag.html.twig => blog.html.twig} | 14 +-- templates/components/wikipedia.html.twig | 8 +- templates/components/youtube.html.twig | 12 +- templates/index.html.twig | 18 +-- tests/Blog/LoaderTest.php | 6 +- tests/SmokeTest.php | 34 +++--- 33 files changed, 239 insertions(+), 231 deletions(-) delete mode 100644 assets/icons/entypo/chat.svg create mode 100644 assets/icons/mdi/symfony.svg delete mode 100644 assets/icons/mingcute/ai-fill.svg create mode 100644 assets/styles/blog.css create mode 100644 assets/styles/wikipedia.css create mode 100644 assets/styles/youtube.css rename src/Blog/{Chat/Blog.php => Chat.php} (93%) rename src/Blog/Command/{BlogEmbedCommand.php => EmbedCommand.php} (95%) rename src/{Command/ChromaTestCommand.php => Blog/Command/QueryCommand.php} (69%) rename src/Blog/{Loader.php => FeedLoader.php} (98%) rename src/Blog/{Twig/BlogComponent.php => TwigComponent.php} (84%) rename src/Wikipedia/{Chat/Wikipedia.php => Chat.php} (96%) rename src/Wikipedia/{Twig/WikipediaComponent.php => TwigComponent.php} (84%) rename src/YouTube/{Chat/YouTube.php => Chat.php} (96%) rename src/YouTube/{Twig/YouTubeComponent.php => TwigComponent.php} (92%) rename templates/{components => }/_message.html.twig (94%) rename templates/{chat/rag.html.twig => chat.html.twig} (56%) delete mode 100644 templates/chat/wikipedia.html.twig delete mode 100644 templates/chat/youtube.html.twig rename templates/components/{rag.html.twig => blog.html.twig} (70%) diff --git a/README.md b/README.md index 68377bd8c..521351720 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ docker compose exec app bin/console app:blog:embed -vv Now you should be able to run the test command and get some results: ```shell -docker compose exec app bin/console app:chroma:test +docker compose exec app bin/console app:blog:query ``` **Don't forget to set up the project in your favorite IDE or editor.** @@ -89,4 +89,4 @@ docker compose exec app bin/console app:chroma:test * The chatbot application is a simple and small Symfony 7.2 application. * The UI is coupled to a [Twig LiveComponent](https://symfony.com/bundles/ux-live-component/current/index.html), that integrates different `Chat` implementations on top of the user's session. * You can reset the chat context by hitting the `Reset` button in the top right corner. -* You find three different usage scenarios in the upper navbar. \ No newline at end of file +* You find three different usage scenarios in the upper navbar. diff --git a/assets/app.js b/assets/app.js index dda9570a0..82c28fd28 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,3 +1,6 @@ import './bootstrap.js'; import 'bootstrap/dist/css/bootstrap.min.css'; import './styles/app.css'; +import './styles/blog.css'; +import './styles/youtube.css'; +import './styles/wikipedia.css'; diff --git a/assets/icons/entypo/chat.svg b/assets/icons/entypo/chat.svg deleted file mode 100644 index b1f6939e2..000000000 --- a/assets/icons/entypo/chat.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/icons/mdi/symfony.svg b/assets/icons/mdi/symfony.svg new file mode 100644 index 000000000..5ed86808f --- /dev/null +++ b/assets/icons/mdi/symfony.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/mingcute/ai-fill.svg b/assets/icons/mingcute/ai-fill.svg deleted file mode 100644 index 03ddb45f3..000000000 --- a/assets/icons/mingcute/ai-fill.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/styles/app.css b/assets/styles/app.css index a546e8d5e..f6151b1d8 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -1,16 +1,5 @@ body { min-height: 100vh; - &.rag, .rag .card-img-top { - background: rgb(220,139,110); - background: linear-gradient(0deg, rgba(220,139,110,1) 0%, rgba(244,233,115,1) 100%); - } - &.youtube, .youtube .card-img-top { - background: rgb(34,34,34); - background: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(71, 71, 71) 100%); - } - &.wikipedia, .wikipedia .card-img-top { - background: url('/wiki.png') no-repeat right 50px bottom 50px fixed, linear-gradient(0deg, rgb(246, 246, 246) 0%, rgb(197, 197, 197) 100%); - } footer, footer a { color: #6c757d; @@ -20,10 +9,6 @@ body { .index { .card-img-top { text-align: center; - - .youtube & { - color: #ff0000; - } } } @@ -44,55 +29,19 @@ body { .card-body { height: 700px; - .wikipedia & { - background-image: linear-gradient(135deg, #f2f2f2 16.67%, #ebebeb 16.67%, #ebebeb 50%, #f2f2f2 50%, #f2f2f2 66.67%, #ebebeb 66.67%, #ebebeb 100%); - background-size: 21.21px 21.21px; - } - .user-message { border-radius: 10px 10px 0 10px; color: #292929; - - .rag & { - background: #f4e973; - } - - .youtube & { - background: #3e2926; - color: #fafafa; - } - - .wikipedia & { - background: #ffffff; - } } .bot-message { border-radius: 10px 10px 10px 0; + color: #292929; - .rag & { - background: #dc8b6e; - - a { - color: #f4e973; - } - } - - .youtube & { - background: #df3535; - - a { - color: #3e2926; - } - } - - .wikipedia & { - background: #ffffff; - color: #292929 !important; + &.loading { + color: rgba(41, 41, 41, 0.5); } - color: #fff; - p { margin-bottom: 0; } @@ -102,31 +51,6 @@ body { width: 50px; height: 50px; border: 2px solid white; - - .rag &.bot { - outline: 1px solid #ffdacc; - background: #ffdacc; - } - - .rag &.user { - outline: 1px solid #fffad1; - background: #fffad1; - } - - .youtube &.bot { - outline: 1px solid #ffcccc; - background: #ffcccc; - } - - .youtube &.user { - outline: 1px solid #9e8282; - background: #9e8282; - } - - .wikipedia &.bot, .wikipedia &.user { - outline: 1px solid #eaeaea; - background: #eaeaea; - } } } @@ -138,28 +62,4 @@ body { box-shadow: none !important; } } - - #welcome { - h4 { - .rag & { - color: #f97b62; - } - - .youtube & { - color: #ff0000; - } - } - } - - #chat-reset, #chat-submit { - .rag &:hover { - background: #f97b62; - border-color: #f97b62; - } - - .youtube &:hover { - background: #ff0000; - border-color: #ff0000; - } - } } diff --git a/assets/styles/blog.css b/assets/styles/blog.css new file mode 100644 index 000000000..7750ecc9d --- /dev/null +++ b/assets/styles/blog.css @@ -0,0 +1,61 @@ +.blog { + body&, .card-img-top { + background: #2c5282; + background: linear-gradient(0deg, #2c5282 0%, #3c366b 100%); + } + + .card-img-top { + color: #ffffff; + } + + &.chat { + .user-message { + background: #d5054e; + color: #ffffff; + } + + .bot-message { + color: #ffffff; + background: #3182ce; + + &.loading { + color: rgba(255, 255, 255, 0.5); + } + + a { + color: #c8d8ef; + + &:hover { + color: #ffffff; + } + } + + code { + color: #ffb1ca; + } + } + + .avatar { + &.bot { + outline: 1px solid #b8d8fb; + background: #b8d8fb; + } + + &.user { + outline: 1px solid #ffb1ca; + background: #ffb1ca; + } + } + + #welcome h4 { + color: #2c5282; + } + + #chat-reset, #chat-submit { + &:hover { + background: #d5054e; + border-color: #d5054e; + } + } + } +} diff --git a/assets/styles/wikipedia.css b/assets/styles/wikipedia.css new file mode 100644 index 000000000..a1eec154f --- /dev/null +++ b/assets/styles/wikipedia.css @@ -0,0 +1,31 @@ +.wikipedia { + body&, .card-img-top { + background: url('/wiki.png') no-repeat right 50px bottom 50px fixed, linear-gradient(0deg, rgb(246, 246, 246) 0%, rgb(197, 197, 197) 100%); + } + + &.chat { + .card-body { + background-image: linear-gradient(135deg, #f2f2f2 16.67%, #ebebeb 16.67%, #ebebeb 50%, #f2f2f2 50%, #f2f2f2 66.67%, #ebebeb 66.67%, #ebebeb 100%); + background-size: 21.21px 21.21px; + } + + .user-message { + background: #ffffff; + } + + .bot-message { + background: #ffffff; + + a { + color: #3e2926; + } + } + + .avatar { + &.bot, &.user { + outline: 1px solid #eaeaea; + background: #eaeaea; + } + } + } +} diff --git a/assets/styles/youtube.css b/assets/styles/youtube.css new file mode 100644 index 000000000..85967ea1f --- /dev/null +++ b/assets/styles/youtube.css @@ -0,0 +1,49 @@ +.youtube { + body&, .card-img-top { + background: rgb(34,34,34); + background: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(71, 71, 71) 100%); + } + + .card-img-top { + color: #ff0000; + } + + &.chat { + .user-message { + background: #3e2926; + color: #fafafa; + } + + .bot-message { + color: #ffffff; + background: #df3535; + + &.loading { + color: rgba(255, 255, 255, 0.5); + } + } + + .avatar { + &.bot { + outline: 1px solid #ffcccc; + background: #ffcccc; + } + + &.user { + outline: 1px solid #9e8282; + background: #9e8282; + } + } + + #welcome h4 { + color: #ff0000; + } + + #chat-reset, #chat-submit { + &:hover { + background: #ff0000; + border-color: #ff0000; + } + } + } +} diff --git a/config/packages/llm_chain.yaml b/config/packages/llm_chain.yaml index 8b5bdc6e0..809b96bed 100644 --- a/config/packages/llm_chain.yaml +++ b/config/packages/llm_chain.yaml @@ -16,7 +16,7 @@ llm_chain: openai: api_key: '%env(OPENAI_API_KEY)%' chain: - rag: + blog: # platform: 'llm_chain.platform.anthropic' model: name: 'GPT' diff --git a/config/routes.yaml b/config/routes.yaml index 86dad1ee3..0da2b0651 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -4,20 +4,23 @@ index: defaults: template: 'index.html.twig' -rag: - path: '/rag' +blog: + path: '/blog' controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController' defaults: - template: 'chat/rag.html.twig' + template: 'chat.html.twig' + context: { chat: 'blog' } youtube: path: '/youtube' controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController' defaults: - template: 'chat/youtube.html.twig' + template: 'chat.html.twig' + context: { chat: 'youtube' } wikipedia: path: '/wikipedia' controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController' defaults: - template: 'chat/wikipedia.html.twig' + template: 'chat.html.twig' + context: { chat: 'wikipedia' } diff --git a/demo.png b/demo.png index 85fdbad35601bafc1a8480b0093e417fa3d1974b..703b2457094fdb7e5dd135f44c2f30299687427f 100644 GIT binary patch literal 66632 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�#OlMITbclW23M3UzBhrWrd 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
{% if loading %} -
+
{{ content }}
diff --git a/templates/base.html.twig b/templates/base.html.twig index 9af33983b..377065500 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -17,13 +17,13 @@