refactor!: remove legacy architecture and unused infrastructure

BREAKING CHANGE: Remove hexagonal architecture scaffolding, CI/CD workflows,
test infrastructure, and legacy configuration files following Laravel migration.

- Remove hexagonal architecture structure (Application, Domain, Infrastructure, Presentation layers)
- Remove CI/CD workflows and release automation
- Remove legacy web server configurations (.htaccess, nginx.conf, Caddyfile)
- Remove test infrastructure and PHPUnit configuration
- Remove development utilities and cleanup scripts
- Remove extensive documentation scaffolding
- Clean up unused configuration files
This commit is contained in:
Yury Pikhtarev 2025-06-23 16:28:57 +04:00
commit ae418d4b6a
No known key found for this signature in database
82 changed files with 0 additions and 5057 deletions

View file

@ -1,7 +0,0 @@
9766c534bddad8e82e6d19f9bad5cf70b9887f9a
92ce77ec0ec703c08a659419087a373f76e711f7
2d53efc945c7747be1755d0b66557a86bdc12cbd
602137b65129b817811b80975a369ebde3270c6d
4eb26ae37e1f4c82a45961517ffeb54c20200408
e59adce848a9e10ee5775254045cbbd915236b8b
9e0a64108d62236ab07b3f8d10e8c78269b8e1d1

View file

@ -1,7 +0,0 @@
---
name: Feature / Enhancement request
about: Suggest an idea for TorrentPier
title: "[Feature]"
labels: [Feature, Enhancement]
assignees: ''
---

View file

@ -1,80 +0,0 @@
name: Continuous Deployment
on:
push:
tags:
- "v*.*.*"
jobs:
generate-changelog:
name: Generate changelog
runs-on: ubuntu-22.04
outputs:
release_body: ${{ steps.git-cliff.outputs.content }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
id: git-cliff
with:
config: cliff.toml
args: -vv --latest --no-exec --github-repo ${{ github.repository }}
- name: Print the changelog
run: cat "${{ steps.git-cliff.outputs.changelog }}"
release:
name: Create release
needs: [ generate-changelog ]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set the release version
shell: bash
run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: Install Composer dependencies
run: composer install --no-dev --no-progress --prefer-dist --optimize-autoloader
- name: Cleanup
run: php _cleanup.php && rm _cleanup.php
- name: Create archive
id: create-zip
run: |
ZIP_NAME="torrentpier-v${{ env.RELEASE_VERSION }}.zip"
zip -r "$ZIP_NAME" . -x ".git/*"
echo "ZIP_NAME=$ZIP_NAME" >> $GITHUB_OUTPUT
- name: Publish to GitHub
if: ${{ !contains(github.ref, '-') }}
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.create-zip.outputs.ZIP_NAME }}
overwrite: true
tag: ${{ github.ref }}
release_name: "v${{ env.RELEASE_VERSION }}"
body: "${{ needs.generate-changelog.outputs.release_body }}"
- name: Publish to GitHub (pre-release)
if: ${{ contains(github.ref, '-') }}
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.create-zip.outputs.ZIP_NAME }}
overwrite: true
tag: ${{ github.ref }}
release_name: "v${{ env.RELEASE_VERSION }}"
body: "${{ needs.generate-changelog.outputs.release_body }}"
prerelease: true

View file

@ -1,74 +0,0 @@
name: Continuous Integration
on:
push:
branches:
- master
jobs:
nightly:
name: Nightly builds 📦
runs-on: ubuntu-22.04
steps:
- name: Checkout code 🗳
uses: actions/checkout@v4
- name: Setup PHP 🔩
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: Install Composer dependencies 🪚
run: composer install --no-dev --no-progress --prefer-dist --optimize-autoloader
- name: Get commit hash 🔗
id: get-commit-hash
run: |
COMMIT_HASH=$(git rev-parse --short HEAD)
echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_OUTPUT
- name: Cleanup
run: php _cleanup.php && rm _cleanup.php
- name: Create archive 🗞
id: create-zip
run: |
ZIP_NAME="torrentpier-${{ steps.get-commit-hash.outputs.COMMIT_HASH }}.zip"
zip -r "$ZIP_NAME" . -x ".git/*"
echo "ZIP_NAME=$ZIP_NAME" >> $GITHUB_OUTPUT
- name: Upload Archive 📤
uses: actions/upload-artifact@v4
with:
name: TorrentPier-master
path: ${{ steps.create-zip.outputs.ZIP_NAME }}
deploy:
name: 🎉 Deploy
runs-on: ubuntu-22.04
steps:
- name: 🚚 Get latest code
uses: actions/checkout@v4
- name: 🔩 Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: 🖇 Install Composer dependencies
run: composer install --no-dev --no-progress --prefer-dist --optimize-autoloader
- name: 📂 Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
server-dir: ${{ secrets.FTP_DIR }}
protocol: ${{ secrets.FTP_PROTOCOL }}
port: ${{ secrets.FTP_PORT }}
exclude: |
**/.git*
**/.git*/**
.env

View file

@ -1,57 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# PHPMD is a spin-off project of PHP Depend and
# aims to be a PHP equivalent of the well known Java tool PMD.
# What PHPMD does is: It takes a given PHP source code base
# and look for several potential problems within that source.
# These problems can be things like:
# Possible bugs
# Suboptimal code
# Overcomplicated expressions
# Unused parameters, methods, properties
# More details at https://phpmd.org/
name: PHPMD
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '40 0 * * 3'
permissions:
contents: read
jobs:
PHPMD:
name: Run PHPMD scanning
runs-on: ubuntu-latest
permissions:
contents: read # for checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@aa1fe473f9c687b6fb896056d771232c0bc41161
with:
coverage: none
tools: phpmd
- name: Run PHPMD
run: phpmd . sarif codesize --reportfile phpmd-results.sarif
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: phpmd-results.sarif
wait-for-processing: true

View file

@ -1,41 +0,0 @@
name: Changelog generation
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
changelog:
name: Changelog generation
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: master
token: ${{ secrets.REPO_TOKEN }}
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
id: git-cliff
with:
config: cliff.toml
args: v2.4.6-alpha.4.. --verbose
env:
OUTPUT: CHANGELOG.md
GITHUB_REPO: ${{ github.repository }}
- name: Print the changelog
run: cat "${{ steps.git-cliff.outputs.changelog }}"
- name: Commit changelog
run: |
git checkout master
git config --local user.name 'belomaxorka'
git config --local user.email 'roman25052006.kelesh@gmail.com'
set +e
git add CHANGELOG.md
git commit -m "changelog: Update CHANGELOG.md 📖"
git push https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git master

View file

@ -1,18 +0,0 @@
## set default server charset
AddDefaultCharset UTF-8
## folder listing access control
Options All -Indexes
## sitemap and atom rewrite
RewriteEngine On
RewriteRule ^sitemap.xml$ sitemap/sitemap.xml [L]
RewriteRule ^/internal_data/atom/(.*) /atom$1 [L]
## deny access to git folder
RedirectMatch 404 /\\.git(/|$)
## deny access to system files
<FilesMatch "\.(.*sql|tpl|db|inc|log|md|env)|(config|common).php$">
Require all denied
</FilesMatch>

View file

@ -1,57 +0,0 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
if (!defined('BB_ROOT')) {
define('BB_ROOT', __DIR__ . DIRECTORY_SEPARATOR);
define('BB_PATH', BB_ROOT);
}
// Check CLI mode
if (PHP_SAPI != 'cli') {
exit;
}
if (!function_exists('removeFile')) {
// Get all constants
require_once BB_ROOT . 'library/defines.php';
// Include CLI functions
require INC_DIR . '/functions_cli.php';
}
$items = [
'.github',
'.cliffignore',
'.editorconfig',
'.gitignore',
'.styleci.yml',
'_release.php',
'CHANGELOG.md',
'CLAUDE.md',
'cliff.toml',
'CODE_OF_CONDUCT.md',
'CONTRIBUTING.md',
'crowdin.yml',
'HISTORY.md',
'phpunit.xml',
'README.md',
'SECURITY.md',
'tests',
'UPGRADE_GUIDE.md'
];
foreach ($items as $item) {
$path = BB_ROOT . $item;
if (is_file($path)) {
removeFile($path);
} elseif (is_dir($path)) {
removeDir($path);
}
}

View file

@ -1,130 +0,0 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
define('BB_ROOT', __DIR__ . DIRECTORY_SEPARATOR);
define('BB_PATH', BB_ROOT);
// Check CLI mode
if (PHP_SAPI != 'cli') {
die('Please run <code style="background:#222;color:#00e01f;padding:2px 6px;border-radius:3px;">php ' . basename(__FILE__) . '</code> in CLI mode');
}
// Get all constants
require_once BB_ROOT . 'library/defines.php';
// Include CLI functions
require INC_DIR . '/functions_cli.php';
// Welcoming message
out("--- Release creation tool ---\n", 'info');
$configFile = BB_PATH . '/library/config.php';
if (!is_file($configFile)) {
out('- Config file ' . basename($configFile) . ' not found', 'error');
exit;
}
if (!is_readable($configFile)) {
out('- Config file ' . basename($configFile) . ' is not readable', 'error');
exit;
}
if (!is_writable($configFile)) {
out('- Config file ' . basename($configFile) . ' is not writable', 'error');
exit;
}
// Ask for version
fwrite(STDOUT, 'Enter version number (e.g, v2.4.0): ');
$version = trim(fgets(STDIN));
if (empty($version)) {
out("- Version cannot be empty. Please enter a valid version number", 'error');
exit;
} else {
// Add 'v' prefix if missing
if (!str_starts_with($version, 'v')) {
$version = 'v' . $version;
}
out("- Using version: $version", 'info');
}
// Ask for version emoji
fwrite(STDOUT, 'Enter version emoji: ');
$versionEmoji = trim(fgets(STDIN));
if (!empty($versionEmoji)) {
out("- Using version emoji: $versionEmoji", 'info');
}
// Ask for release date or use today's date
fwrite(STDOUT, "Enter release date (e.g. 25-05-2025), leave empty to use today's date: ");
$date = trim(fgets(STDIN));
if (empty($date)) {
$date = date('d-m-Y');
out("- Using current date: $date", 'info');
} else {
// Validate date format (dd-mm-yyyy)
$dateObj = DateTime::createFromFormat('d-m-Y', $date);
if (!$dateObj || $dateObj->format('d-m-Y') !== $date) {
out("- Invalid date format. Expected format: DD-MM-YYYY", 'error');
exit;
}
out("- Using date: $date", 'info');
}
// Read config file content
$content = file_get_contents($configFile);
// Update version
$content = preg_replace(
"/\\\$bb_cfg\['tp_version'\]\s*=\s*'[^']*';/",
"\$bb_cfg['tp_version'] = '$version';",
$content
);
// Update release date
$content = preg_replace(
"/\\\$bb_cfg\['tp_release_date'\]\s*=\s*'[^']*';/",
"\$bb_cfg['tp_release_date'] = '$date';",
$content
);
// Save updated config
$bytesWritten = file_put_contents($configFile, $content);
if ($bytesWritten === false) {
out("- Failed to write to config file", 'error');
exit;
}
if ($bytesWritten === 0) {
out("- Config file was not updated (0 bytes written)", 'error');
exit;
}
out("\n- Config file has been updated!", 'success');
// Update CHANGELOG.md
runProcess('npx git-cliff v2.4.6-alpha.4.. --config cliff.toml --tag "' . $version . '" > CHANGELOG.md');
// Git add & commit
runProcess('git add -A && git commit -m "release: ' . escapeshellarg($version) . (!empty($versionEmoji) ? (' ' . $versionEmoji) : '') . '"');
// Git tag
runProcess("git tag -a \"$version\" -m \"Release $version\"");
runProcess("git tag -v \"$version\"");
// Git push
runProcess("git push origin master");
runProcess("git push origin $version");
out("\n- Release $version has been successfully prepared, committed and pushed!", 'success');

View file

@ -1,126 +0,0 @@
# git-cliff ~ TorrentPier configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[remote.github]
owner = "torrentpier"
repo = "torrentpier"
[changelog]
# template for the changelog header
header = """
[![TorrentPier](https://raw.githubusercontent.com/torrentpier/.github/refs/heads/main/versions/Cattle.png)](https://github.com/torrentpier)\n
# 📖 Change Log\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{%- macro nightly_url() -%}
https://nightly.link/{{ remote.github.owner }}/{{ remote.github.repo }}/workflows/ci/master/TorrentPier-master
{%- endmacro -%}
{% macro print_commit(commit) -%}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }} - \
([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
{% endmacro -%}
{% if version %}\
{% if previous.version %}\
## [{{ version }}]\
({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
## {{ version }} ({{ timestamp | date(format="%Y-%m-%d") }})
{% endif %}\
{% else %}\
## [nightly]({{ self::nightly_url() }})
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
{{ self::print_commit(commit=commit) }}
{%- endfor %}
{% for commit in commits %}
{%- if not commit.scope -%}
{{ self::print_commit(commit=commit) }}
{% endif -%}
{% endfor -%}
{% endfor -%}
{%- if github -%}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
## New Contributors ❤️
{% endif %}\
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
* @{{ contributor.username }} made their first contribution
{%- if contributor.pr_number %} in \
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor -%}
{%- endif %}
"""
# template for the changelog footer
footer = """
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
{ pattern = '<REPO>', replace = "https://github.com/torrentpier/torrentpier" }, # replace repository URL
]
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/pull/${2}))" },
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
# { pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^ignore|^release|^changelog", skip = true },
{ message = "^chore|^ci|^misc", group = "<!-- 7 -->⚙️ Miscellaneous" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = "^crowdin|^crodwin", group = "<!-- 10 -->🈳 New translations" }, # crowdin pulls supporting
{ message = "^Composer", group = "<!-- 11 -->📦 Dependencies" }, # dependabot pulls supporting
{ message = "^rem|^drop|^removed", group = "<!-- 12 -->🗑️ Removed" },
{ message = ".*", group = "<!-- 13 -->💼 Other" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers
filter_commits = false
# regex for matching git tags
tag_pattern = "v[0-9].*"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "newest"

View file

@ -1,53 +0,0 @@
# Application Configuration
System configuration files using PHP arrays for type safety and IDE support:
- **app.php**: Core application settings
- Site name, URL, timezone
- Debug mode, environment
- Feature flags and toggles
- **database.php**: Database connection settings
- Multiple connection definitions
- Read/write splitting configuration
- Connection pooling settings
- **cache.php**: Cache driver configurations
- Redis, Memcached, file-based settings
- TTL defaults per cache type
- Cache key prefixes
- **tracker.php**: BitTorrent tracker settings
- Announce intervals
- Peer limits
- Ratio requirements
- **environments/**: Environment-specific overrides
- Development, staging, production settings
- Local developer configurations
Example database configuration:
```php
<?php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'tp'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
],
],
];
```

View file

@ -1,26 +0,0 @@
<?php
return [
// Container configuration
'environment' => env('APP_ENV', 'development'),
'debug' => env('APP_DEBUG', false),
// Enable/disable features
'autowiring' => true,
'annotations' => false,
// Compilation settings for production
'compilation_dir' => __DIR__ . '/../internal_data/cache/container',
'proxies_dir' => __DIR__ . '/../internal_data/cache/proxies',
// Additional definition files to load
'definition_files' => [
// Add custom definition files here
// __DIR__ . '/services/custom.php',
],
// Container-specific settings
'container' => [
// Add any PHP-DI specific settings here
],
];

View file

@ -1,30 +0,0 @@
<?php
use function DI\factory;
use function DI\get;
use function DI\autowire;
return [
// Add custom service definitions here as they are implemented
// Examples (uncomment and modify when implementing):
// Logger service example
// 'logger' => factory(function () {
// $logger = new \Monolog\Logger('torrentpier');
// $logger->pushHandler(new \Monolog\Handler\StreamHandler(__DIR__ . '/../internal_data/logs/app.log'));
// return $logger;
// }),
// Configuration service example
// 'config' => factory(function () {
// return [
// 'app' => require __DIR__ . '/app.php',
// 'database' => require __DIR__ . '/database.php',
// 'cache' => require __DIR__ . '/cache.php',
// ];
// }),
// Interface to implementation binding example
// 'ServiceInterface' => autowire('ConcreteService'),
];

View file

@ -1,99 +0,0 @@
<?php
/**
* Example: How to use the DI Container in TorrentPier 3.0
*
* NOTE: These are examples for future implementation following the hexagonal architecture spec.
* Most services referenced here don't exist yet and will be implemented in phases.
*/
use TorrentPier\Infrastructure\DependencyInjection\Bootstrap;
use TorrentPier\Infrastructure\DependencyInjection\ContainerFactory;
// ===== PHASE 1: Foundation (CURRENT) =====
// 1. Bootstrap the container (typically done once in index.php or bootstrap file)
$rootPath = __DIR__ . '/../..';
$container = Bootstrap::init($rootPath);
// 2. Basic container usage (works now)
$containerInstance = app(); // Get container itself
$hasService = $container->has('some.service'); // Check if service exists
// ===== PHASE 2: Domain Modeling (FUTURE) =====
// 3. Repository interfaces (when implemented in Domain layer)
// $userRepository = app('TorrentPier\Domain\User\Repository\UserRepositoryInterface');
// $torrentRepository = app('TorrentPier\Domain\Tracker\Repository\TorrentRepositoryInterface');
// $forumRepository = app('TorrentPier\Domain\Forum\Repository\ForumRepositoryInterface');
// ===== PHASE 3: Application Services (FUTURE) =====
// 4. Command/Query handlers (when implemented)
// $registerUserHandler = app('TorrentPier\Application\User\Handler\RegisterUserHandler');
// $announceHandler = app('TorrentPier\Application\Tracker\Handler\ProcessAnnounceHandler');
// $createPostHandler = app('TorrentPier\Application\Forum\Handler\CreatePostHandler');
// 5. Making command instances with parameters
// $command = $container->make('TorrentPier\Application\User\Command\RegisterUserCommand', [
// 'username' => 'john_doe',
// 'email' => 'john@example.com',
// 'password' => 'secure_password'
// ]);
// ===== PHASE 4: Infrastructure (FUTURE) =====
// 6. Database and cache (when infrastructure is implemented)
// $database = app('database.connection.default');
// $cache = app('cache.factory')('forum'); // Get cache instance for 'forum' namespace
// ===== PHASE 5: Presentation (FUTURE) =====
// 7. Controllers (when implemented)
// $userController = app('TorrentPier\Presentation\Http\Controllers\Api\UserController');
// $trackerController = app('TorrentPier\Presentation\Http\Controllers\Web\TrackerController');
// ===== TESTING EXAMPLES =====
// 8. Testing with custom container (works now)
$testContainer = ContainerFactory::create([
'definitions' => [
'test.service' => \DI\factory(function () {
return new class {
public function test() { return 'test'; }
};
}),
],
'environment' => 'testing',
]);
// 9. Safe service resolution (works now)
try {
$service = app('optional.service');
} catch (RuntimeException $e) {
// Service not found, handle gracefully
$service = null;
}
// ===== LEGACY INTEGRATION (CURRENT) =====
// 10. Integration with legacy code
// In legacy files, after including common.php or similar:
if (!Bootstrap::getContainer()) {
Bootstrap::init(BB_ROOT ?? __DIR__ . '/../..');
}
// 11. Method injection (works now if service exists)
class ExampleService
{
public function processData(string $data)
{
// Container can inject dependencies when calling this method
return "Processed: $data";
}
}
$exampleService = new ExampleService();
$result = $container->call([$exampleService, 'processData'], [
'data' => 'test data'
]);

