Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
1075 lines
22 KiB
Markdown
1075 lines
22 KiB
Markdown
# Troubleshooting
|
|
|
|
Comprehensive guide for diagnosing and fixing common issues in the Custom PHP Framework.
|
|
|
|
## Common Errors
|
|
|
|
### Class Not Found Errors
|
|
|
|
**Symptom**: `Class 'App\...' not found`
|
|
|
|
**Causes**:
|
|
- Autoloader not regenerated after creating new classes
|
|
- Incorrect namespace
|
|
- File not saved in correct directory
|
|
|
|
**Solutions**:
|
|
```bash
|
|
# Regenerate autoloader
|
|
composer reload
|
|
|
|
# Or in Docker
|
|
make reload
|
|
|
|
# Check namespace matches directory structure
|
|
# src/Domain/User/Services/UserService.php
|
|
namespace App\Domain\User\Services;
|
|
```
|
|
|
|
**Prevention**:
|
|
- Always run `composer reload` after creating new classes
|
|
- Use PSR-4 autoloading standards
|
|
- Namespace must match directory structure
|
|
|
|
---
|
|
|
|
### Container Binding Missing
|
|
|
|
**Symptom**: `No binding found for interface X`
|
|
|
|
**Causes**:
|
|
- Missing `#[Initializer]` attribute
|
|
- Initializer not discovered (cache issue)
|
|
- Circular dependency
|
|
|
|
**Solutions**:
|
|
```php
|
|
// 1. Add Initializer attribute
|
|
final readonly class ServiceInitializer
|
|
{
|
|
#[Initializer]
|
|
public function initialize(Container $container): void
|
|
{
|
|
$container->singleton(
|
|
UserRepository::class,
|
|
new DatabaseUserRepository($container->get(Connection::class))
|
|
);
|
|
}
|
|
}
|
|
|
|
// 2. Clear discovery cache
|
|
rm -rf storage/cache/discovery_*
|
|
|
|
// 3. Check for circular dependencies in constructor
|
|
// ❌ Bad: A depends on B, B depends on A
|
|
final readonly class ServiceA
|
|
{
|
|
public function __construct(private readonly ServiceB $b) {}
|
|
}
|
|
|
|
final readonly class ServiceB
|
|
{
|
|
public function __construct(private readonly ServiceA $a) {} // Circular!
|
|
}
|
|
```
|
|
|
|
**Prevention**:
|
|
- Use `#[Initializer]` for all service registrations
|
|
- Avoid circular dependencies through interface abstraction
|
|
- Use method injection instead of constructor injection when needed
|
|
|
|
---
|
|
|
|
### Route Not Found (404)
|
|
|
|
**Symptom**: Route returns 404 even though controller exists
|
|
|
|
**Causes**:
|
|
- Missing `#[Route]` attribute
|
|
- Route cache out of date
|
|
- HTTP method mismatch
|
|
- Middleware blocking request
|
|
|
|
**Solutions**:
|
|
```bash
|
|
# 1. Check route is registered
|
|
docker exec php php console.php routes:list
|
|
|
|
# 2. Clear route cache
|
|
rm -rf storage/cache/routes_*
|
|
|
|
# 3. Verify Route attribute
|
|
#[Route(path: '/api/users', method: Method::GET)]
|
|
public function getUsers(): JsonResult
|
|
|
|
# 4. Check middleware chain
|
|
docker exec php php -r "
|
|
use App\Framework\Discovery\UnifiedDiscoveryService;
|
|
\$discovery = new UnifiedDiscoveryService();
|
|
\$routes = \$discovery->discoverRoutes();
|
|
print_r(\$routes);
|
|
"
|
|
```
|
|
|
|
**Common Mistakes**:
|
|
```php
|
|
// ❌ Wrong: Method mismatch
|
|
#[Route(path: '/api/users', method: Method::GET)]
|
|
// Request: POST /api/users → 404
|
|
|
|
// ✅ Correct: Match HTTP method
|
|
#[Route(path: '/api/users', method: Method::POST)]
|
|
```
|
|
|
|
---
|
|
|
|
### HTTPS Required Error
|
|
|
|
**Symptom**: `HTTPS is required for this operation`
|
|
|
|
**Causes**:
|
|
- Accessing framework via HTTP instead of HTTPS
|
|
- Missing SSL certificates in development
|
|
- Reverse proxy not forwarding HTTPS headers
|
|
|
|
**Solutions**:
|
|
```bash
|
|
# Development: Use HTTPS
|
|
https://localhost/ # ✅ Correct
|
|
http://localhost/ # ❌ Wrong - will be rejected
|
|
|
|
# Check SSL certificates exist
|
|
ls -la docker/ssl/
|
|
# Should see: localhost.crt, localhost.key
|
|
|
|
# Regenerate if missing
|
|
cd docker/ssl
|
|
./generate-ssl.sh
|
|
```
|
|
|
|
**Production Fix**:
|
|
```nginx
|
|
# Nginx: Forward HTTPS headers
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-Ssl on;
|
|
```
|
|
|
|
---
|
|
|
|
### User Agent Required Error
|
|
|
|
**Symptom**: `Valid User-Agent header is required`
|
|
|
|
**Causes**:
|
|
- Missing User-Agent in cURL/API requests
|
|
- Bot/Scanner detection blocking legitimate requests
|
|
|
|
**Solutions**:
|
|
```bash
|
|
# Add User-Agent to cURL requests
|
|
curl -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
|
|
https://localhost/api/endpoint
|
|
|
|
# In API client code
|
|
$client = new GuzzleHttp\Client([
|
|
'headers' => [
|
|
'User-Agent' => 'MyApp/1.0'
|
|
]
|
|
]);
|
|
```
|
|
|
|
---
|
|
|
|
## Container/DI Issues
|
|
|
|
### Cannot Resolve Dependency
|
|
|
|
**Symptom**: `Cannot resolve parameter 'X' for class Y`
|
|
|
|
**Causes**:
|
|
- Constructor parameter has no type hint
|
|
- Scalar parameter without default value
|
|
- Interface not bound in container
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ❌ Problem: No type hint
|
|
public function __construct($logger) {}
|
|
|
|
// ✅ Solution: Add type hint
|
|
public function __construct(private readonly Logger $logger) {}
|
|
|
|
// ❌ Problem: Scalar without default
|
|
public function __construct(string $apiKey) {}
|
|
|
|
// ✅ Solution: Use Value Object or default
|
|
public function __construct(
|
|
private readonly ApiKey $apiKey
|
|
) {}
|
|
|
|
// OR provide default
|
|
public function __construct(
|
|
private readonly string $apiKey = ''
|
|
) {}
|
|
```
|
|
|
|
**Interface Binding**:
|
|
```php
|
|
// Bind interface to implementation
|
|
#[Initializer]
|
|
public function initialize(Container $container): void
|
|
{
|
|
$container->bind(
|
|
LoggerInterface::class,
|
|
fn() => new FileLogger('/var/log/app.log')
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Circular Dependency Detected
|
|
|
|
**Symptom**: `Circular dependency detected: A -> B -> A`
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ❌ Problem: Direct circular dependency
|
|
final readonly class OrderService
|
|
{
|
|
public function __construct(
|
|
private readonly InvoiceService $invoices
|
|
) {}
|
|
}
|
|
|
|
final readonly class InvoiceService
|
|
{
|
|
public function __construct(
|
|
private readonly OrderService $orders // Circular!
|
|
) {}
|
|
}
|
|
|
|
// ✅ Solution 1: Extract shared logic to third service
|
|
final readonly class OrderService
|
|
{
|
|
public function __construct(
|
|
private readonly PriceCalculator $calculator
|
|
) {}
|
|
}
|
|
|
|
final readonly class InvoiceService
|
|
{
|
|
public function __construct(
|
|
private readonly PriceCalculator $calculator
|
|
) {}
|
|
}
|
|
|
|
// ✅ Solution 2: Use events for decoupling
|
|
final readonly class OrderService
|
|
{
|
|
public function __construct(
|
|
private readonly EventDispatcher $events
|
|
) {}
|
|
|
|
public function completeOrder(Order $order): void
|
|
{
|
|
// Process order
|
|
$this->events->dispatch(new OrderCompletedEvent($order));
|
|
}
|
|
}
|
|
|
|
final readonly class InvoiceService
|
|
{
|
|
#[EventHandler]
|
|
public function onOrderCompleted(OrderCompletedEvent $event): void
|
|
{
|
|
// Generate invoice without direct dependency
|
|
$this->generateInvoice($event->order);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Service Not Singleton
|
|
|
|
**Symptom**: Multiple instances created when singleton expected
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ❌ Wrong: Regular binding creates new instance each time
|
|
$container->bind(
|
|
DatabaseConnection::class,
|
|
fn() => new DatabaseConnection($config)
|
|
);
|
|
|
|
// ✅ Correct: Singleton creates one instance
|
|
$container->singleton(
|
|
DatabaseConnection::class,
|
|
new DatabaseConnection($config)
|
|
);
|
|
|
|
// ✅ Alternative: Lazy singleton
|
|
$container->singleton(
|
|
DatabaseConnection::class,
|
|
fn() => new DatabaseConnection($config)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Routing Problems
|
|
|
|
### Route Conflicts
|
|
|
|
**Symptom**: Wrong controller handling request
|
|
|
|
**Causes**:
|
|
- Multiple routes match same pattern
|
|
- Route priority issues
|
|
- Catch-all route defined too early
|
|
|
|
**Solutions**:
|
|
```bash
|
|
# List all routes to find conflicts
|
|
docker exec php php console.php routes:list | grep '/api/users'
|
|
|
|
# Check route priority order
|
|
docker exec php php console.php routes:list --sort=priority
|
|
```
|
|
|
|
**Fix Route Conflicts**:
|
|
```php
|
|
// ❌ Problem: Routes conflict
|
|
#[Route(path: '/api/users/{id}', method: Method::GET)]
|
|
public function getUser(string $id): JsonResult {}
|
|
|
|
#[Route(path: '/api/users/me', method: Method::GET)]
|
|
public function getCurrentUser(): JsonResult {} // Never matched!
|
|
|
|
// ✅ Solution: Most specific routes first
|
|
#[Route(path: '/api/users/me', method: Method::GET)]
|
|
public function getCurrentUser(): JsonResult {}
|
|
|
|
#[Route(path: '/api/users/{id}', method: Method::GET)]
|
|
public function getUser(string $id): JsonResult {}
|
|
```
|
|
|
|
---
|
|
|
|
### Route Parameters Not Working
|
|
|
|
**Symptom**: Route parameter is null or empty
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ✅ Parameter name must match route
|
|
#[Route(path: '/api/users/{userId}', method: Method::GET)]
|
|
public function getUser(string $userId): JsonResult // Parameter name matches!
|
|
{
|
|
return new JsonResult(['userId' => $userId]);
|
|
}
|
|
|
|
// ❌ Wrong: Parameter name mismatch
|
|
#[Route(path: '/api/users/{userId}', method: Method::GET)]
|
|
public function getUser(string $id): JsonResult // Mismatch!
|
|
{
|
|
// $id will be null
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Middleware Not Executing
|
|
|
|
**Symptom**: Middleware bypassed or not running
|
|
|
|
**Causes**:
|
|
- Missing `#[MiddlewarePriority]` attribute
|
|
- Wrong middleware registration
|
|
- Middleware exception not caught
|
|
|
|
**Solutions**:
|
|
```php
|
|
// Add priority attribute
|
|
#[MiddlewarePriority(100)]
|
|
final readonly class AuthMiddleware implements Middleware
|
|
{
|
|
public function process(Request $request, callable $next): Response
|
|
{
|
|
// Auth logic
|
|
return $next($request);
|
|
}
|
|
}
|
|
|
|
// Check middleware is discovered
|
|
docker exec php php console.php debug:middleware
|
|
```
|
|
|
|
**Middleware Chain Debug**:
|
|
```php
|
|
// Add logging to trace execution
|
|
final readonly class DebugMiddleware implements Middleware
|
|
{
|
|
public function process(Request $request, callable $next): Response
|
|
{
|
|
Logger::debug('[Middleware] Before: ' . get_class($this));
|
|
|
|
$response = $next($request);
|
|
|
|
Logger::debug('[Middleware] After: ' . get_class($this));
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Issues
|
|
|
|
### Slow Page Load Times
|
|
|
|
**Diagnosis Steps**:
|
|
```bash
|
|
# 1. Enable performance profiling
|
|
docker exec php php console.php performance:enable
|
|
|
|
# 2. Check slow query log
|
|
docker exec php tail -f /var/log/mysql/slow-queries.log
|
|
|
|
# 3. Profile specific endpoint
|
|
docker exec php php console.php performance:profile /api/users
|
|
|
|
# 4. Check N+1 queries
|
|
docker exec php php console.php db:explain
|
|
```
|
|
|
|
**Common Causes & Fixes**:
|
|
|
|
#### N+1 Query Problem
|
|
```php
|
|
// ❌ Problem: N+1 queries
|
|
$users = $this->userRepository->findAll();
|
|
foreach ($users as $user) {
|
|
// Triggers separate query for each user!
|
|
$profile = $user->getProfile();
|
|
}
|
|
|
|
// ✅ Solution: Eager loading
|
|
$users = $this->userRepository->findAllWithProfiles();
|
|
// Single query with JOIN
|
|
```
|
|
|
|
#### Missing Indexes
|
|
```bash
|
|
# Check missing indexes
|
|
docker exec php php console.php db:analyze-indexes
|
|
|
|
# Add index via migration
|
|
$table->index('email'); // Single column
|
|
$table->index(['user_id', 'created_at']); // Composite
|
|
```
|
|
|
|
#### Cache Not Used
|
|
|
|
```php
|
|
// ✅ Cache expensive operations
|
|
use App\Framework\Cache\CacheKey;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
$cacheKey = CacheKey::fromString('users_list');
|
|
$users = $this->cache->remember(
|
|
$cacheKey,
|
|
fn() => $this->repository->findAll(),
|
|
Duration::fromMinutes(10)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### Memory Exhaustion
|
|
|
|
**Symptom**: `Allowed memory size exhausted`
|
|
|
|
**Causes**:
|
|
- Large result sets loaded into memory
|
|
- Memory leaks in loops
|
|
- Circular references
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ❌ Problem: Loading entire table
|
|
$allUsers = $this->connection->query('SELECT * FROM users');
|
|
// 1 million rows = memory exhausted
|
|
|
|
// ✅ Solution: Batch processing
|
|
$batchSize = 1000;
|
|
$offset = 0;
|
|
|
|
do {
|
|
$batch = $this->connection->query(
|
|
"SELECT * FROM users LIMIT {$batchSize} OFFSET {$offset}"
|
|
);
|
|
|
|
foreach ($batch as $user) {
|
|
$this->processUser($user);
|
|
}
|
|
|
|
$offset += $batchSize;
|
|
|
|
// Force garbage collection
|
|
gc_collect_cycles();
|
|
|
|
} while (count($batch) === $batchSize);
|
|
```
|
|
|
|
**Memory Leak Prevention**:
|
|
```php
|
|
// ✅ Clear references in loops
|
|
foreach ($largeDataset as $item) {
|
|
$result = $this->process($item);
|
|
|
|
// Clear large objects
|
|
unset($result, $item);
|
|
|
|
if ($iteration % 100 === 0) {
|
|
gc_collect_cycles();
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Database Connection Issues
|
|
|
|
### Connection Refused
|
|
|
|
**Symptom**: `SQLSTATE[HY000] [2002] Connection refused`
|
|
|
|
**Solutions**:
|
|
```bash
|
|
# 1. Check MySQL container is running
|
|
docker ps | grep mysql
|
|
|
|
# 2. Check database credentials in .env
|
|
DB_HOST=mysql # Use service name, not 'localhost'
|
|
DB_PORT=3306
|
|
DB_NAME=your_database
|
|
DB_USER=root
|
|
DB_PASS=your_password
|
|
|
|
# 3. Test connection directly
|
|
docker exec mysql mysql -u root -p your_database
|
|
|
|
# 4. Check MySQL logs
|
|
docker logs mysql
|
|
```
|
|
|
|
**Common Mistakes**:
|
|
```bash
|
|
# ❌ Wrong: localhost (from container perspective)
|
|
DB_HOST=localhost
|
|
|
|
# ✅ Correct: Docker service name
|
|
DB_HOST=mysql
|
|
```
|
|
|
|
---
|
|
|
|
### Too Many Connections
|
|
|
|
**Symptom**: `SQLSTATE[HY000] [1040] Too many connections`
|
|
|
|
**Causes**:
|
|
- Connection leaks (not closing connections)
|
|
- Too many concurrent requests
|
|
- Connection pool exhausted
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ✅ Always use try-finally for connections
|
|
$connection = $this->connectionPool->getConnection();
|
|
|
|
try {
|
|
$connection->beginTransaction();
|
|
|
|
// Database operations
|
|
|
|
$connection->commit();
|
|
} catch (\Exception $e) {
|
|
$connection->rollback();
|
|
throw $e;
|
|
} finally {
|
|
// Always release connection
|
|
$this->connectionPool->releaseConnection($connection);
|
|
}
|
|
```
|
|
|
|
**Increase Connection Limit**:
|
|
```ini
|
|
# docker/mysql/my.cnf
|
|
[mysqld]
|
|
max_connections = 200
|
|
```
|
|
|
|
---
|
|
|
|
### Deadlock Detected
|
|
|
|
**Symptom**: `SQLSTATE[40001] Deadlock found when trying to get lock`
|
|
|
|
**Causes**:
|
|
- Transactions accessing tables in different order
|
|
- Long-running transactions
|
|
- Missing indexes causing lock escalation
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ✅ Always access tables in consistent order
|
|
final readonly class PaymentService
|
|
{
|
|
public function transfer(Account $from, Account $to, Money $amount): void
|
|
{
|
|
$this->connection->beginTransaction();
|
|
|
|
try {
|
|
// ALWAYS lock in same order (e.g., by ID)
|
|
$accounts = [$from, $to];
|
|
usort($accounts, fn($a, $b) => $a->id <=> $b->id);
|
|
|
|
foreach ($accounts as $account) {
|
|
$this->lockAccount($account);
|
|
}
|
|
|
|
// Process transfer
|
|
$from->debit($amount);
|
|
$to->credit($amount);
|
|
|
|
$this->connection->commit();
|
|
} catch (\Exception $e) {
|
|
$this->connection->rollback();
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Retry Strategy**:
|
|
```php
|
|
// ✅ Retry on deadlock
|
|
public function executeWithRetry(callable $operation, int $maxAttempts = 3): mixed
|
|
{
|
|
$attempt = 0;
|
|
|
|
while ($attempt < $maxAttempts) {
|
|
try {
|
|
return $operation();
|
|
} catch (DeadlockException $e) {
|
|
$attempt++;
|
|
|
|
if ($attempt >= $maxAttempts) {
|
|
throw $e;
|
|
}
|
|
|
|
// Exponential backoff
|
|
usleep(pow(2, $attempt) * 100000); // 200ms, 400ms, 800ms
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Cache Problems
|
|
|
|
### Cache Not Working
|
|
|
|
**Symptom**: Data not cached, fetched every time
|
|
|
|
**Diagnosis**:
|
|
```bash
|
|
# 1. Check cache driver in .env
|
|
CACHE_DRIVER=redis # or file, array, memcached
|
|
|
|
# 2. Test cache directly
|
|
docker exec php php -r "
|
|
\$cache = new App\Framework\Cache\SmartCache();
|
|
\$cache->set('test', 'value', 60);
|
|
var_dump(\$cache->get('test'));
|
|
"
|
|
|
|
# 3. Check Redis connection (if using Redis)
|
|
docker exec redis redis-cli PING
|
|
# Should return: PONG
|
|
```
|
|
|
|
**Common Issues**:
|
|
|
|
```php
|
|
// ❌ Problem: TTL too short
|
|
$cache->set($key, $value, 1); // 1 second - expires immediately
|
|
|
|
// ✅ Solution: Appropriate TTL
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
$cache->set($key, $value, Duration::fromMinutes(10)->toSeconds());
|
|
|
|
// ✅ Better: Use CacheItem with Duration
|
|
$cacheItem = CacheItem::forSetting(
|
|
key: $key,
|
|
value: $value,
|
|
ttl: Duration::fromHours(1)
|
|
);
|
|
$cache->set($cacheItem);
|
|
```
|
|
|
|
---
|
|
|
|
### Cache Stampede
|
|
|
|
**Symptom**: Multiple processes regenerating same cached value simultaneously
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ✅ Use cache locking
|
|
use App\Framework\Cache\CacheLock;
|
|
|
|
public function getExpensiveData(string $key): array
|
|
{
|
|
$cacheKey = CacheKey::fromString($key);
|
|
|
|
// Try to get from cache
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
// Acquire lock to prevent stampede
|
|
$lock = $this->cache->lock($cacheKey, Duration::fromSeconds(10));
|
|
|
|
if (!$lock->acquire()) {
|
|
// Another process is regenerating, wait for it
|
|
sleep(1);
|
|
return $this->cache->get($cacheKey) ?? [];
|
|
}
|
|
|
|
try {
|
|
// Regenerate data
|
|
$data = $this->expensiveOperation();
|
|
|
|
// Cache it
|
|
$this->cache->set(
|
|
CacheItem::forSetting($cacheKey, $data, Duration::fromMinutes(10))
|
|
);
|
|
|
|
return $data;
|
|
} finally {
|
|
$lock->release();
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Cache Invalidation Issues
|
|
|
|
**Symptom**: Stale data served from cache
|
|
|
|
**Solutions**:
|
|
```php
|
|
// ✅ Tag-based invalidation
|
|
use App\Framework\Cache\CacheTag;
|
|
|
|
// Cache with tags
|
|
$userTag = CacheTag::fromString("user_{$userId}");
|
|
$teamTag = CacheTag::fromString("team_{$teamId}");
|
|
|
|
$cacheItem = CacheItem::forSetting(
|
|
key: CacheKey::fromString("user_profile_{$userId}"),
|
|
value: $userProfile,
|
|
ttl: Duration::fromHours(1),
|
|
tags: [$userTag, $teamTag]
|
|
);
|
|
|
|
$this->cache->set($cacheItem);
|
|
|
|
// Invalidate all user-related caches
|
|
$this->cache->forget($userTag);
|
|
|
|
// Invalidate all team-related caches
|
|
$this->cache->forget($teamTag);
|
|
```
|
|
|
|
**Event-Based Invalidation**:
|
|
```php
|
|
// ✅ Automatic invalidation via events
|
|
final readonly class CacheInvalidationListener
|
|
{
|
|
#[EventHandler]
|
|
public function onUserUpdated(UserUpdatedEvent $event): void
|
|
{
|
|
$userTag = CacheTag::fromString("user_{$event->userId}");
|
|
$this->cache->forget($userTag);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Debug Tools
|
|
|
|
### Framework Debug Mode
|
|
|
|
```bash
|
|
# Enable debug mode in .env
|
|
APP_ENV=development
|
|
APP_DEBUG=true
|
|
|
|
# Enhanced error pages with:
|
|
# - Full stack traces
|
|
# - Variable dumps
|
|
# - SQL query logs
|
|
# - Performance metrics
|
|
```
|
|
|
|
---
|
|
|
|
### Performance Profiling
|
|
|
|
```bash
|
|
# Enable performance collector
|
|
docker exec php php console.php performance:enable
|
|
|
|
# Profile specific request
|
|
docker exec php php console.php performance:profile /api/users
|
|
|
|
# View performance report
|
|
docker exec php php console.php performance:report
|
|
|
|
# Disable profiling
|
|
docker exec php php console.php performance:disable
|
|
```
|
|
|
|
---
|
|
|
|
### Database Query Logging
|
|
|
|
```php
|
|
// Enable query logging in code
|
|
use App\Framework\Database\QueryLogger;
|
|
|
|
$queryLogger = new QueryLogger();
|
|
$connection->setQueryLogger($queryLogger);
|
|
|
|
// Log queries
|
|
$users = $connection->query('SELECT * FROM users');
|
|
|
|
// View logged queries
|
|
foreach ($queryLogger->getQueries() as $query) {
|
|
Logger::debug('[SQL]', [
|
|
'query' => $query['sql'],
|
|
'bindings' => $query['bindings'],
|
|
'duration' => $query['duration']
|
|
]);
|
|
}
|
|
```
|
|
|
|
**Detect N+1 Queries**:
|
|
```bash
|
|
# Run N+1 detection
|
|
docker exec php php console.php db:detect-n-plus-one /api/users
|
|
```
|
|
|
|
---
|
|
|
|
### MCP Server Debugging
|
|
|
|
```bash
|
|
# Test MCP server connection
|
|
echo '{"jsonrpc": "2.0", "method": "initialize", "params": {}}' | \
|
|
docker exec -i php php console.php mcp:server
|
|
|
|
# Analyze routes via MCP
|
|
docker exec php php console.php mcp:analyze routes
|
|
|
|
# Analyze container bindings
|
|
docker exec php php console.php mcp:analyze container
|
|
|
|
# Check framework health
|
|
docker exec php php console.php mcp:health
|
|
```
|
|
|
|
---
|
|
|
|
### Request Debugging
|
|
|
|
```php
|
|
// Log full request details
|
|
use App\Framework\Http\Request;
|
|
|
|
Logger::debug('[Request]', [
|
|
'method' => $request->method->value,
|
|
'path' => $request->path,
|
|
'query' => $request->queryParameters ?? [],
|
|
'body' => $request->parsedBody ?? [],
|
|
'headers' => $request->headers->toArray(),
|
|
'server' => [
|
|
'ip' => $request->server->getRemoteAddr(),
|
|
'user_agent' => $request->server->getUserAgent()
|
|
]
|
|
]);
|
|
```
|
|
|
|
---
|
|
|
|
### Event System Debugging
|
|
|
|
```php
|
|
// Log all dispatched events
|
|
final readonly class EventDebugListener
|
|
{
|
|
#[EventHandler]
|
|
public function onAnyEvent(object $event): void
|
|
{
|
|
Logger::debug('[Event]', [
|
|
'type' => get_class($event),
|
|
'data' => $event
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Emergency Procedures
|
|
|
|
### Application Down
|
|
|
|
**Quick Recovery Steps**:
|
|
```bash
|
|
# 1. Check Docker containers
|
|
docker ps -a
|
|
|
|
# 2. Restart all services
|
|
make down && make up
|
|
|
|
# 3. Check logs
|
|
make logs
|
|
|
|
# 4. Verify database connection
|
|
docker exec mysql mysql -u root -p
|
|
|
|
# 5. Clear all caches
|
|
rm -rf storage/cache/*
|
|
composer reload
|
|
|
|
# 6. Test health endpoint
|
|
curl -k https://localhost/health
|
|
```
|
|
|
|
---
|
|
|
|
### Database Corruption
|
|
|
|
```bash
|
|
# 1. Stop application
|
|
make down
|
|
|
|
# 2. Backup database immediately
|
|
docker exec mysql mysqldump -u root -p your_database > backup.sql
|
|
|
|
# 3. Check and repair tables
|
|
docker exec mysql mysqlcheck -u root -p --auto-repair your_database
|
|
|
|
# 4. Restore from backup if needed
|
|
docker exec -i mysql mysql -u root -p your_database < backup.sql
|
|
|
|
# 5. Restart application
|
|
make up
|
|
```
|
|
|
|
---
|
|
|
|
### Cache Corruption
|
|
|
|
```bash
|
|
# 1. Flush all caches
|
|
docker exec redis redis-cli FLUSHALL # If using Redis
|
|
rm -rf storage/cache/* # File cache
|
|
|
|
# 2. Restart services
|
|
make restart
|
|
|
|
# 3. Warm up critical caches
|
|
docker exec php php console.php cache:warm
|
|
```
|
|
|
|
---
|
|
|
|
## Getting Help
|
|
|
|
### Framework Support Channels
|
|
|
|
**Issues & Bug Reports**:
|
|
- GitHub Issues: Report bugs with reproduction steps
|
|
- Include: PHP version, Docker logs, error messages
|
|
|
|
**Documentation**:
|
|
- `/docs/claude/` - Complete framework documentation
|
|
- CLAUDE.md - AI assistant integration guide
|
|
|
|
**Debugging Resources**:
|
|
- MCP Server - AI-powered framework analysis
|
|
- Performance Profiler - Built-in performance monitoring
|
|
- Query Logger - SQL debugging tool
|
|
|
|
---
|
|
|
|
## Prevention Best Practices
|
|
|
|
### Development Checklist
|
|
|
|
- [ ] Run `composer reload` after creating new classes
|
|
- [ ] Clear caches after config changes
|
|
- [ ] Test with HTTPS in development
|
|
- [ ] Add User-Agent to API requests
|
|
- [ ] Use Value Objects instead of primitives
|
|
- [ ] Implement proper error handling
|
|
- [ ] Add logging to critical operations
|
|
- [ ] Test with production-like data volumes
|
|
- [ ] Profile performance before deployment
|
|
- [ ] Review query execution plans
|
|
|
|
### Code Review Checklist
|
|
|
|
- [ ] No circular dependencies
|
|
- [ ] Proper exception handling
|
|
- [ ] Database transactions properly closed
|
|
- [ ] Cache keys properly namespaced
|
|
- [ ] Routes don't conflict
|
|
- [ ] Middleware properly ordered
|
|
- [ ] Security best practices followed
|
|
- [ ] Performance considerations addressed
|
|
- [ ] Tests cover critical paths
|
|
- [ ] Documentation updated
|
|
|
|
---
|
|
|
|
## Appendix: Common Error Codes
|
|
|
|
| Error Code | Meaning | Common Cause |
|
|
|------------|---------|--------------|
|
|
| 403 | Forbidden | WAF blocking, missing Auth, IP restriction |
|
|
| 404 | Not Found | Route not registered, wrong path |
|
|
| 405 | Method Not Allowed | HTTP method mismatch in Route attribute |
|
|
| 429 | Too Many Requests | Rate limit exceeded |
|
|
| 500 | Internal Server Error | Unhandled exception, see logs |
|
|
| 502 | Bad Gateway | PHP-FPM down, connection refused |
|
|
| 503 | Service Unavailable | Database down, cache unavailable |
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-01-28
|
|
**Framework Version**: 2.x
|