1. php
  2. /frameworks
  3. /slim

Slim Framework Guide

Introduction to Slim Framework

Slim is a PHP microframework that helps you quickly write simple yet powerful web applications and APIs. It's built on top of PSR-7 HTTP message interfaces and supports dependency injection, middleware, and fast routing.

Installation and Setup

Installing Slim

# Create new project with Slim skeleton
composer create-project slim/slim-skeleton my-slim-app

# Or install Slim into existing project
composer require slim/slim
composer require slim/psr7

Basic Application Setup

<?php
// public/index.php
require_once __DIR__ . '/../vendor/autoload.php';

use Slim\Factory\AppFactory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

// Create Slim app
$app = AppFactory::create();

// Add routing middleware
$app->addRoutingMiddleware();

// Add error middleware
$errorMiddleware = $app->addErrorMiddleware(true, true, true);

// Define routes
$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write("Hello, Slim Framework!");
    return $response;
});

$app->run();
?>

Project Structure

my-slim-app/
├── public/
│   └── index.php
├── src/
│   ├── Application/
│   │   ├── Actions/
│   │   ├── Handlers/
│   │   ├── Middleware/
│   │   └── ResponseEmitter/
│   ├── Domain/
│   │   ├── User/
│   │   └── Product/
│   └── Infrastructure/
│       ├── Persistence/
│       └── Validation/
├── config/
│   ├── container.php
│   ├── middleware.php
│   ├── routes.php
│   └── settings.php
├── templates/
├── logs/
├── tests/
└── composer.json

Routing

Basic Routes

<?php
use Slim\Factory\AppFactory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

$app = AppFactory::create();

// GET route
$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");
    return $response;
});

// POST route
$app->post('/users', function (Request $request, Response $response, array $args) {
    $data = $request->getParsedBody();
    // Create user logic here
    $response->getBody()->write(json_encode(['status' => 'created']));
    return $response->withHeader('Content-Type', 'application/json');
});

// PUT route
$app->put('/users/{id}', function (Request $request, Response $response, array $args) {
    $id = $args['id'];
    $data = $request->getParsedBody();
    // Update user logic here
    $response->getBody()->write(json_encode(['id' => $id, 'status' => 'updated']));
    return $response->withHeader('Content-Type', 'application/json');
});

// DELETE route
$app->delete('/users/{id}', function (Request $request, Response $response, array $args) {
    $id = $args['id'];
    // Delete user logic here
    $response->getBody()->write(json_encode(['id' => $id, 'status' => 'deleted']));
    return $response->withHeader('Content-Type', 'application/json');
});
?>

Route Groups

<?php
$app->group('/api/v1', function ($group) {
    $group->group('/users', function ($group) {
        $group->get('', \App\Action\User\ListUsersAction::class);
        $group->post('', \App\Action\User\CreateUserAction::class);
        $group->get('/{id}', \App\Action\User\ViewUserAction::class);
        $group->put('/{id}', \App\Action\User\UpdateUserAction::class);
        $group->delete('/{id}', \App\Action\User\DeleteUserAction::class);
    });
    
    $group->group('/products', function ($group) {
        $group->get('', \App\Action\Product\ListProductsAction::class);
        $group->post('', \App\Action\Product\CreateProductAction::class);
        $group->get('/{id}', \App\Action\Product\ViewProductAction::class);
    });
})->add(\App\Middleware\AuthenticationMiddleware::class);
?>

Route Patterns and Constraints

<?php
// Route with optional parameters
$app->get('/users[/{id}]', function (Request $request, Response $response, array $args) {
    $id = $args['id'] ?? null;
    if ($id) {
        // Show specific user
        $response->getBody()->write("User ID: $id");
    } else {
        // Show all users
        $response->getBody()->write("All users");
    }
    return $response;
});

// Route with constraints
$app->get('/users/{id:[0-9]+}', function (Request $request, Response $response, array $args) {
    $id = $args['id'];
    $response->getBody()->write("User ID: $id");
    return $response;
});

// Route with multiple constraints
$app->get('/archive/{year:[0-9]{4}}/{month:[0-9]{2}}', 
    function (Request $request, Response $response, array $args) {
        $year = $args['year'];
        $month = $args['month'];
        $response->getBody()->write("Archive for $year-$month");
        return $response;
    }
);
?>