View file

@ -1,436 +0,0 @@
# Hexagonal Architecture Directory Structure Specification
## Overview
This document specifies the new hexagonal architecture directory structure for TorrentPier 3.0. The structure follows Domain-Driven Design (DDD) principles and implements a clean separation of concerns through hexagonal architecture (ports and adapters pattern).
## Directory Structure
```
/src/
├── Domain/ # Core business logic - no framework dependencies
│ ├── Forum/ # Forum bounded context
│ │ ├── Model/ # Aggregates and entities
│ │ ├── ValueObject/ # Value objects (PostId, ThreadTitle, etc.)
│ │ ├── Repository/ # Repository interfaces
│ │ └── Exception/ # Domain-specific exceptions
│ ├── Tracker/ # BitTorrent tracker bounded context
│ │ ├── Model/ # Torrent, Peer aggregates
│ │ ├── ValueObject/ # InfoHash, PeerId, etc.
│ │ ├── Repository/ # Repository interfaces
│ │ └── Exception/ # Tracker-specific exceptions
│ ├── User/ # User management bounded context
│ │ ├── Model/ # User aggregate
│ │ ├── ValueObject/ # UserId, Email, Username
│ │ ├── Repository/ # User repository interface
│ │ └── Exception/ # Authentication/authorization exceptions
│ └── Shared/ # Shared kernel - minimal shared concepts
│ ├── Model/ # Base classes (AggregateRoot, Entity)
│ ├── ValueObject/ # Common value objects (Id, DateTime)
│ └── Event/ # Domain events base classes
├── Application/ # Application services - orchestration layer
│ ├── Forum/
│ │ ├── Command/ # Commands (CreatePost, LockThread)
│ │ ├── Query/ # Queries (GetThreadList, SearchPosts)
│ │ └── Handler/ # Command and query handlers
│ ├── Tracker/
│ │ ├── Command/ # Commands (RegisterTorrent, ProcessAnnounce)
│ │ ├── Query/ # Queries (GetPeerList, GetTorrentStats)
│ │ └── Handler/ # Command and query handlers
│ └── User/
│ ├── Command/ # Commands (RegisterUser, ChangePassword)
│ ├── Query/ # Queries (GetUserProfile, SearchUsers)
│ └── Handler/ # Command and query handlers
├── Infrastructure/ # External concerns and implementations
│ ├── Persistence/ # Data persistence layer
│ │ ├── Database/ # Database adapter and connection management
│ │ ├── Migration/ # Database migrations
│ │ └── Repository/ # Repository implementations
│ ├── Cache/ # Caching implementations
│ │ ├── Redis/ # Redis adapter
│ │ ├── Memcached/ # Memcached adapter
│ │ └── File/ # File-based cache adapter
│ ├── Email/ # Email service implementations
│ │ ├── Template/ # Email templates
│ │ └── Transport/ # SMTP, API transports
│ └── FileStorage/ # File storage abstractions
│ ├── Local/ # Local filesystem storage
│ └── S3/ # AWS S3 storage adapter
└── Presentation/ # User interface layer
├── Http/ # Web interface
│ ├── Controllers/ # HTTP controllers
│ │ ├── Admin/ # Admin panel controllers
│ │ ├── Api/ # REST API controllers
│ │ └── Web/ # Web UI controllers
│ ├── Middleware/ # HTTP middleware (auth, CORS, etc.)
│ ├── Requests/ # Request DTOs and validation
│ └── Responses/ # Response transformers
└── Cli/ # Command line interface
└── Commands/ # Console commands
# Additional directories (outside /src/)
/config/ # Application configuration
├── app.php # Main application settings
├── database.php # Database connections
├── cache.php # Cache drivers configuration
├── tracker.php # BitTorrent tracker settings
└── environments/ # Environment-specific overrides
/tests/ # Test suites (Pest)
├── Unit/ # Unit tests (mirrors src/ structure)
├── Feature/ # Feature/Integration tests
├── Pest.php # Pest configuration
└── TestCase.php # Base test case
```
## Directory README.md Templates
### Domain Layer READMEs
#### `/src/Domain/README.md`
```markdown
# Domain Layer
This directory contains the core business logic of TorrentPier. Code here should:
- Have no dependencies on frameworks or infrastructure
- Represent pure business rules and domain models
- Be testable in isolation
- Use only PHP language features and domain concepts
## Bounded Contexts
- **Forum**: Discussion forums, posts, threads
- **Tracker**: BitTorrent tracking, peers, torrents
- **User**: User management, authentication, profiles
- **Shared**: Minimal shared concepts between contexts
```
#### `/src/Domain/Tracker/Model/README.md`
```markdown
# Tracker Domain Models
Contains aggregate roots and entities for the BitTorrent tracker:
- `Torrent`: Aggregate root for torrent management
- `Peer`: Entity representing a BitTorrent peer
- `TorrentStatistics`: Value object for torrent stats
Example:
```php
class Torrent extends AggregateRoot
{
public function announce(Peer $peer, AnnounceEvent $event): void
{
// Business logic for handling announces
}
}
```
#### `/src/Domain/Tracker/ValueObject/README.md`
```markdown
# Tracker Value Objects
Immutable objects representing domain concepts:
- `InfoHash`: 20-byte torrent identifier
- `PeerId`: Peer client identifier
- `Port`: Network port (1-65535)
- `BytesTransferred`: Upload/download bytes
Example:
```php
final class InfoHash
{
private string $hash;
public function __construct(string $hash)
{
$this->guardAgainstInvalidHash($hash);
$this->hash = $hash;
}
}
```
### Application Layer READMEs
#### `/src/Application/README.md`
```markdown
# Application Layer
Contains application services that orchestrate domain objects to fulfill use cases.
- Commands: Write operations that change state
- Queries: Read operations for data retrieval
- Handlers: Process commands and queries
This layer should:
- Coordinate domain objects
- Handle transactions
- Dispatch domain events
- Not contain business logic
```
#### `/src/Application/Tracker/Command/README.md`
```markdown
# Tracker Commands
Commands representing write operations:
- `RegisterTorrentCommand`: Register new torrent
- `UpdateTorrentCommand`: Modify torrent details
- `DeleteTorrentCommand`: Remove torrent from tracker
Example:
```php
final class RegisterTorrentCommand
{
public function __construct(
public readonly string $infoHash,
public readonly int $uploaderId,
public readonly string $name,
public readonly int $size
) {}
}
```
### Infrastructure Layer READMEs
#### `/src/Infrastructure/README.md`
```markdown
# Infrastructure Layer
Technical implementations and external service adapters:
- Database persistence
- Caching mechanisms
- Email services
- File storage
- Third-party integrations
Infrastructure depends on domain, not vice versa.
```
#### `/src/Infrastructure/Persistence/Repository/README.md`
```markdown
# Repository Implementations
Concrete implementations of domain repository interfaces:
- Uses database adapter for persistence
- Implements caching strategies
- Handles query optimization
- Supports multiple database backends
Example:
```php
class TorrentRepository implements TorrentRepositoryInterface
{
public function __construct(
private DatabaseAdapterInterface $db
) {}
public function findByInfoHash(InfoHash $infoHash): ?Torrent
{
// Database adapter implementation
$row = $this->db->select('torrents')
->where('info_hash', $infoHash->toString())
->first();
return $row ? $this->hydrateFromRow($row) : null;
}
}
```
### Presentation Layer READMEs
#### `/src/Presentation/README.md`
```markdown
# Presentation Layer
User interface implementations:
- HTTP controllers for web and API
- CLI commands for console operations
- Request/response handling
- Input validation
- Output formatting
This layer translates between external format and application format.
```
#### `/src/Presentation/Http/Controllers/Api/README.md`
```markdown
# API Controllers
RESTful API endpoints following OpenAPI specification:
- JSON request/response format
- Proper HTTP status codes
- HATEOAS where applicable
- Rate limiting aware
Example:
```php
class UserController
{
public function register(RegisterRequest $request): JsonResponse
{
$command = new RegisterUserCommand(
$request->getUsername(),
$request->getEmail(),
$request->getPassword()
);
$userId = $this->commandBus->handle($command);
return new JsonResponse([
'id' => $userId,
'username' => $request->getUsername()
], Response::HTTP_CREATED);
}
}
```
#### `/src/Presentation/Http/Controllers/Admin/README.md`
```markdown
# Admin Panel Controllers
Administrative interface controllers with enhanced security:
- Role-based access control (RBAC)
- Audit logging for all actions
- Additional authentication checks
- Administrative dashboards and reports
Example:
```php
class AdminUserController
{
public function index(Request $request): Response
{
$query = new GetUsersQuery(
page: $request->getPage(),
filters: $request->getFilters()
);
$users = $this->queryBus->handle($query);
return $this->render('admin/users/index', [
'users' => $users,
'filters' => $request->getFilters()
]);
}
}
```
#### `/config/README.md`
```markdown
# Application Configuration
System configuration files using PHP arrays for type safety and IDE support:
- **app.php**: Core application settings
- Site name, URL, timezone
- Debug mode, environment
- Feature flags and toggles
- **database.php**: Database connection settings
- Multiple connection definitions
- Read/write splitting configuration
- Connection pooling settings
- **cache.php**: Cache driver configurations
- Redis, Memcached, file-based settings
- TTL defaults per cache type
- Cache key prefixes
- **tracker.php**: BitTorrent tracker settings
- Announce intervals
- Peer limits
- Ratio requirements
- **environments/**: Environment-specific overrides
- Development, staging, production settings
- Local developer configurations
Example database configuration:
```php
<?php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'tp'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
],
],
];
```
## Implementation Order
1. **Phase 1: Foundation**
- Create directory structure
- Set up base classes in Domain/Shared
- Configure dependency injection
2. **Phase 2: Domain Modeling**
- Implement core aggregates
- Create value objects
- Define repository interfaces
3. **Phase 3: Application Services**
- Create commands and queries
- Implement handlers
- Set up event dispatching
4. **Phase 4: Infrastructure**
- Implement repositories
- Configure database adapter
- Set up caching
5. **Phase 5: Presentation**
- Create controllers
- Implement middleware
- Build CLI commands
## Migration Strategy
- Existing code remains in current locations
- New features built in hexagonal architecture
- Gradual migration using strangler fig pattern
- Legacy adapters bridge old and new code
- Feature flags control rollout
## Key Principles
1. **Dependency Rule**: Dependencies point inward (Presentation → Application → Domain)
2. **Domain Isolation**: Business logic has no framework dependencies
3. **Interface Segregation**: Small, focused interfaces
4. **CQRS**: Separate read and write models
5. **Event-Driven**: Domain events for cross-context communication
## Testing Strategy
- **Domain**: Pure unit tests, no mocks needed
- **Application**: Unit tests with mocked repositories
- **Infrastructure**: Integration tests with real services
- **Presentation**: E2E tests for user journeys
## Notes for Developers
- Start reading code from the Domain layer
- Business rules live in aggregates, not services
- Use value objects for type safety
- Prefer composition over inheritance
- Keep bounded contexts loosely coupled

View file

@ -1,27 +0,0 @@
# Example Caddy configuration for TorrentPier
example.com {
root * /path/to/root
encode gzip zstd
php_fastcgi unix//run/php/php-fpm.sock
try_files {path} {path}/ /index.php?{query}
file_server
@blocked {
path /install/* /internal_data/* /library/*
path /.ht* /.en*
path /.git/*
path *.sql *.tpl *.db *.inc *.log *.md
}
respond @blocked 404
redir /sitemap.xml /sitemap/sitemap.xml
@html_css_js {
path *.html *.css *.js *.json *.xml *.txt
}
header @html_css_js Content-Type "{mime}; charset=utf-8"
}
# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

View file

@ -1,39 +0,0 @@
# Example nginx configuration for TorrentPier
server {
listen 80; # port
server_name example.com; # your domain
root /path/to/root; # folder with TorrentPier installed
index index.php;
charset utf-8;
location / {
try_files \$uri \$uri/ /index.php?\$args;
}
location ~ \/(install|internal_data|library)\/ {
return 404;
}
location ~ /\.(ht|en) {
return 404;
}
location ~ /\.git {
return 404;
}
location ~ \.(.*sql|tpl|db|inc|log|md)$ {
return 404;
}
rewrite ^/sitemap.xml$ /sitemap/sitemap.xml;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}
}

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
<directory>src</directory>
</include>
</source>
</phpunit>

View file

@ -1,11 +0,0 @@
# Forum Commands
Commands representing write operations:
- `CreateThreadCommand`: Start new discussion
- `CreatePostCommand`: Reply to thread
- `EditPostCommand`: Modify existing post
- `LockThreadCommand`: Lock thread from replies
- `DeletePostCommand`: Remove post
Commands are simple DTOs containing operation data.

View file

@ -1,9 +0,0 @@
# Forum Handlers
Command and query handlers for forum operations:
- `CreateThreadHandler`: Handles thread creation
- `CreatePostHandler`: Handles post creation
- `GetThreadListHandler`: Retrieves thread listings
Handlers coordinate between domain and infrastructure layers.

View file

@ -1,10 +0,0 @@
# Forum Queries
Read-only queries for forum data:
- `GetThreadListQuery`: Paginated thread listing
- `GetPostsByThreadQuery`: Thread posts with pagination
- `SearchPostsQuery`: Full-text post search
- `GetForumStatsQuery`: Forum statistics
Queries return DTOs optimized for presentation.

View file

@ -1,14 +0,0 @@
# Application Layer
Contains application services that orchestrate domain objects to fulfill use cases.
- Commands: Write operations that change state
- Queries: Read operations for data retrieval
- Handlers: Process commands and queries
This layer should:
- Coordinate domain objects
- Handle transactions
- Dispatch domain events
- Not contain business logic

View file

@ -1,21 +0,0 @@
# Tracker Commands
Commands representing write operations:
- `RegisterTorrentCommand`: Register new torrent
- `UpdateTorrentCommand`: Modify torrent details
- `DeleteTorrentCommand`: Remove torrent from tracker
Example:
```php
final class RegisterTorrentCommand
{
public function __construct(
public readonly string $infoHash,
public readonly int $uploaderId,
public readonly string $name,
public readonly int $size
) {}
}
```

View file

@ -1,9 +0,0 @@
# Tracker Handlers
Command and query handlers for tracker operations:
- `RegisterTorrentHandler`: Processes torrent registration
- `GetTorrentDetailsHandler`: Retrieves torrent information
- `UpdateTorrentHandler`: Handles torrent updates
Note: High-performance announce operations bypass this layer.

View file

@ -1,10 +0,0 @@
# Tracker Queries
Read-only queries for tracker data:
- `GetTorrentDetailsQuery`: Single torrent information
- `GetPeerListQuery`: Active peers for torrent
- `GetTorrentStatsQuery`: Download/upload statistics
- `SearchTorrentsQuery`: Browse and filter torrents
Optimized for high-performance read operations.

View file

@ -1,11 +0,0 @@
# User Commands
Commands for user management operations:
- `RegisterUserCommand`: New user registration
- `ChangePasswordCommand`: Password update
- `UpdateProfileCommand`: Profile modifications
- `ActivateUserCommand`: Account activation
- `BanUserCommand`: User suspension
These commands trigger domain events for cross-context synchronization.

View file

@ -1,9 +0,0 @@
# User Handlers
Command and query handlers for user operations:
- `RegisterUserHandler`: Processes user registration
- `ChangePasswordHandler`: Handles password changes
- `GetUserProfileHandler`: Retrieves user profiles
Handlers manage transactions and event dispatching.

View file

@ -1,10 +0,0 @@
# User Queries
Read operations for user data:
- `GetUserProfileQuery`: User profile details
- `SearchUsersQuery`: User search with filters
- `GetUserStatisticsQuery`: Upload/download stats
- `GetUserPermissionsQuery`: Authorization data
Returns DTOs suitable for presentation layer.

View file

@ -1,422 +0,0 @@
# Unified Cache System
A modern, unified caching solution for TorrentPier 3.0 that uses **Nette Caching** internally. **Breaking changes**: This replaces the legacy Cache and Datastore APIs and requires code migration.
## Overview
The Unified Cache System addresses the complexity and duplication in TorrentPier's caching architecture by:
- **Unifying** Cache and Datastore systems into a single, coherent solution
- **Modernizing** the caching layer with Nette's advanced features and breaking changes for better architecture
- **Reducing** complexity and maintenance overhead
- **Improving** performance with efficient singleton pattern and advanced features
## Architecture
### Core Components
1. **UnifiedCacheSystem** - Main singleton orchestrator following TorrentPier's architectural patterns
2. **CacheManager** - Cache interface using Nette Caching internally with singleton pattern
3. **DatastoreManager** - Datastore interface that uses CacheManager internally for unified functionality
### Singleton Architecture
The system follows TorrentPier's consistent singleton pattern, similar to `config()`, `dev()`, `censor()`, and `DB()`:
```php
// Main singleton instance
TorrentPier\Cache\UnifiedCacheSystem::getInstance(config()->all());
// Clean global functions with proper return types
function CACHE(string $cache_name): \TorrentPier\Cache\CacheManager
function datastore(): \TorrentPier\Cache\DatastoreManager
// Usage (exactly like before)
$cache = CACHE('bb_cache');
$datastore = datastore();
```
### Key Benefits
- ✅ **Single Source of Truth**: One caching system instead of two separate ones
- ✅ **Modern Foundation**: Built on Nette Caching v3.3 with all its advanced features
- ✅ **Zero Breaking Changes**: All existing `CACHE()` and `$datastore` calls work unchanged
- ✅ **Consistent Architecture**: Proper singleton pattern matching other TorrentPier services
- ✅ **Advanced Features**: Dependencies, tags, bulk operations, memoization, output buffering
- ✅ **Better Debugging**: Unified debug interface with compatibility for Dev.php
- ✅ **Performance**: 456,647+ operations per second with efficient memory usage
- ✅ **Clean Architecture**: No redundant configuration logic, single storage creation path
## Usage
### Basic Cache Operations (100% Backward Compatible)
```php
// All existing cache calls work exactly the same
$cache = CACHE('bb_cache');
$value = $cache->get('key');
$cache->set('key', $value, 3600);
$cache->rm('key');
```
### Datastore Operations (100% Backward Compatible)
```php
// All existing datastore calls work exactly the same
$datastore = datastore();
$forums = $datastore->get('cat_forums');
$datastore->store('custom_data', $data);
$datastore->update(['cat_forums', 'stats']);
```
### Advanced Nette Caching Features
```php
// Get cache manager for advanced features
$cache = CACHE('bb_cache');
// Load with callback (compute if not cached)
$value = $cache->load('expensive_key', function() {
return expensive_computation();
});
// Cache with time expiration
$cache->save('key', $value, [
\Nette\Caching\Cache::Expire => '1 hour'
]);
// Cache with file dependencies
$cache->save('config', $data, [
\Nette\Caching\Cache::Files => ['/path/to/config.php']
]);
// Memoize function calls
$result = $cache->call('expensive_function', $param1, $param2);
// Bulk operations
$values = $cache->bulkLoad(['key1', 'key2', 'key3'], function($key) {
return "computed_value_for_$key";
});
// Clean by tags (requires SQLite storage)
$cache->clean([\Nette\Caching\Cache::Tags => ['user-123']]);
// Output buffering
$content = $cache->capture('output_key', function() {
echo "This content will be cached";
});
```
### Datastore Advanced Features
```php
$datastore = datastore();
// All standard operations work
$forums = $datastore->get('cat_forums');
$datastore->store('custom_data', $data);
// Access underlying CacheManager for advanced features
$manager = $datastore->getCacheManager();
$value = $manager->load('complex_data', function() {
return build_complex_data();
}, [
\Nette\Caching\Cache::Expire => '30 minutes',
\Nette\Caching\Cache::Tags => ['forums', 'categories']
]);
```
## Integration & Initialization
### Automatic Integration
The system integrates seamlessly in `library/includes/functions.php`:
```php
// Singleton initialization (done once)
TorrentPier\Cache\UnifiedCacheSystem::getInstance(config()->all());
// Global functions provide backward compatibility
function CACHE(string $cache_name): \TorrentPier\Cache\CacheManager {
return TorrentPier\Cache\UnifiedCacheSystem::getInstance()->getCache($cache_name);
}
function datastore(): \TorrentPier\Cache\DatastoreManager {
return TorrentPier\Cache\UnifiedCacheSystem::getInstance()->getDatastore(config()->get('datastore_type', 'file'));
}
```
### Debug Compatibility
The system maintains full compatibility with Dev.php debugging:
```php
// Dev.php can access debug information via magic __get() methods
$cache = CACHE('bb_cache');
$debug_info = $cache->dbg; // Array of operations
$engine_name = $cache->engine; // Storage engine name
$total_time = $cache->sql_timetotal; // Total operation time
$datastore = datastore();
$datastore_debug = $datastore->dbg; // Datastore debug info
```
## Configuration
The system uses existing configuration seamlessly:
```php
// library/config.php
$bb_cfg['cache'] = [
'db_dir' => realpath(BB_ROOT) . '/internal_data/cache/filecache/',
'prefix' => 'tp_',
'engines' => [
'bb_cache' => ['file'], // Uses Nette FileStorage
'session_cache' => ['sqlite'], // Uses Nette SQLiteStorage
'tr_cache' => ['file'], // Uses Nette FileStorage
// ... other caches
],
];
$bb_cfg['datastore_type'] = 'file'; // Uses Nette FileStorage
```
## Storage Types
### Supported Storage Types
| Legacy Type | Nette Storage | Features |
|------------|---------------|----------|
| `file` | `FileStorage` | File-based, persistent, dependencies |
| `sqlite` | `SQLiteStorage` | Database, supports tags and complex dependencies |
| `memory` | `MemoryStorage` | In-memory, fastest, non-persistent |
| `memcached` | `MemcachedStorage` | Distributed memory, high-performance |
### Storage Features Comparison
| Feature | FileStorage | SQLiteStorage | MemoryStorage | MemcachedStorage |
|---------|-------------|---------------|---------------|------------------|
| Persistence | ✅ | ✅ | ❌ | ✅ |
| File Dependencies | ✅ | ✅ | ✅ | ✅ |
| Tags | ❌ | ✅ | ✅ | ❌ |
| Callbacks | ✅ | ✅ | ✅ | ✅ |
| Bulk Operations | ✅ | ✅ | ✅ | ✅ |
| Performance | High | Medium | Highest | Very High |
| Distributed | ❌ | ❌ | ❌ | ✅ |
## Migration Guide
### Zero Migration Required
All existing code continues to work without any modifications:
```php
// ✅ This works exactly as before - no changes needed
$cache = CACHE('bb_cache');
$forums = $datastore->get('cat_forums');
// ✅ All debug functionality preserved
global $CACHES;
foreach ($CACHES->obj as $cache_name => $cache_obj) {
echo "Cache: $cache_name\n";
}
```
### Enhanced Capabilities for New Code
New code can take advantage of advanced features:
```php
// ✅ Enhanced caching with dependencies and tags
$cache = CACHE('bb_cache');
$forums = $cache->load('forums_with_stats', function() {
return build_forums_with_statistics();
}, [
\Nette\Caching\Cache::Expire => '1 hour',
\Nette\Caching\Cache::Files => ['/path/to/forums.config'],
\Nette\Caching\Cache::Tags => ['forums', 'statistics']
]);
// ✅ Function memoization
$expensive_result = $cache->call('calculate_user_stats', $user_id);
// ✅ Output buffering
$rendered_page = $cache->capture("page_$page_id", function() {
include_template('complex_page.php');
});
```
## Performance Benefits
### Benchmarks
- **456,647+ operations per second** in production testing
- **Singleton efficiency**: Each cache namespace instantiated only once
- **Memory optimization**: Shared storage and efficient instance management
- **Nette optimizations**: Advanced algorithms for cache invalidation and cleanup
### Advanced Features Performance
- **Bulk Operations**: Load multiple keys in single operation
- **Memoization**: Automatic function result caching with parameter-based keys
- **Dependencies**: Smart cache invalidation based on files, time, or custom logic
- **Output Buffering**: Cache generated output directly without intermediate storage
## Critical Issues Resolved
### Sessions Compatibility
**Issue**: Legacy cache returns `false` for missing values, Nette returns `null`
**Solution**: CacheManager->get() returns `$result ?? false` for backward compatibility
### Debug Integration
**Issue**: Dev.php expected `->db` property on cache objects for debug logging
**Solution**: Added `__get()` magic methods returning compatible debug objects with `dbg[]`, `engine`, `sql_timetotal` properties
### Architecture Consistency
**Issue**: Inconsistent initialization pattern compared to other TorrentPier singletons
**Solution**: Converted to proper singleton pattern with `getInstance()` method and clean global functions
## Implementation Details
### Architecture Flow (Refactored)
**Clean, Non-Redundant Architecture:**
```
UnifiedCacheSystem (singleton)
├── _buildStorage() → Creates Nette Storage instances directly
├── get_cache_obj() → Returns CacheManager with pre-built storage
└── getDatastore() → Returns DatastoreManager with pre-built storage
CacheManager (receives pre-built Storage)
├── Constructor receives: Storage instance + minimal config
├── No redundant initializeStorage() switch statement
└── Focuses purely on cache operations
DatastoreManager (uses CacheManager internally)
├── Constructor receives: Storage instance + minimal config
├── Uses CacheManager internally for unified functionality
└── Maintains datastore-specific methods and compatibility
```
**Benefits of Refactored Architecture:**
- **Single Source of Truth**: Only UnifiedCacheSystem creates storage instances
- **No Redundancy**: Eliminated duplicate switch statements and configuration parsing
- **Cleaner Separation**: CacheManager focuses on caching, not storage creation
- **Impossible Path Bugs**: Storage is pre-built, no configuration mismatches possible
- **Better Maintainability**: One place to modify storage creation logic
### Directory Structure
```
src/Cache/
├── CacheManager.php # Cache interface with Nette Caching + singleton pattern
├── DatastoreManager.php # Datastore interface using CacheManager internally
├── UnifiedCacheSystem.php # Main singleton orchestrator + storage factory
└── README.md # This documentation
```
### Removed Development Files
The following development and testing files were removed after successful integration:
- `Example.php` - Migration examples (no longer needed)
- `Integration.php` - Testing utilities (production-ready)
- `cache_test.php` - Performance testing script (completed)
### Key Features Achieved
1. **100% Backward Compatibility**: All existing APIs work unchanged
2. **Modern Foundation**: Built on stable, well-tested Nette Caching v3.3
3. **Advanced Features**: Dependencies, tags, bulk operations, memoization, output buffering
4. **Efficient Singletons**: Memory-efficient instance management following TorrentPier patterns
5. **Unified Debugging**: Consistent debug interface compatible with Dev.php
6. **Production Ready**: Comprehensive error handling, validation, and performance optimization
7. **Clean Architecture**: Eliminated redundant configuration logic and switch statements
8. **Single Storage Source**: All storage creation centralized in UnifiedCacheSystem
### Architectural Consistency
Following TorrentPier's established patterns:
```php
// Consistent with other singletons
config() -> Config::getInstance()
dev() -> Dev::getInstance()
censor() -> Censor::getInstance()
DB() -> DB::getInstance()
CACHE() -> UnifiedCacheSystem::getInstance()->getCache()
datastore() -> UnifiedCacheSystem::getInstance()->getDatastore()
```
## Testing & Verification
### Backward Compatibility Verified
```php
// ✅ All existing functionality preserved
$cache = CACHE('bb_cache');
assert($cache->set('test', 'value', 60) === true);
assert($cache->get('test') === 'value');
assert($cache->rm('test') === true);
$datastore = datastore();
$datastore->store('test_item', ['data' => 'test']);
assert($datastore->get('test_item')['data'] === 'test');
```
### Advanced Features Verified
```php
// ✅ Nette features working correctly
$cache = CACHE('advanced_test');
// Memoization
$result1 = $cache->call('expensive_function', 'param');
$result2 = $cache->call('expensive_function', 'param'); // From cache
// Dependencies
$cache->save('file_dependent', $data, [
\Nette\Caching\Cache::Files => [__FILE__]
]);
// Bulk operations
$values = $cache->bulkLoad(['key1', 'key2'], function($key) {
return "value_$key";
});
// Performance: 456,647+ ops/sec verified
```
### Debug Functionality Verified
```php
// ✅ Dev.php integration working
$cache = CACHE('bb_cache');
$debug = $cache->dbg; // Returns array of operations
$engine = $cache->engine; // Returns storage type
$time = $cache->sql_timetotal; // Returns total time
// ✅ Singleton behavior verified
$instance1 = TorrentPier\Cache\UnifiedCacheSystem::getInstance();
$instance2 = TorrentPier\Cache\UnifiedCacheSystem::getInstance();
assert($instance1 === $instance2); // Same instance
```
## Future Enhancements
### Planned Storage Implementations
- Redis storage adapter for Nette
- Memcached storage adapter for Nette
- APCu storage adapter for Nette
### Advanced Features Roadmap
- Distributed caching support
- Cache warming and preloading
- Advanced metrics and monitoring
- Multi-tier caching strategies
---
This unified cache system represents a significant architectural improvement in TorrentPier while ensuring seamless backward compatibility and providing a robust foundation for future enhancements. The clean singleton pattern, advanced Nette Caching features, and comprehensive debug support make it a production-ready replacement for the legacy Cache and Datastore systems.

View file

@ -1,362 +0,0 @@
# TorrentPier Database Layer
This directory contains the new database layer for TorrentPier 3.0 that uses Nette Database internally. **Breaking changes**: This replaces the legacy SqlDb interface and requires code migration.
## Overview
The new database system has completely replaced the legacy SqlDb/Dbs system and provides:
- **Modern API** - New `DB()->method()` calls with improved functionality
- **Nette Database integration** - Modern, efficient database layer under the hood
- **Singleton pattern** - Efficient connection management
- **Complete feature parity** - All original functionality preserved
## Architecture
### Classes
1. **`Database`** - Main singleton database class using Nette Database Connection
2. **`DatabaseFactory`** - Factory that has completely replaced the legacy SqlDb/Dbs system
3. **`DatabaseDebugger`** - Dedicated debug functionality extracted from Database class
4. **`DebugSelection`** - Debug-enabled wrapper for Nette Database Selection
### Key Features
- **Singleton Pattern**: Ensures single database connection per server configuration
- **Multiple Database Support**: Handles multiple database servers via DatabaseFactory
- **Raw SQL Support**: Uses Nette Database's Connection class (SQL way) for minimal code impact
- **Complete Error Handling**: Maintains existing error handling behavior
- **Full Debug Support**: Preserves all debugging, logging, and explain functionality
- **Performance Tracking**: Query timing and slow query detection
- **Clean Architecture**: Debug functionality extracted to dedicated DatabaseDebugger class
- **Modular Design**: Single responsibility principle with separate debug and database concerns
## Implementation Status
- ✅ **Complete Replacement**: Legacy SqlDb/Dbs classes have been removed from the codebase
- ✅ **Backward Compatibility**: All existing `DB()->method()` calls work unchanged
- ✅ **Debug System**: Full explain(), logging, and performance tracking
- ✅ **Error Handling**: Complete error handling with sql_error() support
- ✅ **Connection Management**: Singleton pattern with proper initialization
- ✅ **Clean Architecture**: Debug functionality extracted to dedicated classes
- ✅ **Class Renaming**: Renamed DB → Database, DbFactory → DatabaseFactory for consistency
## Usage
### Standard Database Operations
```php
// All existing code works unchanged
$user = DB()->fetch_row("SELECT * FROM users WHERE id = ?", 123);
$users = DB()->fetch_rowset("SELECT * FROM users");
$affected = DB()->affected_rows();
// Raw queries
$result = DB()->sql_query("UPDATE users SET status = ? WHERE id = ?", 1, 123);
// Data building
$data = ['name' => 'John', 'email' => 'john@example.com'];
$sql = DB()->build_array('INSERT', $data);
```
### Multiple Database Servers
```php
// Access different database servers
$main_db = DB('db'); // Main database
$tracker_db = DB('tr'); // Tracker database
$stats_db = DB('stats'); // Statistics database
```
### Error Handling
```php
$result = DB()->sql_query("SELECT * FROM users");
if (!$result) {
$error = DB()->sql_error();
echo "Error: " . $error['message'];
}
```
## Configuration
The database configuration is handled through the existing TorrentPier config system:
```php
// Initialized in common.php
TorrentPier\Database\DatabaseFactory::init(
config()->get('db'), // Database configurations
config()->get('db_alias', []) // Database aliases
);
```
## Benefits
### Performance
- **Efficient Connections**: Singleton pattern prevents connection overhead
- **Modern Database Layer**: Nette Database v3.2 optimizations
- **Resource Management**: Automatic cleanup and proper connection handling
### Maintainability
- **Modern Codebase**: Uses current PHP standards and type declarations
- **Better Architecture**: Clean separation of concerns
- **Nette Ecosystem**: Part of actively maintained Nette framework
### Reliability
- **Proven Technology**: Nette Database is battle-tested
- **Regular Updates**: Automatic security and bug fixes through composer
- **Type Safety**: Better error detection and IDE support
## Debugging Features
All original debugging features are preserved and enhanced:
### Query Logging
- SQL query logging with timing
- Slow query detection and logging
- Memory usage tracking
### Debug Information
```php
// Enable debugging (same as before)
DB()->debug('start');
// ... run queries ...
DB()->debug('stop');
```
### Explain Functionality
```php
// Explain queries (same interface as before)
DB()->explain('start');
DB()->explain('display');
```
## Technical Details
### Nette Database Integration
- Uses Nette Database **Connection** class (SQL way)
- Maintains raw SQL approach for minimal migration impact
- PDO-based with proper parameter binding
### Compatibility Layer
- All original method signatures preserved
- Same return types and behavior
- Error handling matches original implementation
### Connection Management
- Single connection per database server
- Lazy connection initialization
- Proper connection cleanup
## Migration Notes
This is a **complete replacement** that maintains 100% backward compatibility:
1. **No Code Changes Required**: All existing `DB()->method()` calls work unchanged
2. **Same Configuration**: Uses existing database configuration
3. **Same Behavior**: Error handling, return values, and debugging work identically
4. **Enhanced Performance**: Better connection management and modern database layer
## Dependencies
- **Nette Database v3.2**: Already included in composer.json
- **PHP 8.0+**: Required for type declarations and modern features
## Files
- `Database.php` - Main database class with full backward compatibility
- `DatabaseFactory.php` - Factory for managing database instances
- `DatabaseDebugger.php` - Dedicated debug functionality class
- `DebugSelection.php` - Debug-enabled Nette Selection wrapper
- `README.md` - This documentation
## Future Enhancement: Gradual Migration to Nette Explorer
While the current implementation uses Nette Database's **Connection** class (SQL way) for maximum compatibility, TorrentPier can gradually migrate to **Nette Database Explorer** for more modern ORM-style database operations.
### Phase 1: Hybrid Approach
Add Explorer support alongside existing Connection-based methods:
```php
// Current Connection-based approach (maintains compatibility)
$users = DB()->fetch_rowset("SELECT * FROM users WHERE status = ?", 1);
// New Explorer-based approach (added gradually)
$users = DB()->table('users')->where('status', 1)->fetchAll();
```
### Phase 2: Explorer Method Examples
#### Basic Table Operations
```php
// Select operations
$user = DB()->table('users')->get(123); // Get by ID
$users = DB()->table('users')->where('status', 1)->fetchAll(); // Where condition
$count = DB()->table('users')->where('status', 1)->count(); // Count records
// Insert operations
$user_id = DB()->table('users')->insert([
'username' => 'john',
'email' => 'john@example.com',
'reg_time' => time()
]);
// Update operations
DB()->table('users')
->where('id', 123)
->update(['last_visit' => time()]);
// Delete operations
DB()->table('users')
->where('status', 0)
->delete();
```
#### Advanced Explorer Features
```php
// Joins and relationships
$posts = DB()->table('posts')
->select('posts.*, users.username')
->where('posts.forum_id', 5)
->order('posts.post_time DESC')
->limit(20)
->fetchAll();
// Aggregations
$stats = DB()->table('torrents')
->select('forum_id, COUNT(*) as total, SUM(size) as total_size')
->where('approved', 1)
->group('forum_id')
->fetchAll();
// Subqueries
$active_users = DB()->table('users')
->where('last_visit > ?', time() - 86400)
->where('id IN', DB()->table('posts')
->select('user_id')
->where('post_time > ?', time() - 86400)
)
->fetchAll();
```
#### Working with Related Data
```php
// One-to-many relationships
$user = DB()->table('users')->get(123);
$user_posts = $user->related('posts')->order('post_time DESC');
// Many-to-many through junction table
$torrent = DB()->table('torrents')->get(456);
$seeders = $torrent->related('bt_tracker', 'torrent_id')
->where('seeder', 'yes')
->select('user_id');
```
### Phase 3: Migration Strategy
#### Step-by-Step Conversion
1. **Identify Patterns**: Find common SQL patterns in the codebase
2. **Create Helpers**: Build wrapper methods for complex queries
3. **Test Incrementally**: Convert one module at a time
4. **Maintain Compatibility**: Keep both approaches during transition
#### Example Migration Pattern
```php
// Before: Raw SQL
$result = DB()->sql_query("
SELECT t.*, u.username
FROM torrents t
JOIN users u ON t.poster_id = u.user_id
WHERE t.forum_id = ? AND t.approved = 1
ORDER BY t.reg_time DESC
LIMIT ?
", $forum_id, $limit);
$torrents = [];
while ($row = DB()->sql_fetchrow($result)) {
$torrents[] = $row;
}
// After: Explorer ORM
$torrents = DB()->table('torrents')
->alias('t')
->select('t.*, u.username')
->where('t.forum_id', $forum_id)
->where('t.approved', 1)
->order('t.reg_time DESC')
->limit($limit)
->fetchAll();
```
### Phase 4: Advanced Explorer Features
#### Custom Repository Classes
```php
// Create specialized repository classes
class TorrentRepository
{
private $db;
public function __construct($db)
{
$this->db = $db;
}
public function getApprovedByForum($forum_id, $limit = 20)
{
return $this->db->table('torrents')
->where('forum_id', $forum_id)
->where('approved', 1)
->order('reg_time DESC')
->limit($limit)
->fetchAll();
}
public function getTopSeeded($limit = 10)
{
return $this->db->table('torrents')
->where('approved', 1)
->order('seeders DESC')
->limit($limit)
->fetchAll();
}
}
// Usage
$torrentRepo = new TorrentRepository(DB());
$popular = $torrentRepo->getTopSeeded();
```
#### Database Events and Caching
```php
// Add caching layer
$cached_result = DB()->table('users')
->where('status', 1)
->cache('active_users', 3600) // Cache for 1 hour
->fetchAll();
// Database events for logging
DB()->onQuery[] = function ($query, $parameters, $time) {
if ($time > 1.0) { // Log slow queries
error_log("Slow query ({$time}s): $query");
}
};
```
### Benefits of Explorer Migration
#### Developer Experience
- **Fluent Interface**: Chainable method calls
- **IDE Support**: Better autocomplete and type hints
- **Less SQL**: Reduced raw SQL writing
- **Built-in Security**: Automatic parameter binding
#### Code Quality
- **Readable Code**: Self-documenting query building
- **Reusable Patterns**: Common queries become methods
- **Type Safety**: Better error detection
- **Testing**: Easier to mock and test
#### Performance
- **Query Optimization**: Explorer can optimize queries
- **Lazy Loading**: Load related data only when needed
- **Connection Pooling**: Better resource management
- **Caching Integration**: Built-in caching support

View file

@ -1,8 +0,0 @@
# Forum Domain Exceptions
Domain-specific exceptions for forum operations:
- `ThreadLockedException`: Attempt to post in locked thread
- `PostEditTimeExpiredException`: Edit time limit exceeded
- `ForumAccessDeniedException`: Insufficient permissions
- `InvalidPostContentException`: Post validation failed

View file

@ -1,15 +0,0 @@
# Forum Domain Models
Contains aggregate roots and entities for the forum system:
- `Forum`: Forum category aggregate
- `Thread`: Discussion thread aggregate root
- `Post`: Individual post entity
- `Attachment`: File attachment entity
Business rules enforced at this level:
- Post editing time limits
- Thread locking rules
- Forum access permissions
- Post moderation workflow

View file

@ -1,9 +0,0 @@
# Forum Repository Interfaces
Repository interfaces for forum aggregates:
- `ForumRepositoryInterface`: Forum aggregate persistence
- `ThreadRepositoryInterface`: Thread aggregate persistence
- `PostRepositoryInterface`: Post queries (read-only)
These interfaces define contracts that infrastructure must implement.

View file

@ -1,10 +0,0 @@
# Forum Value Objects
Immutable objects representing forum concepts:
- `PostId`: Unique post identifier
- `ThreadTitle`: Thread title with validation
- `PostContent`: Formatted post content
- `ForumSlug`: URL-friendly forum identifier
These objects ensure type safety and encapsulate validation rules.

View file

@ -1,15 +0,0 @@
# Domain Layer
This directory contains the core business logic of TorrentPier. Code here should:
- Have no dependencies on frameworks or infrastructure
- Represent pure business rules and domain models
- Be testable in isolation
- Use only PHP language features and domain concepts
## Bounded Contexts
- **Forum**: Discussion forums, posts, threads
- **Tracker**: BitTorrent tracking, peers, torrents
- **User**: User management, authentication, profiles
- **Shared**: Minimal shared concepts between contexts

View file

@ -1,15 +0,0 @@
# Domain Events
Base classes and interfaces for domain event system:
- `DomainEvent`: Base event interface
- `EventRecording`: Trait for aggregate event recording
- `AggregateHistory`: Event sourcing support
Example events:
- `UserRegisteredEvent`
- `TorrentUploadedEvent`
- `ThreadCreatedEvent`
Events enable loose coupling between bounded contexts.

View file

@ -1,10 +0,0 @@
# Shared Domain Exceptions
Base exception classes used across all bounded contexts:
- `DomainException`: Base domain exception
- `InvalidArgumentException`: Invalid input validation
- `EntityNotFoundException`: Generic entity not found
- `BusinessRuleViolationException`: Business rule violations
These provide common exception handling patterns without coupling contexts.

View file

@ -1,10 +0,0 @@
# Shared Domain Models
Base classes and interfaces used across bounded contexts:
- `AggregateRoot`: Base class for all aggregates
- `Entity`: Base class for entities
- `DomainEvent`: Interface for domain events
- `ValueObject`: Base value object interface
These provide common functionality while maintaining context boundaries.

View file

@ -1,9 +0,0 @@
# Shared Repository Interfaces
Common repository patterns and base interfaces:
- `RepositoryInterface`: Base repository contract
- `ReadOnlyRepositoryInterface`: Query-only operations
- `AggregateRepositoryInterface`: Aggregate-specific methods
These define common persistence patterns without implementation details.

View file

@ -1,10 +0,0 @@
# Shared Value Objects
Common value objects used across contexts:
- `Id`: Generic identifier base class
- `DateTime`: Immutable datetime wrapper
- `Money`: Monetary value representation
- `Percentage`: Percentage value (0-100)
These are minimal shared concepts that don't violate bounded context principles.

View file

@ -1,8 +0,0 @@
# Tracker Domain Exceptions
Domain-specific exceptions for tracker operations:
- `InvalidInfoHashException`: Malformed info hash
- `InactiveTorrentException`: Torrent not active
- `PeerLimitExceededException`: Too many peers
- `InvalidAnnounceException`: Protocol violation

View file

@ -1,19 +0,0 @@
# Tracker Domain Models
Contains aggregate roots and entities for the BitTorrent tracker:
- `Torrent`: Aggregate root for torrent management
- `Peer`: Entity representing a BitTorrent peer
- `TorrentStatistics`: Value object for torrent stats
Example:
```php
class Torrent extends AggregateRoot
{
public function announce(Peer $peer, AnnounceEvent $event): void
{
// Business logic for handling announces
}
}
```

View file

@ -1,9 +0,0 @@
# Tracker Repository Interfaces
Repository interfaces for tracker aggregates:
- `TorrentRepositoryInterface`: Torrent aggregate persistence
- `PeerRepositoryInterface`: Peer data access
- `TrackerStatsRepositoryInterface`: Statistics queries
These define persistence contracts without implementation details.

View file

@ -1,23 +0,0 @@
# Tracker Value Objects
Immutable objects representing domain concepts:
- `InfoHash`: 20-byte torrent identifier
- `PeerId`: Peer client identifier
- `Port`: Network port (1-65535)
- `BytesTransferred`: Upload/download bytes
Example:
```php
final class InfoHash
{
private string $hash;
public function __construct(string $hash)
{
$this->guardAgainstInvalidHash($hash);
$this->hash = $hash;
}
}
```

View file

@ -1,9 +0,0 @@
# User Domain Exceptions
Domain-specific exceptions for user operations:
- `UserNotFoundException`: User not found
- `DuplicateUsernameException`: Username already taken
- `InvalidCredentialsException`: Authentication failed
- `UserNotActivatedException`: Account not activated
- `PasswordComplexityException`: Weak password

View file

@ -1,15 +0,0 @@
# User Domain Models
Contains user management aggregates and entities:
- `User`: User aggregate root
- `UserProfile`: User profile information
- `UserCredentials`: Authentication credentials
- `UserPermissions`: Authorization rules
Business rules:
- Password complexity requirements
- Username uniqueness
- Email verification workflow
- Account activation/deactivation

View file

@ -1,10 +0,0 @@
# User Repository Interface
Repository interface for user aggregate:
- `UserRepositoryInterface`: User persistence and retrieval
- `findById(UserId $id): ?User`
- `findByUsername(Username $username): ?User`
- `findByEmail(Email $email): ?User`
- `save(User $user): void`
- `delete(User $user): void`

View file

@ -1,11 +0,0 @@
# User Value Objects
Immutable objects for user domain:
- `UserId`: Unique user identifier
- `Username`: Username with validation
- `Email`: Email address with format validation
- `HashedPassword`: Secure password representation
- `UserRole`: User role enumeration
These ensure data integrity and type safety.

View file

@ -1,9 +0,0 @@
# File Cache Adapter
File system-based caching implementation:
- Simple file-based storage
- Suitable for single-server deployments
- Directory structure optimization
- Lock file support for concurrency
- Automatic garbage collection

View file

@ -1,9 +0,0 @@
# Memcached Cache Adapter
Memcached-based caching implementation:
- Simple key-value caching
- Distributed cache support
- Automatic key expiration
- Connection pooling
- Consistent hashing for distribution

View file

@ -1,9 +0,0 @@
# Redis Cache Adapter
Redis-based caching implementation:
- High-performance key-value storage
- Support for data structures (lists, sets, hashes)
- TTL management
- Pub/sub for cache invalidation
- Cluster support for scalability

View file

@ -1,171 +0,0 @@
# Dependency Injection Infrastructure
This directory contains the dependency injection container implementation using PHP-DI, following hexagonal architecture principles.
## Architecture Overview
The DI container is placed in the Infrastructure layer because:
- Dependency injection is a technical implementation detail
- It handles wiring and bootstrapping, not business logic
- The domain layer remains pure without framework dependencies
## Structure
```
DependencyInjection/
├── Container.php # Wrapper around PHP-DI container
├── ContainerFactory.php # Factory for building configured containers
├── Bootstrap.php # Application bootstrapper
└── Definitions/ # Service definitions by layer
├── DomainDefinitions.php
├── ApplicationDefinitions.php
├── InfrastructureDefinitions.php
└── PresentationDefinitions.php
```
## Usage
### Basic Bootstrap (Works Now)
```php
use TorrentPier\Infrastructure\DependencyInjection\Bootstrap;
// Initialize the container
$container = Bootstrap::init(__DIR__ . '/../..');
// Basic usage
$containerInstance = app(); // Get container itself
$hasService = $container->has('some.service'); // Check if service exists
```
### Manual Container Creation (Works Now)
```php
use TorrentPier\Infrastructure\DependencyInjection\ContainerFactory;
$config = [
'environment' => 'production',
'definitions' => [
'custom.service' => \DI\factory(function () {
return new CustomService();
}),
],
];
$container = ContainerFactory::create($config);
```
### Future Usage (When Services Are Implemented)
```php
// These will work when the respective layers are implemented:
// $userRepository = $container->get(UserRepositoryInterface::class);
// $commandBus = $container->get(CommandBusInterface::class);
```
## Service Definitions
Services are organized by architectural layer following the hexagonal architecture spec:
### Domain Layer (`DomainDefinitions.php`)
- Repository interface mappings (when implemented in Phase 2)
- Domain service factories
- No direct infrastructure dependencies
### Application Layer (`ApplicationDefinitions.php`)
- Command/Query buses (when implemented in Phase 3)
- Command/Query handlers
- Event dispatcher
- Application services
### Infrastructure Layer (`InfrastructureDefinitions.php`)
- Database connections (when Nette Database integration is ready)
- Cache implementations (when cache infrastructure is ready)
- Repository implementations (when implemented in Phase 4)
- External service adapters
### Presentation Layer (`PresentationDefinitions.php`)
- HTTP controllers (when implemented in Phase 5)
- CLI commands
- Middleware
- Response transformers
**Note**: Most definitions are currently commented out as examples until the actual services are implemented according to the implementation phases.
## Configuration
Configuration is loaded from multiple sources:
1. **Environment Variables** (`.env` file)
2. **Configuration Files** (`/config/*.php`)
3. **Runtime Configuration** (passed to factory)
### Production Optimization
In production mode, the container:
- Compiles definitions for performance
- Generates proxies for lazy loading
- Caches resolved dependencies
Enable by setting `APP_ENV=production` in your `.env` file.
## Best Practices
1. **Use Interfaces**: Define interfaces in domain, implement in infrastructure
2. **Explicit Definitions**: Prefer explicit over magic for complex services
3. **Layer Separation**: Keep definitions organized by architectural layer
4. **Lazy Loading**: Use factories for expensive services
5. **Immutable Services**: Services should be stateless and immutable
## Example Service Registration
### Current Usage (Works Now)
```php
// In services.php or custom definitions
return [
'custom.service' => \DI\factory(function () {
return new CustomService();
}),
'test.service' => \DI\autowire(TestService::class),
];
```
### Future Examples (When Infrastructure Is Ready)
```php
// These will be uncommented when the services are implemented:
// UserRepositoryInterface::class => autowire(UserRepository::class)
// ->constructorParameter('connection', get('database.connection.default'))
// ->constructorParameter('cache', get('cache.factory')),
//
// 'email.service' => factory(function (ContainerInterface $c) {
// $config = $c->get('config')['email'];
// return new SmtpEmailService($config);
// }),
```
## Testing
For testing, create a test container with mocked services:
```php
// Current testing approach (works now)
$testConfig = [
'definitions' => [
'test.service' => \DI\factory(function () {
return new MockTestService();
}),
],
'environment' => 'testing',
];
$container = ContainerFactory::create($testConfig);
// Future testing (when services are implemented)
// $testConfig = [
// 'definitions' => [
// UserRepositoryInterface::class => $mockUserRepository,
// EmailServiceInterface::class => $mockEmailService,
// ],
// ];
```

View file

@ -1,16 +0,0 @@
# Email Templates
Email template management:
- HTML and plain text templates
- Template variables and placeholders
- Multi-language support
- Template caching
- Preview functionality
Common templates:
- User registration confirmation
- Password reset
- Torrent notifications
- Forum post notifications

View file

@ -1,10 +0,0 @@
# Email Transport
Email delivery mechanisms:
- `SmtpTransport`: Traditional SMTP delivery
- `SendgridTransport`: SendGrid API integration
- `MailgunTransport`: Mailgun API integration
- `NullTransport`: Testing/development transport
Supports queuing and retry mechanisms.

View file

@ -1,11 +0,0 @@
# Local File Storage
Local filesystem storage implementation:
- Direct disk storage
- Directory structure management
- File permissions handling
- Atomic file operations
- Cleanup and maintenance
Used for torrent files, avatars, and attachments.

View file

@ -1,11 +0,0 @@
# S3 File Storage
Amazon S3 (or compatible) storage implementation:
- Cloud object storage
- Pre-signed URLs for direct uploads
- CDN integration support
- Lifecycle policies
- Multi-region support
Provides scalable storage for large deployments.

View file

@ -1,9 +0,0 @@
# Database Adapter
Database connection and query building layer:
- `DatabaseAdapterInterface`: Common database operations
- `QueryBuilder`: Fluent query construction
- `ConnectionPool`: Connection management
Provides abstraction over raw database access.

View file

@ -1,10 +0,0 @@
# Database Migrations
Schema version control and migration management:
- Migration files for schema changes
- Seed data for initial setup
- Rollback support
- Migration history tracking
Uses Phinx or similar migration tool.

View file

@ -1,29 +0,0 @@
# Repository Implementations
Concrete implementations of domain repository interfaces:
- Uses database adapter for persistence
- Implements caching strategies
- Handles query optimization
- Supports multiple database backends
Example:
```php
class TorrentRepository implements TorrentRepositoryInterface
{
public function __construct(
private DatabaseAdapterInterface $db
) {}
public function findByInfoHash(InfoHash $infoHash): ?Torrent
{
// Database adapter implementation
$row = $this->db->select('torrents')
->where('info_hash', $infoHash->toString())
->first();
return $row ? $this->hydrateFromRow($row) : null;
}
}
```

View file

@ -1,11 +0,0 @@
# Infrastructure Layer
Technical implementations and external service adapters:
- Database persistence
- Caching mechanisms
- Email services
- File storage
- Third-party integrations
Infrastructure depends on domain, not vice versa.

View file

@ -1,10 +0,0 @@
# CLI Commands
Console commands for administrative tasks:
- `UserCreateCommand`: Create users from CLI
- `CacheClearCommand`: Clear cache stores
- `MigrateCommand`: Run database migrations
- `CronCommand`: Execute scheduled tasks
Built using Symfony Console component.

View file

@ -1,30 +0,0 @@
# Admin Panel Controllers
Administrative interface controllers with enhanced security:
- Role-based access control (RBAC)
- Audit logging for all actions
- Additional authentication checks
- Administrative dashboards and reports
Example:
```php
class AdminUserController
{
public function index(Request $request): Response
{
$query = new GetUsersQuery(
page: $request->getPage(),
filters: $request->getFilters()
);
$users = $this->queryBus->handle($query);
return $this->render('admin/users/index', [
'users' => $users,
'filters' => $request->getFilters()
]);
}
}
```

View file

@ -1,31 +0,0 @@
# API Controllers
RESTful API endpoints following OpenAPI specification:
- JSON request/response format
- Proper HTTP status codes
- HATEOAS where applicable
- Rate limiting aware
Example:
```php
class UserController
{
public function register(RegisterRequest $request): JsonResponse
{
$command = new RegisterUserCommand(
$request->getUsername(),
$request->getEmail(),
$request->getPassword()
);
$userId = $this->commandBus->handle($command);
return new JsonResponse([
'id' => $userId,
'username' => $request->getUsername()
], Response::HTTP_CREATED);
}
}
```

View file

@ -1,16 +0,0 @@
# Web Controllers
Traditional web interface controllers:
- HTML response generation
- Template rendering
- Form handling
- Session management
- CSRF protection
Controllers for:
- Forum browsing and posting
- Torrent browsing and downloading
- User profiles and settings
- Search functionality

View file

@ -1,12 +0,0 @@
# HTTP Middleware
Request/response pipeline middleware:
- `AuthenticationMiddleware`: User authentication
- `AuthorizationMiddleware`: Permission checks
- `CsrfProtectionMiddleware`: CSRF token validation
- `RateLimitMiddleware`: Request throttling
- `LocalizationMiddleware`: Language detection
- `CorsMiddleware`: Cross-origin resource sharing
Middleware follows PSR-15 standard.

View file

@ -1,25 +0,0 @@
# HTTP Requests
Request objects and validation:
- Request DTOs with validation rules
- Type-safe access to request data
- File upload handling
- Input sanitization
- Custom validation rules
Example:
```php
class RegisterRequest extends FormRequest
{
public function rules(): array
{
return [
'username' => ['required', 'string', 'min:3', 'max:20', 'unique:users'],
'email' => ['required', 'email', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
];
}
}
```

View file

@ -1,11 +0,0 @@
# HTTP Responses
Response transformation and formatting:
- Response factories
- JSON transformers
- View presenters
- Error response formatting
- Content negotiation
Ensures consistent API responses and proper HTTP semantics.

View file

@ -1,11 +0,0 @@
# Presentation Layer
User interface implementations:
- HTTP controllers for web and API
- CLI commands for console operations
- Request/response handling
- Input validation
- Output formatting
This layer translates between external format and application format.

View file

@ -1,132 +0,0 @@
# TorrentPier Whoops Enhanced Error Reporting
This directory contains enhanced Whoops error handlers specifically designed for TorrentPier to provide better debugging information when database errors occur.
## Features
### Enhanced Database Error Reporting
The enhanced Whoops handlers provide comprehensive database information when errors occur:
1. **Current SQL Query** - Shows the exact query that caused the error
2. **Recent Query History** - Displays the last 5 SQL queries executed
3. **Database Connection Status** - Connection state, server info, database name
4. **Error Context** - PDO error codes, exception details, source location
5. **TorrentPier Environment** - Debug mode status, system information
### Components
#### EnhancedPrettyPageHandler
Extends Whoops' default `PrettyPageHandler` to include:
- **Database Information** table with connection details and current query
- **Recent SQL Queries** table showing query history with timing
- **TorrentPier Environment** table with system status
#### DatabaseErrorHandler
Specialized handler that:
- Adds database context to exception stack frames
- Identifies database-related code in the call stack
- Collects comprehensive database state information
- Formats SQL queries for readable display
## Usage
The enhanced handlers are automatically activated when `DBG_USER` is enabled in TorrentPier configuration.
### Automatic Integration
```php
// In src/Dev.php - automatically configured
$prettyPageHandler = new \TorrentPier\Whoops\EnhancedPrettyPageHandler();
```
### Database Error Logging
Database errors are now automatically logged even when they occur in Nette Database layer:
```php
// Enhanced error handling in Database.php
try {
$row = $result->fetch();
} catch (\Exception $e) {
// Log the error including the query that caused it
$this->debugger->log_error($e);
throw $e; // Re-throw for Whoops display
}
```
## Error Information Displayed
When a database error occurs, Whoops will now show:
### Database Information
- Connection Status: Connected/Disconnected
- Database Server: Host and port information
- Selected Database: Current database name
- Database Engine: MySQL/PostgreSQL/etc.
- Total Queries: Number of queries executed
- Total Query Time: Cumulative execution time
- Current Query: The SQL that caused the error
- Last Database Error: Error code and message
- PDO Driver: Database driver information
- Server Version: Database server version
### Recent SQL Queries
- **Query #1-5**: Last 5 queries executed
- SQL: Formatted query text
- Time: Execution time in seconds
- Source: File and line where query originated
- Info: Additional query information
- Memory: Memory usage if available
### TorrentPier Environment
- Application Environment: local/production/etc.
- Debug Mode: Enabled/Disabled
- SQL Debug: Enabled/Disabled
- TorrentPier Version: Current version
- Config Loaded: Configuration status
- Cache System: Availability status
- Language System: Status and encoding
- Template System: Twig-based availability
- Execution Time: Request processing time
- Peak Memory: Maximum memory used
- Current Memory: Current memory usage
- Request Method: GET/POST/etc.
- Request URI: Current page
- User Agent: Browser information
- Remote IP: Client IP address
## Configuration
The enhanced handlers respect TorrentPier's debug configuration:
- `DBG_USER`: Must be enabled to show enhanced error pages
- `SQL_DEBUG`: Enables SQL query logging and timing
- `APP_ENV`: Determines environment-specific features
## Logging
Database errors are now logged in multiple locations:
1. **PHP Error Log**: Basic error message
2. **TorrentPier bb_log**: Detailed error with context (`database_errors.log`)
3. **Whoops Log**: Complete error details (`php_whoops.log`)
## Security
The enhanced handlers maintain security by:
- Only showing detailed information when `DBG_USER` is enabled
- Using Whoops' blacklist for sensitive data
- Logging detailed information to files (not user-accessible)
- Providing generic error messages to non-debug users
## Integration with TorrentPier 3.0
All enhancements are:
- **Integrated** with the new TorrentPier 3.0 architecture
- **Modern** - designed for the rewritten codebase
- **Optional** - only activated in debug mode
- **Safe** - no security implications for production use

View file

@ -1,193 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Bootstrap;
use TorrentPier\Infrastructure\DependencyInjection\Container;
describe('Container Integration', function () {
afterEach(function () {
Bootstrap::reset();
});
it('can bootstrap the container with real configuration', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'services' => [
'integration.test' => 'integration_value',
],
]);
$container = Bootstrap::init($rootPath);
expect($container)->toBeInstanceOf(Container::class);
expect($container->get('integration.test'))->toBe('integration_value');
removeTempDirectory($rootPath);
});
it('integrates with global helper functions', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'services' => [
'helper.test' => 'helper_value',
],
]);
Bootstrap::init($rootPath);
// Test container() helper
expect(container())->toBeInstanceOf(Container::class);
// Test app() helper without parameter
expect(app())->toBeInstanceOf(Container::class);
// Test app() helper with service ID
expect(app('helper.test'))->toBe('helper_value');
removeTempDirectory($rootPath);
});
it('handles missing services gracefully in helpers', function () {
$rootPath = $this->createTestRootDirectory();
Bootstrap::init($rootPath);
// Should throw RuntimeException for missing service
expect(fn() => app('missing.service'))
->toThrow(RuntimeException::class)
->toThrow('not found in container');
removeTempDirectory($rootPath);
});
it('supports autowiring for simple classes', function () {
$rootPath = $this->createTestRootDirectory();
$container = Bootstrap::init($rootPath);
// Should be able to autowire stdClass
expect($container->has(stdClass::class))->toBeTrue();
expect($container->get(stdClass::class))->toBeInstanceOf(stdClass::class);
removeTempDirectory($rootPath);
});
it('loads all architectural layer definitions', function () {
$rootPath = $this->createTestRootDirectory();
$container = Bootstrap::init($rootPath);
// Container should be created successfully with all layer definitions loaded
// Even though most definitions are commented out, the loading should work
expect($container)->toBeInstanceOf(Container::class);
// Container should have itself registered
expect($container->get(Container::class))->toBe($container);
expect($container->get('container'))->toBe($container);
removeTempDirectory($rootPath);
});
it('supports environment-based configuration', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'container' => [
'environment' => 'production',
'compilation_dir' => $rootPath . '/internal_data/cache/container',
'proxies_dir' => $rootPath . '/internal_data/cache/proxies',
],
]);
$container = Bootstrap::init($rootPath);
expect($container)->toBeInstanceOf(Container::class);
removeTempDirectory($rootPath);
});
it('supports service provider registration', function () {
$testProviderClass = new class implements \TorrentPier\Infrastructure\DependencyInjection\ServiceProvider {
public static bool $wasRegistered = false;
public static bool $wasBooted = false;
public function register(\TorrentPier\Infrastructure\DependencyInjection\Container $container): void
{
self::$wasRegistered = true;
$container->getWrappedContainer()->set('provider.test', 'provider_registered');
}
public function boot(\TorrentPier\Infrastructure\DependencyInjection\Container $container): void
{
self::$wasBooted = true;
}
};
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'container' => [
'providers' => [get_class($testProviderClass)],
],
]);
$container = Bootstrap::init($rootPath);
expect($testProviderClass::$wasRegistered)->toBeTrue();
expect($testProviderClass::$wasBooted)->toBeTrue();
expect($container->get('provider.test'))->toBe('provider_registered');
removeTempDirectory($rootPath);
});
it('handles configuration file loading priority', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'services' => [
'priority.test' => \DI\factory(function () {
return 'from_services_file';
}),
],
]);
// Initialize with runtime config that should override file config
$container = Bootstrap::init($rootPath, [
'definitions' => [
'priority.test' => \DI\factory(function () {
return 'from_runtime_config';
}),
'runtime.only' => \DI\factory(function () {
return 'runtime_value';
}),
],
]);
// Runtime config should override file config
expect($container->get('priority.test'))->toBe('from_runtime_config');
expect($container->get('runtime.only'))->toBe('runtime_value');
removeTempDirectory($rootPath);
});
it('provides meaningful error messages', function () {
$rootPath = $this->createTestRootDirectory();
Bootstrap::init($rootPath);
try {
app('definitely.missing.service');
fail('Expected exception to be thrown');
} catch (RuntimeException $e) {
expect($e->getMessage())->toContain('definitely.missing.service');
expect($e->getMessage())->toContain('not found in container');
}
removeTempDirectory($rootPath);
});
it('supports performance measurement', function () {
$rootPath = $this->createTestRootDirectory();
$time = measureExecutionTime(function () use ($rootPath) {
Bootstrap::init($rootPath);
});
// Container initialization should be reasonably fast
expect($time)->toBeLessThan(1.0); // Should take less than 1 second
removeTempDirectory($rootPath);
});
});

View file

@ -1,95 +0,0 @@
<?php
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "pest()" function to bind a different classes or traits.
|
*/
pest()->extend(Tests\TestCase::class)->in('Feature');
pest()->extend(Tests\TestCase::class)->in('Unit');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
/**
* Performance Testing Helpers
*/
function measureExecutionTime(callable $callback): float
{
$start = microtime(true);
$callback();
return microtime(true) - $start;
}
function expectExecutionTimeUnder(callable $callback, float $maxSeconds): void
{
$time = measureExecutionTime($callback);
expect($time)->toBeLessThan($maxSeconds, "Execution took {$time}s, expected under {$maxSeconds}s");
}
/**
* File System Helpers
*/
function createTempDirectory(): string
{
$tempDir = sys_get_temp_dir() . '/torrentpier_test_' . uniqid();
mkdir($tempDir, 0755, true);
return $tempDir;
}
function removeTempDirectory(string $dir): void
{
if (is_dir($dir)) {
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
is_dir($path) ? removeTempDirectory($path) : unlink($path);
}
rmdir($dir);
}
}
/**
* Exception Testing Helpers
*/
function expectException(callable $callback, string $exceptionClass, ?string $message = null): void
{
try {
$callback();
fail("Expected exception $exceptionClass was not thrown");
} catch (Throwable $e) {
expect($e)->toBeInstanceOf($exceptionClass);
if ($message) {
expect($e->getMessage())->toContain($message);
}
}
}

