From b334b13e69a4d502fb9bcef6cfdbe780af65295d Mon Sep 17 00:00:00 2001 From: Agzamus Date: Sun, 11 Oct 2026 04:30:05 +0300 Subject: [PATCH] init --- .env.example | 8 + .gitignore | 5 + Makefile | 75 + README.md | 73 + app/bin/migrate | 97 + app/composer.json | 18 + app/composer.lock | 1721 +++++++++++++++++ .../2026_10_06_000000_create_notes_table.php | 22 + app/public/index.php | 355 ++++ app/public/router.php | 10 + app/src/Database.php | 45 + app/src/Http/RequestLog.php | 55 + app/src/Logger.php | 29 + app/src/Models/Note.php | 15 + compose.yaml | 53 + docker/php/Dockerfile | 15 + docker/php/php.ini | 4 + 17 files changed, 2600 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Makefile create mode 100755 app/bin/migrate create mode 100644 app/composer.json create mode 100644 app/composer.lock create mode 100644 app/database/migrations/2026_10_06_000000_create_notes_table.php create mode 100644 app/public/index.php create mode 100644 app/public/router.php create mode 100644 app/src/Database.php create mode 100644 app/src/Http/RequestLog.php create mode 100644 app/src/Logger.php create mode 100644 app/src/Models/Note.php create mode 100644 compose.yaml create mode 100644 docker/php/Dockerfile create mode 100644 docker/php/php.ini diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e3372a1 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +COMPOSE_PROJECT_NAME=webl +APP_PORT=8090 +DB_HOST_PORT=5432 +POSTGRES_DB=webl +POSTGRES_USER=webl +POSTGRES_PASSWORD=webl_dev_password +UID=1000 +GID=1000 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdb661c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +app/vendor/ +app/storage/logs/ +.DS_Store +*.bak diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f9a7259 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +.DEFAULT_GOAL := help + +ifneq (,$(wildcard .env)) +include .env +export +endif + +.PHONY: help init build up down ps logs clean setup composer migrate migrate-status migrate-rollback sh psql + +help: + @echo "Доступные команды:" + @echo " make init - создать .env из .env.example и подставить UID/GID текущего пользователя" + @echo " make build - собрать Docker-образы (docker compose build)" + @echo " make up - запустить контейнеры в фоне (docker compose up -d)" + @echo " make down - остановить контейнеры (docker compose down)" + @echo " make ps - показать состояние контейнеров (docker compose ps)" + @echo " make logs - показать логи контейнеров в реальном времени" + @echo " make clean - остановить контейнеры и УДАЛИТЬ данные PostgreSQL (docker compose down -v)" + @echo " make setup - быстрый старт: init + build + up + composer + migrate" + @echo " make composer - установить PHP-зависимости (composer install в контейнере)" + @echo " make migrate - применить миграции базы данных (php bin/migrate up)" + @echo " make migrate-status - показать статус миграций (php bin/migrate status)" + @echo " make migrate-rollback - откатить последнюю миграцию (php bin/migrate down)" + @echo " make sh - открыть shell в контейнере приложения" + @echo " make psql - подключиться к PostgreSQL (psql)" + +init: + @if [ -f .env ]; then \ + echo ".env уже существует"; \ + else \ + cp .env.example .env; \ + echo ".env создан из .env.example"; \ + fi + @sed -i.bak -e "s/^UID=.*/UID=$$(id -u)/" -e "s/^GID=.*/GID=$$(id -g)/" .env && rm -f .env.bak + @echo "UID/GID обновлены: UID=$$(id -u) GID=$$(id -g)" + +build: + docker compose build + +up: + docker compose up -d + +down: + docker compose down + +ps: + docker compose ps + +logs: + docker compose logs -f + +clean: + @echo "Удаление контейнеров и БД (volume pgdata)" + docker compose down -v + +setup: init build up composer migrate + @echo "Готово: http://localhost:$${APP_PORT:-8080}" + +composer: + docker compose exec -u www-data app composer install + +migrate: + docker compose exec app php bin/migrate up + +migrate-status: + docker compose exec app php bin/migrate status + +migrate-rollback: + docker compose exec app php bin/migrate down + +sh: + docker compose exec app sh + +psql: + docker compose exec db psql -U $(POSTGRES_USER) -d $(POSTGRES_DB) diff --git a/README.md b/README.md index e69de29..aa159c8 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,73 @@ +# WebLab + +Дз по лекциям + +## Быстрый старт + +```sh +make setup +``` + +Или: + +```bash +make init # создать .env (+UID/GID) +make build # сборка образа +make up # запустить контейнеры +make composer # установить зависимости +make migrate # применить миграции +``` + +Приложение: + +Порт задается `APP_PORT` в `.env` (по умолчанию 8090) + +## Команды + +| Команда | Описание | +|-------------------------|--------------------------------------------------------------| +| `make help` | Показать список всех команд | +| `make init` | Создать `.env` из `.env.example`, подставить UID/GID | +| `make build` | Собрать Docker-образы | +| `make up` | Запустить контейнеры в фоне | +| `make down` | Остановить контейнеры | +| `make ps` | Показать состояние контейнеров | +| `make logs` | Логи контейнеров в реальном времени | +| `make clean` | Остановить контейнеры и удалить данные PostgreSQL (`-v`) | +| `make setup` | Быстрый старт: init + build + up + composer + migrate | +| `make composer` | `composer install` внутри контейнера | +| `make migrate` | Применить миграции (`php bin/migrate up`) | +| `make migrate-status` | Статус миграций (`php bin/migrate status`) | +| `make migrate-rollback` | Откатить последнюю миграцию (`php bin/migrate down`) | +| `make sh` | Shell внутри контейнера приложения | +| `make psql` | Подключиться к PostgreSQL | + +## Логи + +Каждый HTTP-запрос логируется с `method`, `uri`, `status`, `body`. + +- Консоль: `docker compose logs app` +- Файлы: `app/storage/logs/app-*.log` (ротация 7 дней) + +## Структура + +``` +├── compose.yaml # сервисы db и app +├── .env.example +├── Makefile +├── docker/php/ # Dockerfile и php.ini +└── app/ # PHP-код (монтируется в контейнер) + ├── public/ # index.php, router.php + ├── src/ # исходники + ├── bin/ # мигратор + ├── database/ # миграции + └── storage/logs/ # логи +``` + +## Технологии + +- PHP 8.5 (`php:8.5-cli`, встроенный сервер) +- Eloquent — illuminate/database 12 (standalone) +- Monolog 3 +- PostgreSQL 18 (`postgres:18-alpine`) +- Docker Compose v2 diff --git a/app/bin/migrate b/app/bin/migrate new file mode 100755 index 0000000..4f20043 --- /dev/null +++ b/app/bin/migrate @@ -0,0 +1,97 @@ +#!/usr/bin/env php +getContainer(); + $manager = $capsule->getDatabaseManager(); + + Facade::setFacadeApplication($container); + + $container->instance('db', $manager); + + $container->bind('db.schema', fn ($app) => $app['db']->connection()->getSchemaBuilder()); + + $repository = new DatabaseMigrationRepository($manager, 'migrations'); + + if (!$repository->repositoryExists()) { + $repository->createRepository(); + } + + $migrator = new Migrator($repository, $manager, new Filesystem()); + + $paths = [__DIR__ . '/../database/migrations']; + + switch ($command) { + case 'up': + $migrator->run($paths); + echo "Миграции применены.\n"; + break; + case 'down': + $migrator->rollback($paths); + echo "Миграции откатены.\n"; + break; + case 'status': + $ran = $repository->getRan(); + $batches = $repository->getMigrationBatches(); + $files = $migrator->getMigrationFiles($paths); + + $rows = []; + foreach ($files as $file) { + $name = $migrator->getMigrationName($file); + if (in_array($name, $ran, true)) { + $batch = $batches[$name] ?? '?'; + $rows[] = [$name, sprintf('[%s] Ran', $batch)]; + } else { + $rows[] = [$name, 'Pending']; + } + } + + if ($rows === []) { + echo "Миграции не найдены.\n"; + break; + } + + $nameWidth = max(mb_strlen('Migration name'), ...array_map(static fn (array $r): int => mb_strlen($r[0]), $rows)); + $statusWidth = max(mb_strlen('Batch/Status'), ...array_map(static fn (array $r): int => mb_strlen($r[1]), $rows)); + $line = '+' . str_repeat('-', $nameWidth + 2) . '+' . str_repeat('-', $statusWidth + 2) . '+'; + + echo $line, "\n"; + echo '| ' . str_pad('Migration name', $nameWidth) . ' | ' . str_pad('Batch/Status', $statusWidth) . " |\n"; + echo $line, "\n"; + foreach ($rows as $row) { + echo '| ' . str_pad($row[0], $nameWidth) . ' | ' . str_pad($row[1], $statusWidth) . " |\n"; + } + echo $line, "\n"; + break; + case 'refresh': + $migrator->rollback($paths); + $migrator->run($paths); + echo "Миграции обновлены (rollback + run).\n"; + break; + } + + exit(0); +} catch (Throwable $e) { + fwrite(STDERR, 'Ошибка миграции: ' . $e->getMessage() . "\n"); + exit(1); +} diff --git a/app/composer.json b/app/composer.json new file mode 100644 index 0000000..f89ffd9 --- /dev/null +++ b/app/composer.json @@ -0,0 +1,18 @@ +{ + "name": "agzamus/webl", + "description": "Дз по лекции", + "type": "project", + "require": { + "php": ">=8.5", + "ext-pdo_pgsql": "*", + "illuminate/database": "^12.0", + "illuminate/events": "^12.0", + "illuminate/filesystem": "^12.0", + "monolog/monolog": "^3.0" + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + } +} diff --git a/app/composer.lock b/app/composer.lock new file mode 100644 index 0000000..47a007d --- /dev/null +++ b/app/composer.lock @@ -0,0 +1,1721 @@ +{ + "_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": "5f07511ecfd75b8e01602a14b8bcd97c", + "packages": [ + { + "name": "brick/math", + "version": "0.14.8", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/brick/math/0.14.8/brick-math-0.14.8.zip", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.14.8" + }, + "time": "2026-02-10T14:33:43+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.1", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/carbonphp/carbon-doctrine-types/3.2.1/carbonphp-carbon-doctrine-types-3.2.1.zip", + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.1" + }, + "time": "2026-09-06T14:11:38+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/doctrine/inflector/2.1.0/doctrine-inflector-2.1.0.zip", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "illuminate/bus", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/bus/v12.69.3/illuminate-bus-v12.69.3.zip", + "reference": "9e70f409c744e0ba6b301fa608bb5b3c8c38669a", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/pipeline": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^12.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-03-09T13:55:34+00:00" + }, + { + "name": "illuminate/collections", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/collections/v12.69.3/illuminate-collections-v12.69.3.zip", + "reference": "83313b009c4afb6f02dbc090bdb67809756eefa2", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" + }, + "suggest": { + "illuminate/http": "Required to convert collections to API resources (^12.0).", + "symfony/var-dumper": "Required to use the dump method (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php", + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-03-11T14:13:25+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/conditionable/v12.69.3/illuminate-conditionable-v12.69.3.zip", + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-05-13T15:08:45+00:00" + }, + { + "name": "illuminate/container", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/container/v12.69.3/illuminate-container-v12.69.3.zip", + "reference": "9abe9dba6b3f5220205ce143069c975a601e4294", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^12.0", + "illuminate/reflection": "^12.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "suggest": { + "illuminate/auth": "Required to use the Auth attribute", + "illuminate/cache": "Required to use the Cache attribute", + "illuminate/config": "Required to use the Config attribute", + "illuminate/database": "Required to use the DB attribute", + "illuminate/filesystem": "Required to use the Storage attribute", + "illuminate/log": "Required to use the Log or Context attributes" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-03-16T14:05:01+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/contracts/v12.69.3/illuminate-contracts-v12.69.3.zip", + "reference": "c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5", + "shasum": "" + }, + "require": { + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-06-09T13:20:54+00:00" + }, + { + "name": "illuminate/database", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/database/v12.69.3/illuminate-database-v12.69.3.zip", + "reference": "81bc26c9d40e8e5de3b5268d1465166db1ae7209", + "shasum": "" + }, + "require": { + "brick/math": "^0.11|^0.12|^0.13|^0.14", + "ext-pdo": "*", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "laravel/serializable-closure": "^1.3|^2.0", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.34", + "symfony/polyfill-php85": "^1.34" + }, + "suggest": { + "ext-filter": "Required to use the Postgres database driver.", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.24).", + "illuminate/console": "Required to use the database commands (^12.0).", + "illuminate/events": "Required to use the observers with Eloquent (^12.0).", + "illuminate/filesystem": "Required to use the migrations (^12.0).", + "illuminate/http": "Required to convert Eloquent models to API resources (^12.0).", + "illuminate/pagination": "Required to paginate the result set (^12.0).", + "symfony/finder": "Required to use Eloquent model factories (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Database\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Database package.", + "homepage": "https://laravel.com", + "keywords": [ + "database", + "laravel", + "orm", + "sql" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-09-17T21:16:04+00:00" + }, + { + "name": "illuminate/events", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/events/v12.69.3/illuminate-events-v12.69.3.zip", + "reference": "ff22aa6017bdbd90ace660f50638ed9c580ba555", + "shasum": "" + }, + "require": { + "illuminate/bus": "^12.0", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-03-05T16:20:14+00:00" + }, + { + "name": "illuminate/filesystem", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/filesystem/v12.69.3/illuminate-filesystem-v12.69.3.zip", + "reference": "4e2bff4d2ac439db7a2903b8a99bc8dc5c8d5de4", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2", + "symfony/finder": "^7.2.0" + }, + "suggest": { + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-hash": "Required to use the Filesystem class.", + "illuminate/http": "Required for handling uploaded files (^12.0).", + "league/flysystem": "Required to use the Flysystem local driver (^3.25.1).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/mime": "Required to enable support for guessing extensions (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Filesystem\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Filesystem package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-09-28T13:55:57+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/macroable/v12.69.3/illuminate-macroable-v12.69.3.zip", + "reference": "e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-03-30T19:05:19+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/pipeline/v12.69.3/illuminate-pipeline-v12.69.3.zip", + "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" + }, + "suggest": { + "illuminate/database": "Required to use database transactions (^12.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-08-20T13:36:50+00:00" + }, + { + "name": "illuminate/reflection", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/reflection/v12.69.3/illuminate-reflection-v12.69.3.zip", + "reference": "348cf5da9de89b596d7723be6425fb048e2bf4bb", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Reflection package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-02-25T15:25:18+00:00" + }, + { + "name": "illuminate/support", + "version": "v12.69.3", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/illuminate/support/v12.69.3/illuminate-support-v12.69.3.zip", + "reference": "0ae65f419c69a3bb5f8100b9c5286354742e7ef8", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^12.0", + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/reflection": "^12.0", + "nesbot/carbon": "^3.8.4", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php85": "^1.33", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "replace": { + "spatie/once": "*" + }, + "suggest": { + "illuminate/filesystem": "Required to use the Composer class (^12.0).", + "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).", + "league/uri": "Required to use the Uri class (^7.5.1).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the Composer class (^7.2).", + "symfony/uid": "Required to use Str::ulid() (^7.2).", + "symfony/var-dumper": "Required to use the dd function (^7.2).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php", + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-09-28T00:20:42+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.1.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/laravel/serializable-closure/v2.1.0/laravel-serializable-closure-v2.1.0.zip", + "reference": "2d5869a838bbcf37e0d8b0568fc41914e81374b5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2026-09-22T14:32:34+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.12.1", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/monolog/monolog/3.12.1/monolog-monolog-3.12.1.zip", + "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "psr/clock": "^1.0", + "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", + "psr/clock": "Required to pass a clock to the Logger and control the timestamp of log records", + "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" + } + }, + "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.12.1" + }, + "time": "2026-09-29T19:21:12+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.14.2", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/nesbot/carbon/3.14.2/nesbot-carbon-3.14.2.zip", + "reference": "94cfc34887f07ab5c0efa07914c8f527544e3224", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^3.0@dev", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbonphp.github.io/carbon/", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "time": "2026-10-02T07:26:52+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/clock/1.0.0/psr-clock-1.0.0.zip", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "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", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/container/2.0.2/psr-container-2.0.2.zip", + "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/" + } + }, + "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/log", + "version": "3.0.2", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/log/3.0.2/psr-log-3.0.2.zip", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "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": "psr/simple-cache", + "version": "3.0.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/simple-cache/3.0.0/psr-simple-cache-3.0.0.zip", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "symfony/clock", + "version": "v8.1.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/clock/v8.1.0/symfony-clock-v8.1.0.zip", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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/v8.1.0" + }, + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.20", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/finder/v7.4.20/symfony-finder-v7.4.20.zip", + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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.4.20" + }, + "time": "2026-09-26T06:31:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.43.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/polyfill-mbstring/v1.43.0/symfony-polyfill-mbstring-v1.43.0.zip", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" + }, + "time": "2026-09-24T15:19:04+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.43.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/polyfill-php83/v1.43.0/symfony-polyfill-php83-v1.43.0.zip", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.43.0" + }, + "time": "2026-09-25T15:50:05+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.43.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/polyfill-php84/v1.43.0/symfony-polyfill-php84-v1.43.0.zip", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" + }, + "time": "2026-09-23T22:01:09+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.43.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/polyfill-php85/v1.43.0/symfony-polyfill-php85-v1.43.0.zip", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0" + }, + "time": "2026-09-25T15:46:47+00:00" + }, + { + "name": "symfony/translation", + "version": "v8.1.5", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/translation/v8.1.5/symfony-translation-v8.1.5.zip", + "reference": "d9e1caba0d6b6f9a26710af8a2f88d37f001215a", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/http-client-contracts": "<2.5", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v8.1.5" + }, + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.1", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/translation-contracts/v3.7.1/symfony-translation-contracts-v3.7.1.zip", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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.7.1" + }, + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.1.1", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/voku/portable-ascii/2.1.1/voku-portable-ascii-2.1.1.zip", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" + }, + "time": "2026-04-26T05:33:54+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.5", + "ext-pdo_pgsql": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/app/database/migrations/2026_10_06_000000_create_notes_table.php b/app/database/migrations/2026_10_06_000000_create_notes_table.php new file mode 100644 index 0000000..543d159 --- /dev/null +++ b/app/database/migrations/2026_10_06_000000_create_notes_table.php @@ -0,0 +1,22 @@ +bigIncrements('id'); + $table->string('title'); + $table->text('content'); + $table->timestamp('created_at')->useCurrent(); + }); + } + + public function down(): void { + Schema::dropIfExists('notes'); + } +}; diff --git a/app/public/index.php b/app/public/index.php new file mode 100644 index 0000000..ec7b210 --- /dev/null +++ b/app/public/index.php @@ -0,0 +1,355 @@ +error('database.boot_failed', ['error' => $e->getMessage()]); + http_response_code(500); + exit('БД недоступна. Увы'); +} + +// Шорткатик экранирования +function h(string $v): string { + return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); +} + +// POST +$validationError = null; + +if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') { + $title = trim((string) ($_POST['title'] ?? '')); + $content = trim((string) ($_POST['content'] ?? '')); + + if ($title === '' || $content === '') { + http_response_code(422); + $validationError = 'Заполните все поля'; + } else { + try { + \App\Models\Note::create(['title' => $title, 'content' => $content]); + } catch (\Throwable $e) { + $logger->error('note.create_failed', ['error' => $e->getMessage()]); + http_response_code(302); + header('Location: /?err=1'); + exit; + } + http_response_code(302); + header('Location: /'); + exit; + } +} + +// ------------------------------------------------------------------------------------------ // + +$notes = \App\Models\Note::query()->orderByDesc('id')->get(); + +$pgError = null; +$pgVersion = null; +try { + $row = \App\Database::connection()->selectOne('SELECT version()'); + $pgVersion = (string) ($row->version ?? 'unknown'); +} catch (\Throwable $e) { + $logger->error('database.version_failed', ['error' => $e->getMessage()]); + $pgError = $e->getMessage(); +} + +$showErrBanner = ($_GET['err'] ?? '') === '1'; +?> + + + + + + + +webl — заметки + + + +
+
+
WebLab · заметки
+ +
+ +
+ +
+ + + + + + +
+ + +
+
+
+