Controllers and Actions

Action Classes

<?php
// src/Action/User/ListUsersAction.php
namespace App\Action\User;

use App\Domain\User\UserRepository;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

final class ListUsersAction
{
    private UserRepository $userRepository;
    
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }
    
    public function __invoke(Request $request, Response $response): Response
    {
        $queryParams = $request->getQueryParams();
        $page = (int) ($queryParams['page'] ?? 1);
        $limit = (int) ($queryParams['limit'] ?? 10);
        
        $users = $this->userRepository->findAll($page, $limit);
        
        $payload = json_encode([
            'data' => $users,
            'pagination' => [
                'page' => $page,
                'limit' => $limit,
                'total' => $this->userRepository->count()
            ]
        ]);
        
        $response->getBody()->write($payload);
        return $response->withHeader('Content-Type', 'application/json');
    }
}

// src/Action/User/CreateUserAction.php
namespace App\Action\User;

use App\Domain\User\UserRepository;
use App\Domain\User\User;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Valitron\Validator;

final class CreateUserAction
{
    private UserRepository $userRepository;
    
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }
    
    public function __invoke(Request $request, Response $response): Response
    {
        $data = $request->getParsedBody();
        
        // Validation
        $validator = new Validator($data);
        $validator->rule('required', ['name', 'email']);
        $validator->rule('email', 'email');
        $validator->rule('lengthMin', 'name', 2);
        
        if (!$validator->validate()) {
            $payload = json_encode([
                'error' => 'Validation failed',
                'details' => $validator->errors()
            ]);
            $response->getBody()->write($payload);
            return $response
                ->withHeader('Content-Type', 'application/json')
                ->withStatus(400);
        }
        
        // Create user
        $user = new User();
        $user->setName($data['name']);
        $user->setEmail($data['email']);
        
        $userId = $this->userRepository->save($user);
        
        $payload = json_encode([
            'id' => $userId,
            'message' => 'User created successfully'
        ]);
        
        $response->getBody()->write($payload);
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus(201);
    }
}
?>

Controller Base Class

<?php
// src/Controller/BaseController.php
namespace App\Controller;

use Psr\Http\Message\ResponseInterface as Response;
use Slim\Psr7\Response as SlimResponse;

abstract class BaseController
{
    protected function jsonResponse(
        array $data, 
        int $status = 200,
        Response $response = null
    ): Response {
        $response = $response ?: new SlimResponse();
        
        $payload = json_encode($data, JSON_PRETTY_PRINT);
        $response->getBody()->write($payload);
        
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus($status);
    }
    
    protected function errorResponse(
        string $message,
        int $status = 400,
        array $details = [],
        Response $response = null
    ): Response {
        return $this->jsonResponse([
            'error' => $message,
            'details' => $details
        ], $status, $response);
    }
    
    protected function successResponse(
        $data = null,
        string $message = 'Success',
        Response $response = null
    ): Response {
        $payload = ['message' => $message];
        
        if ($data !== null) {
            $payload['data'] = $data;
        }
        
        return $this->jsonResponse($payload, 200, $response);
    }
}

// Example usage
class UserController extends BaseController
{
    public function index(Request $request, Response $response): Response
    {
        $users = [
            ['id' => 1, 'name' => 'John Doe'],
            ['id' => 2, 'name' => 'Jane Smith']
        ];
        
        return $this->successResponse($users, 'Users retrieved successfully', $response);
    }
    
    public function show(Request $request, Response $response, array $args): Response
    {
        $id = $args['id'];
        
        // Simulate user not found
        if ($id > 100) {
            return $this->errorResponse('User not found', 404, [], $response);
        }
        
        $user = ['id' => $id, 'name' => 'User ' . $id];
        return $this->successResponse($user, 'User retrieved successfully', $response);
    }
}
?>

Middleware

Creating Custom Middleware

<?php
// src/Middleware/AuthenticationMiddleware.php
namespace App\Middleware;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response as SlimResponse;

class AuthenticationMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandlerInterface $handler): Response
    {
        $authHeader = $request->getHeaderLine('Authorization');
        
        if (empty($authHeader)) {
            $response = new SlimResponse();
            $response->getBody()->write(json_encode([
                'error' => 'Authentication required'
            ]));
            
            return $response
                ->withHeader('Content-Type', 'application/json')
                ->withStatus(401);
        }
        
        // Extract token from "Bearer TOKEN" format
        if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
            $token = $matches[1];
            
            // Validate token (example validation)
            if ($this->validateToken($token)) {
                // Add user info to request attributes
                $request = $request->withAttribute('user', $this->getUserFromToken($token));
                return $handler->handle($request);
            }
        }
        
        $response = new SlimResponse();
        $response->getBody()->write(json_encode([
            'error' => 'Invalid authentication token'
        ]));
        
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus(401);
    }
    
    private function validateToken(string $token): bool
    {
        // Implement your token validation logic
        return strlen($token) > 10; // Simple example
    }
    
    private function getUserFromToken(string $token): array
    {
        // Implement user retrieval from token
        return ['id' => 1, 'name' => 'John Doe'];
    }
}

// src/Middleware/CorsMiddleware.php
namespace App\Middleware;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class CorsMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandlerInterface $handler): Response
    {
        $response = $handler->handle($request);
        
        return $response
            ->withHeader('Access-Control-Allow-Origin', '*')
            ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
            ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
    }
}
?>

Built-in and Third-party Middleware

<?php
// config/middleware.php
use App\Middleware\AuthenticationMiddleware;
use App\Middleware\CorsMiddleware;
use Slim\Middleware\ContentLengthMiddleware;

return function ($app) {
    // CORS middleware (should be early in the stack)
    $app->add(CorsMiddleware::class);
    
    // Content length middleware
    $app->add(ContentLengthMiddleware::class);
    
    // Authentication middleware for API routes
    $app->group('/api', function ($group) {
        // Your API routes here
    })->add(AuthenticationMiddleware::class);
    
    // Add global error handling middleware
    $app->add(function ($request, $handler) {
        try {
            return $handler->handle($request);
        } catch (\Throwable $e) {
            $response = new \Slim\Psr7\Response();
            $response->getBody()->write(json_encode([
                'error' => 'Internal server error',
                'message' => $e->getMessage()
            ]));
            
            return $response
                ->withHeader('Content-Type', 'application/json')
                ->withStatus(500);
        }
    });
};
?>

Dependency Injection

Container Configuration

<?php
// config/container.php
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;

return function () {
    $containerBuilder = new ContainerBuilder();
    
    $containerBuilder->addDefinitions([
        // Database
        PDO::class => function (ContainerInterface $c) {
            $settings = $c->get('settings')['database'];
            
            $dsn = sprintf(
                'mysql:host=%s;dbname=%s;charset=utf8mb4',
                $settings['host'],
                $settings['database']
            );
            
            return new PDO($dsn, $settings['username'], $settings['password'], [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            ]);
        },
        
        // Repositories
        \App\Domain\User\UserRepository::class => function (ContainerInterface $c) {
            return new \App\Infrastructure\Persistence\User\DatabaseUserRepository(
                $c->get(PDO::class)
            );
        },
        
        // Services
        \App\Domain\User\UserService::class => function (ContainerInterface $c) {
            return new \App\Domain\User\UserService(
                $c->get(\App\Domain\User\UserRepository::class)
            );
        },
        
        // Settings
        'settings' => [
            'database' => [
                'host' => $_ENV['DB_HOST'] ?? 'localhost',
                'database' => $_ENV['DB_NAME'] ?? 'slim_app',
                'username' => $_ENV['DB_USER'] ?? 'root',
                'password' => $_ENV['DB_PASS'] ?? '',
            ],
            'app' => [
                'name' => 'Slim Application',
                'version' => '1.0.0',
            ]
        ]
    ]);
    
    return $containerBuilder->build();
};
?>

Service Classes

<?php
// src/Domain/User/UserService.php
namespace App\Domain\User;