View file

@ -1,433 +0,0 @@
# 🧪 TorrentPier 3.0 Testing Infrastructure
This document outlines the testing infrastructure for TorrentPier 3.0, built using **Pest PHP** and following the hexagonal architecture principles outlined in the project specification.
## 📖 Table of Contents
- [Overview](#overview)
- [Hexagonal Architecture Testing](#hexagonal-architecture-testing)
- [Test Organization](#test-organization)
- [DI Container Testing](#di-container-testing)
- [Testing Patterns](#testing-patterns)
- [Test Execution](#test-execution)
- [Best Practices](#best-practices)
## 🎯 Overview
TorrentPier 3.0's testing suite is designed following the hexagonal architecture testing strategy:
- **Domain**: Pure unit tests, no mocks needed
- **Application**: Unit tests with mocked repositories
- **Infrastructure**: Integration tests with real services
- **Presentation**: E2E tests for user journeys
### Core Testing Principles
1. **Architecture-Driven**: Tests follow the hexagonal architecture layers
2. **Phase-Aligned**: Testing matches the 5-phase implementation strategy
3. **Clean Slate**: No legacy dependencies, modern PHP 8.3+ testing
4. **Infrastructure First**: Focus on foundational DI container testing
5. **Future-Ready**: Structure prepared for upcoming domain/application layers
## 🏗️ Hexagonal Architecture Testing
### Testing Strategy by Layer
#### Domain Layer Testing (Phase 2 - Future)
```php
// Pure unit tests, no framework dependencies
it('validates business rules without external dependencies', function () {
$user = new User(new UserId(1), new Email('test@example.com'));
expect($user->canPost())->toBeTrue();
});
```
#### Application Layer Testing (Phase 3 - Future)
```php
// Unit tests with mocked repositories
it('handles user registration command', function () {
$mockRepo = Mockery::mock(UserRepositoryInterface::class);
$handler = new RegisterUserHandler($mockRepo);
$command = new RegisterUserCommand('john', 'john@example.com');
$handler->handle($command);
$mockRepo->shouldHaveReceived('save');
});
```
#### Infrastructure Layer Testing (Phase 1 - Current)
```php
// Integration tests with real services
it('creates container with real PHP-DI integration', function () {
$container = ContainerFactory::create();
expect($container)->toBeInstanceOf(Container::class);
});
```
#### Presentation Layer Testing (Phase 5 - Future)
```php
// E2E tests for user journeys
it('handles API request end-to-end', function () {
$response = $this->post('/api/users', ['name' => 'John']);
expect($response->status())->toBe(201);
});
```
## 📁 Test Organization
### Directory Structure
```
tests/
├── README.md # This documentation
├── Pest.php # Clean Pest configuration
├── TestCase.php # Enhanced base test case with DI utilities
├── Unit/Infrastructure/DependencyInjection/ # DI Container tests (Phase 1)
│ ├── ContainerTest.php # Container wrapper tests
│ ├── ContainerFactoryTest.php # Factory functionality tests
│ ├── BootstrapTest.php # Application bootstrapping tests
│ ├── ServiceProviderTest.php # Service provider interface tests
│ └── Definitions/ # Layer-specific definition tests
│ ├── DomainDefinitionsTest.php
│ ├── ApplicationDefinitionsTest.php
│ ├── InfrastructureDefinitionsTest.php
│ └── PresentationDefinitionsTest.php
└── Feature/ # Integration tests
└── ContainerIntegrationTest.php # End-to-end container tests
```
### Future Structure (As Phases Are Implemented)
```
tests/
├── Unit/
│ ├── Domain/ # Phase 2: Pure business logic tests
│ │ ├── User/
│ │ ├── Forum/
│ │ └── Tracker/
│ ├── Application/ # Phase 3: Use case orchestration tests
│ │ ├── User/
│ │ ├── Forum/
│ │ └── Tracker/
│ ├── Infrastructure/ # Phase 4: External service integration tests
│ │ ├── Persistence/
│ │ ├── Cache/
│ │ └── Email/
│ └── Presentation/ # Phase 5: Interface layer tests
│ ├── Http/
│ └── Cli/
└── Feature/ # Cross-layer integration tests
```
## 🛠️ DI Container Testing
### Current Implementation (Phase 1)
The DI container is the foundation of TorrentPier 3.0's architecture. Our tests ensure:
#### Container Wrapper Testing
```php
// tests/Unit/Infrastructure/DependencyInjection/ContainerTest.php
it('implements PSR-11 ContainerInterface', function () {
expect($this->container)->toBeInstanceOf(\Psr\Container\ContainerInterface::class);
});
it('can resolve autowired classes', function () {
$result = $this->container->get(stdClass::class);
expect($result)->toBeInstanceOf(stdClass::class);
});
it('throws NotFoundExceptionInterface for non-existent services', function () {
expect(fn() => $this->container->get('non.existent.service'))
->toThrow(NotFoundExceptionInterface::class);
});
```
#### Factory Configuration Testing
```php
// tests/Unit/Infrastructure/DependencyInjection/ContainerFactoryTest.php
it('applies configuration correctly', function () {
$config = [
'environment' => 'testing',
'autowiring' => true,
'definitions' => [
'test.service' => \DI\factory(fn() => 'test_value'),
],
];
$container = ContainerFactory::create($config);
expect($container->get('test.service'))->toBe('test_value');
});
```
#### Bootstrap Integration Testing
```php
// tests/Unit/Infrastructure/DependencyInjection/BootstrapTest.php
it('loads configuration from multiple sources', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'env' => ['APP_ENV' => 'testing'],
'services' => ['config.service' => \DI\factory(fn() => 'merged_config')],
]);
$container = Bootstrap::init($rootPath);
expect($container->get('config.service'))->toBe('merged_config');
});
```
### Test Utilities
#### Enhanced TestCase
```php
// tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
protected function createTestContainer(array $config = []): Container
{
$defaultConfig = [
'environment' => 'testing',
'autowiring' => true,
'definitions' => [],
];
return ContainerFactory::create(array_merge($defaultConfig, $config));
}
protected function assertCanResolve(Container $container, string $serviceId): void
{
$this->assertTrue($container->has($serviceId));
$this->assertNotNull($container->get($serviceId));
}
}
```
## 🎨 Testing Patterns
### 1. Infrastructure Integration Testing
```php
// Real service integration (current phase)
it('integrates with real PHP-DI container', function () {
$container = $this->createTestContainer([
'definitions' => [
'real.service' => \DI\autowire(stdClass::class),
],
]);
$service = $container->get('real.service');
expect($service)->toBeInstanceOf(stdClass::class);
});
```
### 2. Configuration-Driven Testing
```php
// Environment-based configuration
it('adapts to different environments', function () {
$prodContainer = $this->createTestContainer(['environment' => 'production']);
$devContainer = $this->createTestContainer(['environment' => 'development']);
expect($prodContainer)->toBeInstanceOf(Container::class);
expect($devContainer)->toBeInstanceOf(Container::class);
});
```
### 3. Service Provider Testing
```php
// Modular service registration
it('registers services through providers', function () {
$provider = new class implements ServiceProvider {
public function register(Container $container): void {
$container->getWrappedContainer()->set('provider.service', 'registered');
}
public function boot(Container $container): void {}
};
$container = $this->createTestContainer();
$provider->register($container);
expect($container->get('provider.service'))->toBe('registered');
});
```
### 4. Layer Definition Testing
```php
// Architectural layer compliance
it('follows domain layer principles', function () {
$definitions = DomainDefinitions::getDefinitions();
// Domain definitions should be empty in Phase 1
expect($definitions)->toBe([]);
// Structure should be prepared for Phase 2
expect($definitions)->toBeArray();
});
```
## 🚀 Test Execution
### Running Tests
```bash
# Run all tests
./vendor/bin/pest
# Run DI container tests specifically
./vendor/bin/pest tests/Unit/Infrastructure/DependencyInjection/
# Run integration tests
./vendor/bin/pest tests/Feature/
# Run with coverage
./vendor/bin/pest --coverage
# Run specific test file
./vendor/bin/pest tests/Unit/Infrastructure/DependencyInjection/ContainerTest.php
```
### Performance Testing
```bash
# Measure container bootstrap performance
./vendor/bin/pest --filter="performance"
# Container creation should be fast
expectExecutionTimeUnder(fn() => Bootstrap::init($rootPath), 1.0);
```
## 📋 Best Practices
### 1. Phase-Aligned Testing
```php
// Current Phase 1: Test infrastructure only
it('provides foundation for future phases', function () {
$container = $this->createTestContainer();
// Infrastructure works now
expect($container)->toBeInstanceOf(Container::class);
// Ready for future domain services
expect($container->has(stdClass::class))->toBeTrue();
});
```
### 2. Architecture Compliance
```php
// Ensure clean architectural boundaries
it('keeps domain layer pure', function () {
$definitions = DomainDefinitions::getDefinitions();
// Domain should have no infrastructure dependencies
expect($definitions)->toBeArray();
// Future domain services will be dependency-free
});
```
### 3. Configuration Testing
```php
// Test multiple configuration sources
it('merges configuration correctly', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'container' => ['autowiring' => true],
'services' => ['test.service' => \DI\factory(fn() => 'test')],
]);
$container = Bootstrap::init($rootPath, [
'definitions' => ['runtime.service' => \DI\factory(fn() => 'runtime')],
]);
expect($container->get('test.service'))->toBe('test');
expect($container->get('runtime.service'))->toBe('runtime');
});
```
### 4. Error Handling
```php
// Comprehensive error testing
it('provides meaningful error messages', function () {
$container = $this->createTestContainer();
try {
$container->get('missing.service');
fail('Expected exception');
} catch (RuntimeException $e) {
expect($e->getMessage())->toContain('missing.service');
expect($e->getMessage())->toContain('not found in container');
}
});
```
## 📊 Current Implementation Status
### ✅ Phase 1 Complete: Infrastructure Foundation
- **DI Container**: Fully tested container wrapper with PSR-11 compliance
- **Factory Pattern**: Comprehensive configuration and creation testing
- **Bootstrap Process**: Environment loading and configuration merging
- **Service Providers**: Modular service registration interface
- **Helper Functions**: Global container access with proper error handling
- **Layer Definitions**: Prepared structure for all architectural layers
### 🔄 Testing Coverage
- **Container Core**: 100% coverage of wrapper functionality
- **Configuration**: All config sources and merging scenarios tested
- **Error Handling**: Complete PSR-11 exception compliance
- **Integration**: End-to-end bootstrap and usage scenarios
- **Performance**: Container creation and resolution timing validation
### 🔮 Future Phase Testing
As TorrentPier 3.0 phases are implemented:
#### Phase 2: Domain Layer
```php
// Domain entity testing (future)
it('validates user business rules', function () {
$user = new User(UserId::generate(), new Email('test@example.com'));
expect($user->isActive())->toBeTrue();
});
```
#### Phase 3: Application Layer
```php
// Command handler testing (future)
it('processes registration command', function () {
$handler = app(RegisterUserHandler::class);
$command = new RegisterUserCommand('john', 'john@example.com');
$userId = $handler->handle($command);
expect($userId)->toBeInstanceOf(UserId::class);
});
```
#### Phase 4: Infrastructure Layer
```php
// Repository integration testing (future)
it('persists user through repository', function () {
$repository = app(UserRepositoryInterface::class);
$user = User::create('john', 'john@example.com');
$repository->save($user);
expect($repository->findById($user->getId()))->not->toBeNull();
});
```
#### Phase 5: Presentation Layer
```php
// Controller integration testing (future)
it('handles user registration via API', function () {
$response = $this->postJson('/api/users', [
'username' => 'john',
'email' => 'john@example.com',
]);
expect($response->status())->toBe(201);
});
```
---
**TorrentPier 3.0 Testing Philosophy**: Tests serve as both validation and documentation of the hexagonal architecture. Each layer has distinct testing strategies that ensure clean separation of concerns and maintainable code.
For questions about testing patterns or contributions, refer to the [TorrentPier GitHub repository](https://github.com/torrentpier/torrentpier) or the hexagonal architecture specification at `/docs/specs/hexagonal-architecture-spec.md`.

View file

@ -1,120 +0,0 @@
<?php
namespace Tests;
use PHPUnit\Framework\TestCase as BaseTestCase;
use TorrentPier\Infrastructure\DependencyInjection\Bootstrap;
use TorrentPier\Infrastructure\DependencyInjection\Container;
use TorrentPier\Infrastructure\DependencyInjection\ContainerFactory;
abstract class TestCase extends BaseTestCase
{
protected function setUp(): void
{
parent::setUp();
// Reset container state for each test
Bootstrap::reset();
}
protected function tearDown(): void
{
// Clean up container state
Bootstrap::reset();
parent::tearDown();
}
/**
* Create a test container with optional custom configuration
*/
protected function createTestContainer(array $config = []): Container
{
$defaultConfig = [
'environment' => 'testing',
'autowiring' => true,
'definitions' => [],
];
return ContainerFactory::create(array_merge($defaultConfig, $config));
}
/**
* Create a container with custom service definitions
*/
protected function createContainerWithDefinitions(array $definitions): Container
{
return $this->createTestContainer([
'definitions' => $definitions,
]);
}
/**
* Create a temporary test root directory
*/
protected function createTestRootDirectory(): string
{
$tempDir = createTempDirectory();
// Create basic directory structure
mkdir($tempDir . '/config', 0755, true);
mkdir($tempDir . '/internal_data/cache', 0755, true);
return $tempDir;
}
/**
* Create test configuration files
*/
protected function createTestConfigFiles(string $rootPath, array $configs = []): void
{
$configPath = $rootPath . '/config';
// Create container.php
if (isset($configs['container'])) {
file_put_contents(
$configPath . '/container.php',
'<?php return ' . var_export($configs['container'], true) . ';'
);
}
// Create services.php - simplified approach for testing
if (isset($configs['services'])) {
$servicesContent = "<?php\n\nuse function DI\\factory;\n\nreturn [\n";
foreach ($configs['services'] as $key => $value) {
if (is_string($value)) {
$servicesContent .= " '$key' => factory(function () { return '$value'; }),\n";
}
}
$servicesContent .= "];\n";
file_put_contents($configPath . '/services.php', $servicesContent);
}
// Create .env file
if (isset($configs['env'])) {
$envContent = '';
foreach ($configs['env'] as $key => $value) {
$envContent .= "$key=$value\n";
}
file_put_contents($rootPath . '/.env', $envContent);
}
}
/**
* Assert that a service can be resolved from the container
*/
protected function assertCanResolve(Container $container, string $serviceId): void
{
$this->assertTrue($container->has($serviceId), "Container should have service: $serviceId");
$this->assertNotNull($container->get($serviceId), "Should be able to resolve service: $serviceId");
}
/**
* Assert that a service cannot be resolved from the container
*/
protected function assertCannotResolve(Container $container, string $serviceId): void
{
$this->assertFalse($container->has($serviceId), "Container should not have service: $serviceId");
}
}

View file

@ -1,190 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Bootstrap;
use TorrentPier\Infrastructure\DependencyInjection\Container;
describe('Bootstrap', function () {
beforeEach(function () {
// Ensure clean state for each test
Bootstrap::reset();
});
afterEach(function () {
Bootstrap::reset();
});
describe('init() method', function () {
it('initializes and returns a container', function () {
$rootPath = $this->createTestRootDirectory();
$container = Bootstrap::init($rootPath);
expect($container)->toBeInstanceOf(Container::class);
removeTempDirectory($rootPath);
});
it('returns the same container on subsequent calls', function () {
$rootPath = $this->createTestRootDirectory();
$container1 = Bootstrap::init($rootPath);
$container2 = Bootstrap::init($rootPath);
expect($container1)->toBe($container2);
removeTempDirectory($rootPath);
});
it('registers container instance with itself', function () {
$rootPath = $this->createTestRootDirectory();
$container = Bootstrap::init($rootPath);
expect($container->get(Container::class))->toBe($container);
expect($container->get('container'))->toBe($container);
removeTempDirectory($rootPath);
});
it('loads environment variables from .env file', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'env' => [
'TEST_VAR' => 'test_value',
'APP_ENV' => 'testing',
],
]);
Bootstrap::init($rootPath);
expect($_ENV['TEST_VAR'] ?? null)->toBe('test_value');
expect($_ENV['APP_ENV'] ?? null)->toBe('testing');
removeTempDirectory($rootPath);
});
it('loads configuration from config files', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'container' => [
'environment' => 'testing',
'autowiring' => true,
],
'services' => [
'test.service' => 'config_value',
],
]);
$container = Bootstrap::init($rootPath);
expect($container->get('test.service'))->toBe('config_value');
removeTempDirectory($rootPath);
});
it('handles missing config files gracefully', function () {
$rootPath = $this->createTestRootDirectory();
// Should not throw exception even without config files
$container = Bootstrap::init($rootPath);
expect($container)->toBeInstanceOf(Container::class);
removeTempDirectory($rootPath);
});
});
describe('getContainer() method', function () {
it('returns null when not initialized', function () {
expect(Bootstrap::getContainer())->toBeNull();
});
it('returns container after initialization', function () {
$rootPath = $this->createTestRootDirectory();
$container = Bootstrap::init($rootPath);
expect(Bootstrap::getContainer())->toBe($container);
removeTempDirectory($rootPath);
});
});
describe('reset() method', function () {
it('clears the container instance', function () {
$rootPath = $this->createTestRootDirectory();
Bootstrap::init($rootPath);
expect(Bootstrap::getContainer())->not->toBeNull();
Bootstrap::reset();
expect(Bootstrap::getContainer())->toBeNull();
removeTempDirectory($rootPath);
});
it('allows re-initialization after reset', function () {
$rootPath = $this->createTestRootDirectory();
$container1 = Bootstrap::init($rootPath);
Bootstrap::reset();
$container2 = Bootstrap::init($rootPath);
expect($container1)->not->toBe($container2);
expect($container2)->toBeInstanceOf(Container::class);
removeTempDirectory($rootPath);
});
});
describe('configuration loading', function () {
it('merges configuration from multiple sources', function () {
$rootPath = $this->createTestRootDirectory();
$this->createTestConfigFiles($rootPath, [
'env' => [
'APP_ENV' => 'production',
'APP_DEBUG' => 'false',
],
'container' => [
'autowiring' => true,
],
'services' => [
'config.service' => 'merged_config',
],
]);
$container = Bootstrap::init($rootPath, [
'definitions' => [
'runtime.service' => \DI\factory(function () {
return 'runtime_config';
}),
],
]);
expect($container->get('config.service'))->toBe('merged_config');
expect($container->get('runtime.service'))->toBe('runtime_config');
removeTempDirectory($rootPath);
});
it('sets default environment when no .env file exists', function () {
$rootPath = $this->createTestRootDirectory();
$container = Bootstrap::init($rootPath);
// Container should still be created successfully
expect($container)->toBeInstanceOf(Container::class);
removeTempDirectory($rootPath);
});
});
describe('error handling', function () {
it('handles invalid root path gracefully', function () {
// Should not throw fatal error for non-existent path
expect(function () {
Bootstrap::init('/non/existent/path');
})->not->toThrow(Throwable::class);
});
});
});

View file

@ -1,206 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Container;
use TorrentPier\Infrastructure\DependencyInjection\ContainerFactory;
use TorrentPier\Infrastructure\DependencyInjection\ServiceProvider;
describe('ContainerFactory', function () {
describe('create() method', function () {
it('creates a container instance', function () {
$container = ContainerFactory::create();
expect($container)->toBeInstanceOf(Container::class);
});
it('applies configuration correctly', function () {
$config = [
'environment' => 'testing',
'autowiring' => true,
'annotations' => false,
];
$container = ContainerFactory::create($config);
expect($container)->toBeInstanceOf(Container::class);
});
it('loads custom definitions', function () {
$config = [
'definitions' => [
'test.service' => \DI\factory(function () {
return 'test_value';
}),
],
];
$container = ContainerFactory::create($config);
expect($container->get('test.service'))->toBe('test_value');
});
it('configures autowiring when enabled', function () {
$config = ['autowiring' => true];
$container = ContainerFactory::create($config);
// Should be able to autowire stdClass
expect($container->has(stdClass::class))->toBeTrue();
});
it('loads definition files when specified', function () {
$tempDir = createTempDirectory();
$definitionFile = $tempDir . '/definitions.php';
file_put_contents($definitionFile, '<?php return [
"file.service" => \DI\factory(function () {
return "from_file";
}),
];');
$config = [
'definition_files' => [$definitionFile],
];
$container = ContainerFactory::create($config);
expect($container->get('file.service'))->toBe('from_file');
removeTempDirectory($tempDir);
});
it('handles non-existent definition files gracefully', function () {
$config = [
'definition_files' => ['/non/existent/file.php'],
];
// Should not throw an exception
$container = ContainerFactory::create($config);
expect($container)->toBeInstanceOf(Container::class);
});
});
describe('service providers', function () {
it('registers and boots service providers', function () {
$providerClass = new class implements ServiceProvider {
public static bool $registered = false;
public static bool $booted = false;
public function register(Container $container): void
{
self::$registered = true;
$container->getWrappedContainer()->set('provider.service', 'provider_value');
}
public function boot(Container $container): void
{
self::$booted = true;
}
};
$config = [
'providers' => [get_class($providerClass)],
];
$container = ContainerFactory::create($config);
expect($providerClass::$registered)->toBeTrue();
expect($providerClass::$booted)->toBeTrue();
expect($container->get('provider.service'))->toBe('provider_value');
});
it('handles invalid provider classes gracefully', function () {
$config = [
'providers' => ['NonExistentProvider'],
];
// Should not throw an exception
$container = ContainerFactory::create($config);
expect($container)->toBeInstanceOf(Container::class);
});
it('boots providers after all registrations', function () {
// Use a simpler approach without constructor dependencies
$testFile = sys_get_temp_dir() . '/provider_order_test.txt';
if (file_exists($testFile)) {
unlink($testFile);
}
$provider1Class = new class implements ServiceProvider {
public function register(Container $container): void
{
$testFile = sys_get_temp_dir() . '/provider_order_test.txt';
file_put_contents($testFile, "register1\n", FILE_APPEND);
}
public function boot(Container $container): void
{
$testFile = sys_get_temp_dir() . '/provider_order_test.txt';
file_put_contents($testFile, "boot1\n", FILE_APPEND);
}
};
$provider2Class = new class implements ServiceProvider {
public function register(Container $container): void
{
$testFile = sys_get_temp_dir() . '/provider_order_test.txt';
file_put_contents($testFile, "register2\n", FILE_APPEND);
}
public function boot(Container $container): void
{
$testFile = sys_get_temp_dir() . '/provider_order_test.txt';
file_put_contents($testFile, "boot2\n", FILE_APPEND);
}
};
$config = [
'providers' => [get_class($provider1Class), get_class($provider2Class)],
];
ContainerFactory::create($config);
// Read the order from the test file
$content = file_get_contents($testFile);
$lines = array_filter(explode("\n", trim($content)));
// All registrations should happen before any boots
expect($lines)->toBe(['register1', 'register2', 'boot1', 'boot2']);
// Clean up
unlink($testFile);
});
});
describe('environment configuration', function () {
it('enables compilation in production', function () {
$tempDir = createTempDirectory();
$config = [
'environment' => 'production',
'compilation_dir' => $tempDir . '/container',
'proxies_dir' => $tempDir . '/proxies',
];
$container = ContainerFactory::create($config);
expect($container)->toBeInstanceOf(Container::class);
removeTempDirectory($tempDir);
});
it('skips compilation in development', function () {
$config = [
'environment' => 'development',
];
$container = ContainerFactory::create($config);
expect($container)->toBeInstanceOf(Container::class);
});
});
describe('layer definitions integration', function () {
it('loads definitions from all architectural layers', function () {
$container = ContainerFactory::create();
// Container should be created successfully with all layer definitions
expect($container)->toBeInstanceOf(Container::class);
// Since most definitions are commented out, we just verify the container works
expect($container->has(stdClass::class))->toBeTrue();
});
});
});

View file

@ -1,165 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Container;
use TorrentPier\Infrastructure\DependencyInjection\ContainerFactory;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Container\ContainerExceptionInterface;
describe('Container', function () {
beforeEach(function () {
$this->container = $this->createTestContainer();
});
it('implements PSR-11 ContainerInterface', function () {
expect($this->container)->toBeInstanceOf(\Psr\Container\ContainerInterface::class);
});
describe('get() method', function () {
it('can resolve a simple service', function () {
$container = $this->createContainerWithDefinitions([
'test.service' => \DI\factory(function () {
return 'test_value';
}),
]);
$result = $container->get('test.service');
expect($result)->toBe('test_value');
});
it('can resolve autowired classes', function () {
$container = $this->createContainerWithDefinitions([
'test.class' => \DI\autowire(stdClass::class),
]);
$result = $container->get('test.class');
expect($result)->toBeInstanceOf(stdClass::class);
});
it('throws NotFoundExceptionInterface for non-existent services', function () {
expectException(
fn() => $this->container->get('non.existent.service'),
NotFoundExceptionInterface::class,
'non.existent.service'
);
});
it('returns same instance for singleton services', function () {
$container = $this->createContainerWithDefinitions([
'singleton.service' => \DI\factory(function () {
return new stdClass();
}),
]);
$instance1 = $container->get('singleton.service');
$instance2 = $container->get('singleton.service');
expect($instance1)->toBe($instance2);
});
});
describe('has() method', function () {
it('returns true for existing services', function () {
$container = $this->createContainerWithDefinitions([
'existing.service' => \DI\factory(function () {
return 'value';
}),
]);
expect($container->has('existing.service'))->toBeTrue();
});
it('returns false for non-existent services', function () {
expect($this->container->has('non.existent.service'))->toBeFalse();
});
it('returns true for autowirable classes', function () {
expect($this->container->has(stdClass::class))->toBeTrue();
});
});
describe('make() method', function () {
it('can make instances with parameters', function () {
$result = $this->container->make(stdClass::class);
expect($result)->toBeInstanceOf(stdClass::class);
});
it('creates new instances each time', function () {
$instance1 = $this->container->make(stdClass::class);
$instance2 = $this->container->make(stdClass::class);
expect($instance1)->not->toBe($instance2);
});
});
describe('call() method', function () {
it('can call closures with dependency injection', function () {
$result = $this->container->call(function (stdClass $class) {
return get_class($class);
});
expect($result)->toBe('stdClass');
});
it('can call methods with parameters', function () {
$service = new class {
public function test(string $param): string
{
return "Hello $param";
}
};
$result = $this->container->call([$service, 'test'], ['param' => 'World']);
expect($result)->toBe('Hello World');
});
});
describe('injectOn() method', function () {
it('returns the object after injection', function () {
$object = new stdClass();
$result = $this->container->injectOn($object);
expect($result)->toBe($object);
});
});
describe('getWrappedContainer() method', function () {
it('returns the underlying PHP-DI container', function () {
$wrapped = $this->container->getWrappedContainer();
expect($wrapped)->toBeInstanceOf(\DI\Container::class);
});
it('allows direct access to PHP-DI functionality', function () {
$wrapped = $this->container->getWrappedContainer();
$wrapped->set('direct.service', 'direct_value');
expect($this->container->get('direct.service'))->toBe('direct_value');
});
});
describe('error handling', function () {
it('provides meaningful error messages for missing services', function () {
expectException(
fn() => $this->container->get('missing.service'),
NotFoundExceptionInterface::class,
'missing.service'
);
});
it('handles circular dependencies gracefully', function () {
$container = $this->createContainerWithDefinitions([
'service.a' => \DI\factory(function (\Psr\Container\ContainerInterface $c) {
return $c->get('service.b');
}),
'service.b' => \DI\factory(function (\Psr\Container\ContainerInterface $c) {
return $c->get('service.a');
}),
]);
expectException(
fn() => $container->get('service.a'),
ContainerExceptionInterface::class,
'Circular dependency'
);
});
});
});

View file

@ -1,100 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Definitions\ApplicationDefinitions;
describe('ApplicationDefinitions', function () {
describe('getDefinitions() method', function () {
it('returns an array', function () {
$definitions = ApplicationDefinitions::getDefinitions();
expect($definitions)->toBeArray();
});
it('returns empty array when no application services are implemented yet', function () {
$definitions = ApplicationDefinitions::getDefinitions();
// Since we're in Phase 1 and application services aren't implemented yet,
// the definitions should be empty (all examples are commented out)
expect($definitions)->toBe([]);
});
it('follows application layer principles', function () {
// Application layer should orchestrate domain objects
// This test verifies the structure is ready for future application services
$definitions = ApplicationDefinitions::getDefinitions();
// Should be an array (even if empty)
expect($definitions)->toBeArray();
// When application services are added, they should follow these principles:
// - Command and Query handlers
// - Application services that orchestrate domain logic
// - Event dispatchers
// - No direct infrastructure concerns
});
it('can be safely called multiple times', function () {
$definitions1 = ApplicationDefinitions::getDefinitions();
$definitions2 = ApplicationDefinitions::getDefinitions();
expect($definitions1)->toBe($definitions2);
});
it('is prepared for future command/query handlers', function () {
// This test documents the intended structure for Phase 3 implementation
$definitions = ApplicationDefinitions::getDefinitions();
expect($definitions)->toBeArray();
// Future command/query handlers will be registered like:
// 'TorrentPier\Application\User\Handler\RegisterUserHandler' => DI\autowire(),
// 'CommandBusInterface' => DI\factory(function (ContainerInterface $c) {
// return new CommandBus($c);
// }),
// For now, verify the method works without breaking
expect(count($definitions))->toBeGreaterThanOrEqual(0);
});
});
describe('architectural compliance', function () {
it('follows hexagonal architecture principles', function () {
// Application layer should orchestrate domain objects without infrastructure concerns
$definitions = ApplicationDefinitions::getDefinitions();
// Application definitions should focus on:
// 1. Command and Query handlers
// 2. Application services
// 3. Event dispatchers
// 4. Use case orchestration
expect($definitions)->toBeArray();
});
it('supports CQRS pattern', function () {
// Application layer should separate commands and queries
// This test ensures the structure supports CQRS implementation
$definitions = ApplicationDefinitions::getDefinitions();
// Future implementation will separate:
// - Command handlers (write operations)
// - Query handlers (read operations)
// - Command and Query buses
expect($definitions)->toBeArray();
});
it('prepares for event-driven architecture', function () {
// Application layer should support domain events
$definitions = ApplicationDefinitions::getDefinitions();
// Future event dispatcher will be registered here
// 'EventDispatcherInterface' => DI\factory(...)
expect($definitions)->toBeArray();
});
});
});

View file

@ -1,84 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Definitions\DomainDefinitions;
describe('DomainDefinitions', function () {
describe('getDefinitions() method', function () {
it('returns an array', function () {
$definitions = DomainDefinitions::getDefinitions();
expect($definitions)->toBeArray();
});
it('returns empty array when no domain services are implemented yet', function () {
$definitions = DomainDefinitions::getDefinitions();
// Since we're in Phase 1 and domain services aren't implemented yet,
// the definitions should be empty (all examples are commented out)
expect($definitions)->toBe([]);
});
it('follows domain layer principles', function () {
// Domain definitions should not contain infrastructure dependencies
// This test verifies the structure is ready for future domain services
$definitions = DomainDefinitions::getDefinitions();
// Should be an array (even if empty)
expect($definitions)->toBeArray();
// When domain services are added, they should follow these principles:
// - No framework dependencies
// - Repository interfaces mapped to implementations
// - Pure business logic services
});
it('can be safely called multiple times', function () {
$definitions1 = DomainDefinitions::getDefinitions();
$definitions2 = DomainDefinitions::getDefinitions();
expect($definitions1)->toBe($definitions2);
});
it('is prepared for future repository interface mappings', function () {
// This test documents the intended structure for Phase 2 implementation
$definitions = DomainDefinitions::getDefinitions();
expect($definitions)->toBeArray();
// Future repository interfaces will be mapped like:
// 'TorrentPier\Domain\User\Repository\UserRepositoryInterface' =>
// DI\factory(function (ContainerInterface $c) {
// return $c->get('TorrentPier\Infrastructure\Persistence\Repository\UserRepository');
// }),
// For now, verify the method works without breaking
expect(count($definitions))->toBeGreaterThanOrEqual(0);
});
});
describe('architectural compliance', function () {
it('follows hexagonal architecture principles', function () {
// Domain layer should have no infrastructure dependencies
// This test ensures the definition structure is correct
$definitions = DomainDefinitions::getDefinitions();
// Domain definitions should focus on:
// 1. Repository interface mappings
// 2. Domain service factories
// 3. No framework dependencies
expect($definitions)->toBeArray();
});
it('supports dependency injection inversion', function () {
// Domain interfaces should be mapped to infrastructure implementations
// following the dependency inversion principle
$definitions = DomainDefinitions::getDefinitions();
// Even though empty now, the structure supports proper DI mapping
expect($definitions)->toBeArray();
});
});
});

View file

@ -1,159 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Definitions\InfrastructureDefinitions;
describe('InfrastructureDefinitions', function () {
describe('getDefinitions() method', function () {
it('returns an array', function () {
$definitions = InfrastructureDefinitions::getDefinitions();
expect($definitions)->toBeArray();
});
it('accepts configuration parameter', function () {
$config = ['test' => 'value'];
$definitions = InfrastructureDefinitions::getDefinitions($config);
expect($definitions)->toBeArray();
});
it('returns empty array when no infrastructure services are implemented yet', function () {
$definitions = InfrastructureDefinitions::getDefinitions();
// Since we're in Phase 1 and infrastructure services aren't implemented yet,
// the definitions should be empty (all examples are commented out)
expect($definitions)->toBe([]);
});
it('follows infrastructure layer principles', function () {
// Infrastructure layer should handle external concerns
// This test verifies the structure is ready for future infrastructure services
$definitions = InfrastructureDefinitions::getDefinitions();
// Should be an array (even if empty)
expect($definitions)->toBeArray();
// When infrastructure services are added, they should follow these principles:
// - Database connections and repositories
// - Cache implementations
// - External service adapters
// - File storage systems
});
it('can be safely called multiple times', function () {
$definitions1 = InfrastructureDefinitions::getDefinitions();
$definitions2 = InfrastructureDefinitions::getDefinitions();
expect($definitions1)->toBe($definitions2);
});
it('can handle different configurations', function () {
$config1 = ['database' => ['host' => 'localhost']];
$config2 = ['cache' => ['driver' => 'redis']];
$definitions1 = InfrastructureDefinitions::getDefinitions($config1);
$definitions2 = InfrastructureDefinitions::getDefinitions($config2);
// Should handle different configs without breaking
expect($definitions1)->toBeArray();
expect($definitions2)->toBeArray();
});
it('is prepared for future database services', function () {
// This test documents the intended structure for Phase 4 implementation
$definitions = InfrastructureDefinitions::getDefinitions([
'database' => [
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'tp',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
],
]);
expect($definitions)->toBeArray();
// Future database services will be registered like:
// 'database.connection.default' => DI\factory(function () use ($config) { ... }),
// Connection::class => DI\get('database.connection.default'),
// For now, verify the method works without breaking
expect(count($definitions))->toBeGreaterThanOrEqual(0);
});
it('is prepared for future cache services', function () {
$definitions = InfrastructureDefinitions::getDefinitions([
'cache' => [
'driver' => 'file',
'file' => ['path' => '/tmp/cache'],
],
]);
expect($definitions)->toBeArray();
// Future cache services will be registered like:
// 'cache.storage' => DI\factory(function () use ($config) { ... }),
// 'cache.factory' => DI\factory(function (ContainerInterface $c) { ... }),
});
});
describe('architectural compliance', function () {
it('follows hexagonal architecture principles', function () {
// Infrastructure layer should handle external concerns and adapters
$definitions = InfrastructureDefinitions::getDefinitions();
// Infrastructure definitions should focus on:
// 1. Database connections and persistence
// 2. Cache implementations
// 3. External service adapters
// 4. File storage systems
// 5. Third-party integrations
expect($definitions)->toBeArray();
});
it('supports dependency inversion', function () {
// Infrastructure should implement domain interfaces
$definitions = InfrastructureDefinitions::getDefinitions();
// Future repository implementations will be registered here:
// 'TorrentPier\Infrastructure\Persistence\Repository\UserRepository' => DI\autowire()
// ->constructorParameter('connection', DI\get('database.connection.default'))
expect($definitions)->toBeArray();
});
it('handles configuration-based service creation', function () {
// Infrastructure services should be configurable
$config = [
'database' => ['driver' => 'mysql'],
'cache' => ['driver' => 'redis'],
'storage' => ['driver' => 's3'],
];
$definitions = InfrastructureDefinitions::getDefinitions($config);
// Should handle configuration without breaking
expect($definitions)->toBeArray();
});
it('prepares for multiple database connections', function () {
$config = [
'database' => [
'default' => 'mysql',
'connections' => [
'mysql' => ['driver' => 'mysql'],
'sqlite' => ['driver' => 'sqlite'],
],
],
];
$definitions = InfrastructureDefinitions::getDefinitions($config);
expect($definitions)->toBeArray();
});
});
});

View file

@ -1,147 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Definitions\PresentationDefinitions;
describe('PresentationDefinitions', function () {
describe('getDefinitions() method', function () {
it('returns an array', function () {
$definitions = PresentationDefinitions::getDefinitions();
expect($definitions)->toBeArray();
});
it('returns empty array when no presentation services are implemented yet', function () {
$definitions = PresentationDefinitions::getDefinitions();
// Since we're in Phase 1 and presentation services aren't implemented yet,
// the definitions should be empty (all examples are commented out)
expect($definitions)->toBe([]);
});
it('follows presentation layer principles', function () {
// Presentation layer should handle user interface concerns
// This test verifies the structure is ready for future presentation services
$definitions = PresentationDefinitions::getDefinitions();
// Should be an array (even if empty)
expect($definitions)->toBeArray();
// When presentation services are added, they should follow these principles:
// - HTTP controllers for web and API interfaces
// - CLI commands for console operations
// - Middleware for request/response processing
// - Response transformers for output formatting
});
it('can be safely called multiple times', function () {
$definitions1 = PresentationDefinitions::getDefinitions();
$definitions2 = PresentationDefinitions::getDefinitions();
expect($definitions1)->toBe($definitions2);
});
it('is prepared for future HTTP controllers', function () {
// This test documents the intended structure for Phase 5 implementation
$definitions = PresentationDefinitions::getDefinitions();
expect($definitions)->toBeArray();
// Future HTTP controllers will be registered like:
// 'TorrentPier\Presentation\Http\Controllers\Web\HomeController' => DI\autowire(),
// 'TorrentPier\Presentation\Http\Controllers\Api\UserController' => DI\autowire(),
// 'TorrentPier\Presentation\Http\Controllers\Admin\DashboardController' => DI\autowire(),
// For now, verify the method works without breaking
expect(count($definitions))->toBeGreaterThanOrEqual(0);
});
it('is prepared for future CLI commands', function () {
$definitions = PresentationDefinitions::getDefinitions();
expect($definitions)->toBeArray();
// Future CLI commands will be registered like:
// 'TorrentPier\Presentation\Cli\Commands\CacheCommand' => DI\autowire(),
// 'TorrentPier\Presentation\Cli\Commands\MigrateCommand' => DI\autowire(),
});
it('is prepared for future middleware', function () {
$definitions = PresentationDefinitions::getDefinitions();
expect($definitions)->toBeArray();
// Future middleware will be registered like:
// 'AuthenticationMiddleware' => DI\autowire('TorrentPier\Presentation\Http\Middleware\AuthenticationMiddleware'),
// 'CorsMiddleware' => DI\autowire('TorrentPier\Presentation\Http\Middleware\CorsMiddleware'),
});
});
describe('architectural compliance', function () {
it('follows hexagonal architecture principles', function () {
// Presentation layer should handle user interface and external interfaces
$definitions = PresentationDefinitions::getDefinitions();
// Presentation definitions should focus on:
// 1. HTTP controllers (Web, API, Admin)
// 2. CLI commands
// 3. Middleware for request processing
// 4. Response transformers
// 5. Input validation and output formatting
expect($definitions)->toBeArray();
});
it('supports multiple interface types', function () {
// Presentation layer should support web, API, and CLI interfaces
$definitions = PresentationDefinitions::getDefinitions();
// Future implementation will include:
// - Web controllers for HTML responses
// - API controllers for JSON responses
// - Admin controllers for administrative interface
// - CLI commands for console operations
expect($definitions)->toBeArray();
});
it('prepares for middleware stack', function () {
// Presentation layer should support request/response middleware
$definitions = PresentationDefinitions::getDefinitions();
// Future middleware will handle:
// - Authentication and authorization
// - CORS headers
// - Rate limiting
// - Request validation
// - Response transformation
expect($definitions)->toBeArray();
});
it('supports dependency injection for controllers', function () {
// Controllers should have their dependencies injected
$definitions = PresentationDefinitions::getDefinitions();
// Future controllers will be autowired with dependencies:
// - Application services (command/query handlers)
// - Request validators
// - Response transformers
expect($definitions)->toBeArray();
});
it('prepares for different response formats', function () {
// Presentation layer should support multiple response formats
$definitions = PresentationDefinitions::getDefinitions();
// Future response transformers:
// 'JsonResponseTransformer' => DI\autowire(...),
// 'HtmlResponseTransformer' => DI\autowire(...),
expect($definitions)->toBeArray();
});
});
});

View file

@ -1,144 +0,0 @@
<?php
use TorrentPier\Infrastructure\DependencyInjection\Container;
use TorrentPier\Infrastructure\DependencyInjection\ServiceProvider;
describe('ServiceProvider interface', function () {
it('defines required methods', function () {
$reflection = new ReflectionClass(ServiceProvider::class);
expect($reflection->isInterface())->toBeTrue();
expect($reflection->hasMethod('register'))->toBeTrue();
expect($reflection->hasMethod('boot'))->toBeTrue();
});
it('register method has correct signature', function () {
$reflection = new ReflectionClass(ServiceProvider::class);
$method = $reflection->getMethod('register');
expect($method->isPublic())->toBeTrue();
expect($method->getParameters())->toHaveCount(1);
expect($method->getParameters()[0]->getType()?->getName())->toBe(Container::class);
expect($method->getReturnType()?->getName())->toBe('void');
});
it('boot method has correct signature', function () {
$reflection = new ReflectionClass(ServiceProvider::class);
$method = $reflection->getMethod('boot');
expect($method->isPublic())->toBeTrue();
expect($method->getParameters())->toHaveCount(1);
expect($method->getParameters()[0]->getType()?->getName())->toBe(Container::class);
expect($method->getReturnType()?->getName())->toBe('void');
});
});
describe('ServiceProvider implementation examples', function () {
it('can implement a basic service provider', function () {
$provider = new class implements ServiceProvider {
public function register(Container $container): void
{
$container->getWrappedContainer()->set('example.service', 'registered');
}
public function boot(Container $container): void
{
// Boot logic here
}
};
$container = $this->createTestContainer();
$provider->register($container);
expect($container->get('example.service'))->toBe('registered');
});
it('can implement a provider with complex services', function () {
$provider = new class implements ServiceProvider {
public function register(Container $container): void
{
$container->getWrappedContainer()->set('complex.service', \DI\factory(function () {
return new class {
public function getValue(): string
{
return 'complex_value';
}
};
}));
}
public function boot(Container $container): void
{
// Could perform additional setup here
$service = $container->get('complex.service');
// Setup complete
}
};
$container = $this->createTestContainer();
$provider->register($container);
$provider->boot($container);
$service = $container->get('complex.service');
expect($service->getValue())->toBe('complex_value');
});
it('can implement a provider that registers multiple services', function () {
$provider = new class implements ServiceProvider {
public function register(Container $container): void
{
$wrapped = $container->getWrappedContainer();
$wrapped->set('service.a', 'value_a');
$wrapped->set('service.b', 'value_b');
$wrapped->set('service.c', \DI\factory(function () {
return 'value_c';
}));
}
public function boot(Container $container): void
{
// Boot all registered services
}
};
$container = $this->createTestContainer();
$provider->register($container);
$provider->boot($container);
expect($container->get('service.a'))->toBe('value_a');
expect($container->get('service.b'))->toBe('value_b');
expect($container->get('service.c'))->toBe('value_c');
});
it('boot method can access services registered by register method', function () {
$bootedServices = [];
$provider = new class($bootedServices) implements ServiceProvider {
public function __construct(private array &$bootedServices)
{
}
public function register(Container $container): void
{
$container->getWrappedContainer()->set('bootable.service', 'registered_value');
}
public function boot(Container $container): void
{
$value = $container->get('bootable.service');
$this->bootedServices[] = $value;
}
};
$container = $this->createTestContainer();
$provider->register($container);
$provider->boot($container);
expect($bootedServices)->toBe(['registered_value']);
});
});