Новая заметка

+
+
+
+
+ + +
+
+ + +
+ +
+
+ +
+
+
+

Заметки

+
+ +
+ + isEmpty()): ?> +
+

Заметок нет

+
+ +
+ +
+
+ № id) ?> + created_at) ?> +
+

title) ?>

+

content) ?>

+
+ +
+ +
+
+
+ + diff --git a/app/public/router.php b/app/public/router.php new file mode 100644 index 0000000..a1fb3b1 --- /dev/null +++ b/app/public/router.php @@ -0,0 +1,10 @@ +addConnection([ + 'driver' => 'pgsql', + 'host' => getenv('DB_HOST') ?: 'db', + 'port' => getenv('DB_PORT') ?: '5432', + 'database' => getenv('DB_NAME') ?: 'webl', + 'username' => getenv('DB_USER') ?: 'webl', + 'password' => getenv('DB_PASSWORD') ?: '', + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + 'options' => [ + \PDO::ATTR_TIMEOUT => 3, + ], + ]); + + $capsule->setAsGlobal(); + $capsule->bootEloquent(); + + self::$capsule = $capsule; + + return self::$capsule; + } + + public static function connection(): \Illuminate\Database\ConnectionInterface { + return self::boot()->getConnection(); + } +} diff --git a/app/src/Http/RequestLog.php b/app/src/Http/RequestLog.php new file mode 100644 index 0000000..0a6369f --- /dev/null +++ b/app/src/Http/RequestLog.php @@ -0,0 +1,55 @@ +info('http.request', [ + 'method' => $_SERVER['REQUEST_METHOD'] ?? '', + 'uri' => $_SERVER['REQUEST_URI'] ?? '', + 'status' => $status, + 'duration_ms' => round($durationMs, 2), + 'body' => $body, + ]); + + $err = error_get_last(); + if ($err !== null && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) { + $logger->error('http.fatal', [ + 'error' => $err['message'], + 'file' => $err['file'], + 'line' => $err['line'], + 'body' => $body, + ]); + } + } catch (\Throwable $e) { + error_log($e->getMessage()); + } + + echo $body; + }); + } +} diff --git a/app/src/Logger.php b/app/src/Logger.php new file mode 100644 index 0000000..4d7ecea --- /dev/null +++ b/app/src/Logger.php @@ -0,0 +1,29 @@ +pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Level::Debug)); + $logger->pushHandler(new \Monolog\Handler\RotatingFileHandler($logDir . '/app.log', 7, \Monolog\Level::Debug)); + + self::$logger = $logger; + + return self::$logger; + } +} diff --git a/app/src/Models/Note.php b/app/src/Models/Note.php new file mode 100644 index 0000000..eb8d02f --- /dev/null +++ b/app/src/Models/Note.php @@ -0,0 +1,15 @@ +