class UserService
{
    private UserRepository $userRepository;
    
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }
    
    public function getAllUsers(int $page = 1, int $limit = 10): array
    {
        return $this->userRepository->findAll($page, $limit);
    }
    
    public function getUserById(int $id): ?User
    {
        return $this->userRepository->findById($id);
    }
    
    public function createUser(array $userData): User
    {
        $this->validateUserData($userData);
        
        $user = new User();
        $user->setName($userData['name']);
        $user->setEmail($userData['email']);
        
        $this->userRepository->save($user);
        
        return $user;
    }
    
    public function updateUser(int $id, array $userData): User
    {
        $user = $this->getUserById($id);
        
        if (!$user) {
            throw new \RuntimeException('User not found');
        }
        
        $this->validateUserData($userData);
        
        if (isset($userData['name'])) {
            $user->setName($userData['name']);
        }
        
        if (isset($userData['email'])) {
            $user->setEmail($userData['email']);
        }
        
        $this->userRepository->save($user);
        
        return $user;
    }
    
    public function deleteUser(int $id): bool
    {
        return $this->userRepository->delete($id);
    }
    
    private function validateUserData(array $data): void
    {
        if (empty($data['name'])) {
            throw new \InvalidArgumentException('Name is required');
        }
        
        if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('Valid email is required');
        }
    }
}
?>

Database Integration

Repository Pattern

<?php
// src/Domain/User/UserRepository.php
namespace App\Domain\User;

interface UserRepository
{
    public function findAll(int $page = 1, int $limit = 10): array;
    public function findById(int $id): ?User;
    public function save(User $user): void;
    public function delete(int $id): bool;
    public function count(): int;
}

// src/Infrastructure/Persistence/User/DatabaseUserRepository.php
namespace App\Infrastructure\Persistence\User;

use App\Domain\User\User;
use App\Domain\User\UserRepository;
use PDO;

class DatabaseUserRepository implements UserRepository
{
    private PDO $pdo;
    
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
    
    public function findAll(int $page = 1, int $limit = 10): array
    {
        $offset = ($page - 1) * $limit;
        
        $stmt = $this->pdo->prepare(
            'SELECT * FROM users ORDER BY id DESC LIMIT :limit OFFSET :offset'
        );
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        
        $users = [];
        while ($row = $stmt->fetch()) {
            $users[] = $this->hydrateUser($row);
        }
        
        return $users;
    }
    
    public function findById(int $id): ?User
    {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
        $stmt->execute();
        
        $row = $stmt->fetch();
        
        return $row ? $this->hydrateUser($row) : null;
    }
    
    public function save(User $user): void
    {
        if ($user->getId()) {
            $this->updateUser($user);
        } else {
            $this->insertUser($user);
        }
    }
    
    public function delete(int $id): bool
    {
        $stmt = $this->pdo->prepare('DELETE FROM users WHERE id = :id');
        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
        
        return $stmt->execute() && $stmt->rowCount() > 0;
    }
    
    public function count(): int
    {
        $stmt = $this->pdo->query('SELECT COUNT(*) FROM users');
        return (int) $stmt->fetchColumn();
    }
    
    private function insertUser(User $user): void
    {
        $stmt = $this->pdo->prepare(
            'INSERT INTO users (name, email, created_at) VALUES (:name, :email, NOW())'
        );
        $stmt->bindValue(':name', $user->getName());
        $stmt->bindValue(':email', $user->getEmail());
        $stmt->execute();
        
        $user->setId((int) $this->pdo->lastInsertId());
    }
    
    private function updateUser(User $user): void
    {
        $stmt = $this->pdo->prepare(
            'UPDATE users SET name = :name, email = :email WHERE id = :id'
        );
        $stmt->bindValue(':id', $user->getId(), PDO::PARAM_INT);
        $stmt->bindValue(':name', $user->getName());
        $stmt->bindValue(':email', $user->getEmail());
        $stmt->execute();
    }
    
    private function hydrateUser(array $row): User
    {
        $user = new User();
        $user->setId((int) $row['id']);
        $user->setName($row['name']);
        $user->setEmail($row['email']);
        
        return $user;
    }
}
?>

API Development

RESTful API Example

<?php
// config/routes.php
use App\Action\User\{
    ListUsersAction,
    ViewUserAction,
    CreateUserAction,
    UpdateUserAction,
    DeleteUserAction
};

return function ($app) {
    // API v1 routes
    $app->group('/api/v1', function ($group) {
        // Users resource
        $group->get('/users', ListUsersAction::class);
        $group->post('/users', CreateUserAction::class);
        $group->get('/users/{id:[0-9]+}', ViewUserAction::class);
        $group->put('/users/{id:[0-9]+}', UpdateUserAction::class);
        $group->delete('/users/{id:[0-9]+}', DeleteUserAction::class);
        
        // Health check
        $group->get('/health', function ($request, $response) {
            $payload = json_encode([
                'status' => 'healthy',
                'timestamp' => date('c'),
                'version' => '1.0.0'
            ]);
            
            $response->getBody()->write($payload);
            return $response->withHeader('Content-Type', 'application/json');
        });
    });
    
    // Handle OPTIONS requests for CORS
    $app->options('/{routes:.+}', function ($request, $response) {
        return $response;
    });
};
?>

