Open source · Сентябрь 2026. Разбор опубликованного кода на коммите 779d5e59. Ссылки закреплены на этой версии; ограничения описаны для неё.
Небольшой домен с понятной границей хранения
Я написал Shortify как PHP-библиотеку для двух операций: получить короткий код исходного URL и найти исходный URL по коду. Это удобная задача для проверки архитектуры на небольшом масштабе: в ней есть валидация, генерация значения, хранение и обработка отсутствующей записи. Все эти обязанности можно увидеть в нескольких классах и обсудить по конкретному коду.
Моя роль — автор модели URL, сервиса сокращения, фасада, интерфейса репозитория и тестов. Пакет требует PHP ^8.3; это подтверждается composer.json. Сторонних runtime-библиотек в разделе require нет. Здесь DDD выражается в названных операциях и явных зависимостях.
- ShortifyФасад shorten / expand
- CoreUrl, Shortener и CodeGenerator
- RepositoryКонтракт хранения и выбранный адаптер
В комплекте есть InMemoryRepository для локального сценария. Постоянное хранилище и HTTP-перенаправление подключает приложение.
Сценарий: сократить и раскрыть одну ссылку
Для знакомства с библиотекой не нужна база данных. InMemoryRepository из исходников хранит объекты в массиве текущего процесса. Фасад получает этот репозиторий, создаёт URL с коротким кодом и возвращает исходный адрес при последующем вызове expand. Ниже полный пример после установки пакета.
<?php
require __DIR__ . '/vendor/autoload.php';
use Tenqz\Shortify\Shortify;
use Tenqz\Shortify\Infrastructure\Repository\InMemoryRepository;
$shortify = new Shortify(new InMemoryRepository());
$original = 'https://example.com/articles/php-boundaries';
$url = $shortify->shorten($original);
$code = $url->getShortCode();
$expanded = $shortify->expand($code);
echo $code . PHP_EOL;
echo $expanded . PHP_EOL;
if ($expanded !== $original) {
throw new RuntimeException('Round trip failed');
}
Первая строка результата — сгенерированный код, который не следует заранее фиксировать в примере. Вторая должна совпасть с исходным URL; эта проверка записана прямо в коде. После завершения процесса соответствие исчезнет. Публичного короткого адреса пока нет: домен, маршрут и HTTP-ответ перенаправления создаёт веб-приложение.
Решение 1: проверять URL при создании объекта
Url проверяет формат через FILTER_VALIDATE_URL, разрешает только http и https и требует host. Неудачная проверка даёт InvalidUrlException до сохранения. Это единая точка проверки входа для сценария сокращения. Объект изменяемый: исходный адрес задаётся в конструкторе, а короткий код назначается через setShortCode.
Решение 2: зависеть от контракта репозитория
Shortener получает UrlRepositoryInterface с методами save, findByCode и exists. Сервис не содержит SQL и не выбирает базу. Адаптер приложения может сохранять те же объекты в подходящем хранилище, сохраняя публичные операции библиотеки. Интерфейс размещён в namespace Infrastructure; сервис Core обращается к этому контракту, а конкретный адаптер передаётся через конструктор.
Решение 3: отделить сценарий от точки входа
Фасад Shortify собирает Shortener и генератор кода. Внутри сервиса последовательность видна непосредственно: создать Url, сгенерировать код, назначить его и сохранить. Обратная операция ищет запись и бросает UrlNotFoundException, если её нет. Приложение само преобразует эту ошибку в HTTP 404 или другое поведение своего интерфейса.
Что реализовано и как это проверяется
Результат — отдельный Composer-пакет с коротким входным API и заменяемым хранилищем. В интеграционном тесте есть сценарий shorten → expand с in-memory репозиторием, ошибочный URL и отсутствующий код. Unit-тест сервиса проверяет взаимодействие с репозиторием и генератором. Реализация Shortener позволяет сопоставить эти ожидания с последовательностью действий.
Проверяемый контракт результата — сохранённый код раскрывается в исходный URL, неверный адрес отклоняется, отсутствующий код даёт отдельное исключение. Интеграционная проверка охватывает взаимодействие классов внутри одного процесса. Конкурентная запись в постоянное хранилище требует отдельной проверки адаптера.
Что потребуется для публичного сервиса
Генератор по умолчанию берёт шесть символов из MD5-строки, построенной с URL, временем и случайными байтами. Хотя исходный алфавит содержит буквы и цифры, итоговая строка шестнадцатеричная. Код не является токеном доступа или гарантированно уникальным идентификатором.
В Shortener нет проверки коллизии и повторной генерации; exists в этом сценарии не вызывается. InMemoryRepository при совпадении ключа заменяет запись. Для постоянного сервиса нужен уникальный индекс и отдельная политика обработки конфликта с ограниченным числом попыток. Проверка «сначала exists, потом save» сама по себе не защищает от параллельных запросов.
Также вне библиотеки остаются срок жизни ссылок, блокировка нежелательных адресов, аналитика и управление доступом. Валидация синтаксиса URL не проверяет безопасность содержимого по этому адресу.
Как попробовать
composer require tenqz/shortify
На PHP 8.3 или новее сохраните пример рядом с vendor и запустите его. Затем изучите контракт репозитория и адаптер в памяти. Полный проект доступен в tenqz/shortify; начинать расширение стоит с требований к хранению и коллизиям.