API Response Helpers

<?php
// src/Helper/ApiResponse.php
namespace App\Helper;

use Psr\Http\Message\ResponseInterface as Response;
use Slim\Psr7\Response as SlimResponse;

class ApiResponse
{
    public static function success(
        $data = null,
        string $message = 'Success',
        int $status = 200,
        Response $response = null
    ): Response {
        $response = $response ?: new SlimResponse();
        
        $payload = [
            'success' => true,
            'message' => $message
        ];
        
        if ($data !== null) {
            $payload['data'] = $data;
        }
        
        $response->getBody()->write(json_encode($payload, JSON_PRETTY_PRINT));
        
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus($status);
    }
    
    public static function error(
        string $message,
        int $status = 400,
        array $details = [],
        Response $response = null
    ): Response {
        $response = $response ?: new SlimResponse();
        
        $payload = [
            'success' => false,
            'error' => $message
        ];
        
        if (!empty($details)) {
            $payload['details'] = $details;
        }
        
        $response->getBody()->write(json_encode($payload, JSON_PRETTY_PRINT));
        
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus($status);
    }
    
    public static function paginated(
        array $data,
        int $page,
        int $limit,
        int $total,
        Response $response = null
    ): Response {
        $response = $response ?: new SlimResponse();
        
        $payload = [
            'success' => true,
            'data' => $data,
            'pagination' => [
                'page' => $page,
                'limit' => $limit,
                'total' => $total,
                'pages' => ceil($total / $limit)
            ]
        ];
        
        $response->getBody()->write(json_encode($payload, JSON_PRETTY_PRINT));
        
        return $response->withHeader('Content-Type', 'application/json');
    }
}
?>

Testing

Unit Testing

<?php
// tests/Action/User/ListUsersActionTest.php
namespace Tests\Action\User;

use App\Action\User\ListUsersAction;
use App\Domain\User\UserRepository;
use App\Domain\User\User;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Psr7\Factory\StreamFactory;
use Slim\Psr7\Headers;
use Slim\Psr7\Request as SlimRequest;
use Slim\Psr7\Response;
use Slim\Psr7\Uri;

class ListUsersActionTest extends TestCase
{
    use ProphecyTrait;
    
    public function testListUsers()
    {
        // Mock repository
        $userRepository = $this->prophesize(UserRepository::class);
        
        $users = [
            $this->createUser(1, 'John Doe', '[email protected]'),
            $this->createUser(2, 'Jane Smith', '[email protected]')
        ];
        
        $userRepository->findAll(1, 10)->willReturn($users);
        $userRepository->count()->willReturn(2);
        
        // Create action
        $action = new ListUsersAction($userRepository->reveal());
        
        // Create request
        $request = $this->createRequest('GET', '/users');
        $response = new Response();
        
        // Execute action
        $response = $action($request, $response);
        
        // Assertions
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertEquals('application/json', $response->getHeaderLine('Content-Type'));
        
        $body = (string) $response->getBody();
        $data = json_decode($body, true);
        
        $this->assertArrayHasKey('data', $data);
        $this->assertCount(2, $data['data']);
        $this->assertArrayHasKey('pagination', $data);
    }
    
    private function createRequest(string $method, string $path): Request
    {
        $uri = new Uri('', '', 80, $path);
        $handle = fopen('php://temp', 'w+');
        $stream = (new StreamFactory())->createStreamFromResource($handle);
        $headers = new Headers();
        
        return new SlimRequest($method, $uri, $headers, [], [], $stream);
    }
    
    private function createUser(int $id, string $name, string $email): User
    {
        $user = new User();
        $user->setId($id);
        $user->setName($name);
        $user->setEmail($email);
        
        return $user;
    }
}
?>

Integration Testing

<?php
// tests/Integration/UserApiTest.php
namespace Tests\Integration;

use PHPUnit\Framework\TestCase;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Psr7\Factory\StreamFactory;
use Slim\Psr7\Headers;
use Slim\Psr7\Request;
use Slim\Psr7\Uri;

class UserApiTest extends TestCase
{
    private App $app;
    
    protected function setUp(): void
    {
        $this->app = AppFactory::create();
        
        // Configure your test app here
        // Include routes, middleware, etc.
        require __DIR__ . '/../../config/routes.php';
        require __DIR__ . '/../../config/middleware.php';
    }
    
    public function testGetUsers()
    {
        $request = $this->createRequest('GET', '/api/v1/users');
        $response = $this->app->handle($request);
        
        $this->assertEquals(200, $response->getStatusCode());
        
        $body = (string) $response->getBody();
        $data = json_decode($body, true);
        
        $this->assertIsArray($data);
        $this->assertArrayHasKey('data', $data);
    }
    
    public function testCreateUser()
    {
        $userData = [
            'name' => 'Test User',
            'email' => '[email protected]'
        ];
        
        $request = $this->createRequest('POST', '/api/v1/users', $userData);
        $response = $this->app->handle($request);
        
        $this->assertEquals(201, $response->getStatusCode());
        
        $body = (string) $response->getBody();
        $data = json_decode($body, true);
        
        $this->assertArrayHasKey('id', $data);
        $this->assertArrayHasKey('message', $data);
    }
    
    private function createRequest(
        string $method,
        string $path,
        array $data = []
    ): Request {
        $uri = new Uri('', '', 80, $path);
        $handle = fopen('php://temp', 'w+');
        $stream = (new StreamFactory())->createStreamFromResource($handle);
        
        $headers = new Headers();
        $headers->addHeader('Content-Type', 'application/json');
        
        if (!empty($data)) {
            $stream->write(json_encode($data));
            $stream->rewind();
        }
        
        return new Request($method, $uri, $headers, [], [], $stream);
    }
}
?>

Configuration and Environment

Environment Configuration

<?php
// config/settings.php
return [
    'settings' => [
        'displayErrorDetails' => $_ENV['APP_DEBUG'] === 'true',
        'logError' => true,
        'logErrorDetails' => true,
        'logger' => [
            'name' => 'slim-app',
            'path' => __DIR__ . '/../logs/app.log',
            'level' => \Monolog\Logger::DEBUG,
        ],
        'database' => [
            'host' => $_ENV['DB_HOST'] ?? 'localhost',
            'database' => $_ENV['DB_NAME'] ?? 'slim_app',
            'username' => $_ENV['DB_USER'] ?? 'root',
            'password' => $_ENV['DB_PASS'] ?? '',
        ],
        'jwt' => [
            'secret' => $_ENV['JWT_SECRET'] ?? 'your-secret-key',
            'algorithm' => 'HS256',
            'expires_in' => 3600 // 1 hour
        ]
    ]
];
?>

Best Practices

Error Handling

<?php
// src/Middleware/ErrorHandlerMiddleware.php
namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Response;

class ErrorHandlerMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        try {
            return $handler->handle($request);
        } catch (HttpNotFoundException $e) {
            return $this->createErrorResponse('Resource not found', 404);
        } catch (\InvalidArgumentException $e) {
            return $this->createErrorResponse($e->getMessage(), 400);
        } catch (\Throwable $e) {
            // Log the error
            error_log($e->getMessage());
            
            return $this->createErrorResponse('Internal server error', 500);
        }
    }
    
    private function createErrorResponse(string $message, int $status): ResponseInterface
    {
        $response = new Response();
        $payload = json_encode([
            'success' => false,
            'error' => $message
        ]);
        
        $response->getBody()->write($payload);
        
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus($status);
    }
}
?>

Performance Optimization

<?php
// Use route caching in production
if (!$_ENV['APP_DEBUG']) {
    $app->getRouteCollector()->setCacheFile('/tmp/routes.cache');
}

// Enable OpCache
ini_set('opcache.enable', 1);
ini_set('opcache.memory_consumption', 128);
ini_set('opcache.max_accelerated_files', 4000);
ini_set('opcache.revalidate_freq', 60);

// Database connection pooling
$pdo = new PDO($dsn, $username, $password, [
    PDO::ATTR_PERSISTENT => true,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
?>

Slim Framework provides a lightweight, fast, and flexible foundation for building modern PHP applications and APIs with clean architecture and PSR compliance.