adaiasmagdiel / erlenmeyer
Erlenmeyer is a lightweight PHP framework designed to create web applications simply and efficiently.
Requires (Dev)
- pestphp/pest: ^3.8
README
Erlenmeyer is a lightweight PHP framework designed for simplicity and efficiency in building web applications. Inspired by the minimalism of Python's Flask, Erlenmeyer is not a direct clone but a unique solution tailored for PHP developers. It is currently in its early stages, making it perfect for small projects, APIs, or microservices where a lean setup is preferred. I created Erlenmeyer to streamline my own projects, but it's open for anyone seeking a straightforward, no-frills framework.
Table of Contents
- Introduction
- Features
- Requirements
- Installation
- Getting Started
- Routing
- Middlewares
- Error Handling
- Asset Management
- Session Management
- Request and Response Objects
- Logging
- Tests
- Use Cases
- License
- Reference
Introduction
Erlenmeyer is a lightweight PHP framework designed for simplicity and efficiency in building web applications. Inspired by the minimalism of Python's Flask, Erlenmeyer is not a direct clone but a unique solution tailored for PHP developers. It is currently in its early stages, making it perfect for small projects, APIs, or microservices where a lean setup is preferred. I created Erlenmeyer to streamline my own projects, but it's open for anyone seeking a straightforward, no-frills framework.
Key Characteristics:
- Minimalist: Small footprint, easy to learn and use.
- Flexible: Supports various use cases, from simple scripts to full applications.
- Extensible: Built with extensibility in mind, allowing custom functionality.
- Flask-Inspired: Familiar routing and handler concepts for developers coming from Python.
Features
- Simple and intuitive routing system with support for dynamic routes.
- Support for global and route-specific middlewares.
- Custom error handling for 404 errors and exceptions.
- Integrated session management with flash messages.
- Static asset server for CSS, JavaScript, images, and more.
- Comprehensive
Request
andResponse
objects for handling HTTP requests and responses. - Logging with file rotation for application event monitoring.
Requirements
- PHP: 8.1 or higher
- Composer: For dependency management
- Web Server: Apache with
mod_rewrite
or Nginx - PHP Extensions:
json
,mbstring
- Optional:
getallheaders
for enhanced header support (not always necessary)
Installation
Install Erlenmeyer using Composer:
composer require adaiasmagdiel/erlenmeyer
Include it in your PHP project:
require_once 'vendor/autoload.php';
Getting Started
First, make sure to import the necessary classes:
use AdaiasMagdiel\Erlenmeyer\App; use AdaiasMagdiel\Erlenmeyer\Request; use AdaiasMagdiel\Erlenmeyer\Response;
Create a new instance of the App
class:
$app = new App();
Define routes:
$app->get('/', function (Request $req, Response $res, $params) { $res->withHtml('Hello, World!')->send(); });
Run the application:
$app->run();
Routing
Erlenmeyer supports various HTTP methods for routing:
$app->get('/path', $handler); $app->post('/path', $handler); $app->put('/path', $handler); $app->delete('/path', $handler); $app->patch('/path', $handler); $app->options('/path', $handler); $app->head('/path', $handler); $app->any('/path', $handler); // Matches any HTTP method $app->match(['GET', 'POST'], '/path', $handler); // Matches specified methods
Dynamic Routes
Define routes with parameters:
$app->get('/user/[id]', function (Request $req, Response $res, $params) { $id = $params->id; $res->withHtml("User ID: $id")->send(); });
Redirects
Set up redirects:
$app->redirect('/old', '/new', false); // Temporary redirect (302)
Middlewares
Add global middlewares:
$app->addMiddleware(function (Request $req, Response $res, callable $next, $params) { // Middleware logic $next($req, $res, $params); });
Add route-specific middlewares:
$app->get('/admin', $handler, [$middleware1, $middleware2]);
Error Handling
Set a custom 404 handler:
$app->set404Handler(function (Request $req, Response $res, $params) { $res->setStatusCode(404)->withHtml('Custom 404')->send(); });
Set exception handlers:
$app->setExceptionHandler(\Exception::class, function (Request $req, Response $res, \Exception $e) { $res->setStatusCode(500)->withHtml('Error: ' . $e->getMessage())->send(); });
Asset Management
Serve static assets by creating an instance of Assets
:
use AdaiasMagdiel\Erlenmeyer\Assets; $assets = new Assets(assetsDirectory: __DIR__ . '/public', assetsRoute: '/assets'); $app = new App(assets: $assets);
Access assets via /assets/file.ext
. For example:
<link rel="stylesheet" href="/assets/css/style.css">
Session Management
Use the Session
class to manage sessions:
use AdaiasMagdiel\Erlenmeyer\Session; Session::set('key', 'value'); $value = Session::get('key', 'default'); Session::flash('message', 'Flash message'); $flash = Session::getFlash('message');
Request and Response Objects
Request
The Request
object provides access to request data:
$method = $req->getMethod(); $uri = $req->getUri(); $queryParams = $req->getQueryParams(); $formData = $req->getFormData(); $jsonData = $req->getJson(); $files = $req->getFiles(); $isAjax = $req->isAjax(); $isSecure = $req->isSecure();
Response
The Response
object allows building responses:
$res->withHtml('HTML content'); $res->withJson(['key' => 'value']); $res->withText('Plain text'); $res->withFile('/path/to/file'); $res->redirect('/path'); $res->setStatusCode(404); $res->setHeader('Key', 'Value'); $res->setCORS(['origin' => '*', 'methods' => 'GET,POST']); $res->send();
Logging
Erlenmeyer provides a flexible and extensible logging system to help you monitor and debug your application. Logging is essential for tracking events, identifying issues, and understanding application behavior. The logging system is based on the LoggerInterface
, which defines the contract for logging messages and exceptions.
Using Loggers
Erlenmeyer provides two built-in loggers: FileLogger
for file-based logging with rotation, and ConsoleLogger
for logging to the command line using error_log
. You can choose either by passing an instance to the App
constructor.
Example with FileLogger:
use AdaiasMagdiel\Erlenmeyer\Logging\FileLogger; use AdaiasMagdiel\Erlenmeyer\App; $logger = new FileLogger('/path/to/logs'); $app = new App(logger: $logger);
- Logs are written to
/path/to/logs/info.log
. - The
FileLogger
automatically rotates the log file when it exceeds 3MB, keeping up to 5 rotated files (e.g.,info.log.1
,info.log.2
, etc.). - If no log directory is provided,
FileLogger
will not log anything.
Example with ConsoleLogger:
use AdaiasMagdiel\Erlenmeyer\Logging\ConsoleLogger; use AdaiasMagdiel\Erlenmeyer\App; $logger = new ConsoleLogger(); $app = new App(logger: $logger);
- Logs are output to the command line using
error_log
, appearing in the terminal or server error log. - No configuration is required for
ConsoleLogger
, making it ideal for debugging or CLI environments.
Log Levels
The logging system supports the following levels, defined in the LogLevel
enum:
Level | Description |
---|---|
INFO |
General operational information |
DEBUG |
Detailed debug information |
WARNING |
Indicates something unexpected but recoverable |
ERROR |
Indicates a serious error affecting functionality |
CRITICAL |
Indicates a critical error that may cause the application to crash |
These levels can be used when logging messages to categorize their severity.
Creating a Custom Logger
If you need advanced logging features, such as logging to a database, sending logs to an external service, or using a custom format, you can create a custom logger by implementing the LoggerInterface
.
Example of a Custom Logger:
use AdaiasMagdiel\Erlenmeyer\Logging\LoggerInterface; use AdaiasMagdiel\Erlenmeyer\Logging\LogLevel; use Exception; use AdaiasMagdiel\Erlenmeyer\Request; class CustomLogger implements LoggerInterface { private $logFile; public function __construct(string $logFile) { $this->logFile = $logFile; } public function log(LogLevel $level, string $message): void { $timestamp = date('Y-m-d H:i:s'); $logEntry = "[$timestamp] [$level->value] $message\n"; file_put_contents($this->logFile, $logEntry, FILE_APPEND); } public function logException(Exception $e, ?Request $request = null): void { $timestamp = date('Y-m-d H:i:s'); $message = "[$timestamp] [ERROR] Exception: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine() . "\n"; if ($request) { $message .= "Request: " . $request->getMethod() . " " . $request->getUri() . "\n"; } $message .= $e->getTraceAsString() . "\n"; file_put_contents($this->logFile, $message, FILE_APPEND); } }
To use the custom logger, pass an instance to the App
constructor:
$customLogger = new CustomLogger('/path/to/custom.log'); $app = new App(logger: $customLogger);
Important Note: If no logger is explicitly provided, the App
will create a FileLogger
without a log directory, which means no logging will occur. You must configure a logger explicitly to enable logging.
Tests
Erlenmeyer uses PestPHP for testing. Run tests with:
./composer/bin/pest
Use Cases
Erlenmeyer is suitable for a wide range of web applications. Here are some example use cases:
Use Case | Description |
---|---|
Simple REST API | Create an API with endpoints for GET, POST, PUT, and DELETE, returning JSON responses. |
Basic Web Application | Develop an application with routes for HTML pages and session management. |
Static Page Generator | Serve static assets like CSS and JavaScript for landing pages. |
Forms and Uploads | Handle POST forms and file uploads with validation. |
Example REST API:
$app->get('/api/users/[id]', function (Request $req, Response $res, $params) { $id = $params->id; $res->withJson(['id' => $id, 'name' => 'User ' . $id])->send(); });
Example Form Handling:
$app->post('/submit', function (Request $req, Response $res, $params) { $name = $req->getFormDataParam('name', 'Guest'); Session::flash('message', 'Form submitted successfully!'); $res->redirect('/thank-you')->send(); });
License
Erlenmeyer is licensed under the GPLv3. See the LICENSE and the COPYRIGHT files for details.
Reference
App
Method | Description |
---|---|
__construct(?Assets $assets = null, ?string $logDir = null) |
Initializes the application. |
route(string $method, string $route, callable $action, array $middlewares = []) |
Registers a route for an HTTP method. |
get(string $route, callable $action, array $middlewares = []) |
Registers a GET route. |
post(string $route, callable $action, array $middlewares = []) |
Registers a POST route. |
any(string $route, callable $action, array $middlewares = []) |
Registers a route for any HTTP method. |
match(array $methods, string $route, callable $action, array $middlewares = []) |
Registers a route for specified methods. |
redirect(string $from, string $to, bool $permanent = false) |
Registers a redirect. |
set404Handler(callable $action) |
Sets the 404 error handler. |
addMiddleware(callable $middleware) |
Adds a global middleware. |
setExceptionHandler(string $exceptionClass, callable $handler) |
Sets an exception handler. |
run() |
Runs the application. |
Assets
Method | Description |
---|---|
__construct(string $assetsDirectory = "/public", string $assetsRoute = "/assets") |
Initializes the asset manager. |
getAssetsDirectory(): string |
Returns the assets directory. |
getAssetsRoute(): string |
Returns the assets route. |
isAssetRequest(): bool |
Checks if the request is for an asset. |
serveAsset(): bool |
Serves the requested asset. |
Session
Method | Description |
---|---|
static get(string $key, $default = null) |
Gets a session value. |
static set(string $key, $value) |
Sets a session value. |
static has(string $key): bool |
Checks if a session key exists. |
static remove(string $key) |
Removes a session key. |
static flash(string $key, $value) |
Sets a flash message. |
static getFlash(string $key, $default = null) |
Gets and removes a flash message. |
static hasFlash(string $key): bool |
Checks if a flash message exists. |
Request
Method | Description |
---|---|
__construct(?array $server = null, ?array $get = null, ?array $post = null, ?array $files = null, string $inputStream = 'php://input') |
Initializes the request. |
getHeader(string $name): ?string |
Gets a specific header. |
getHeaders(): array |
Gets all headers. |
getMethod(): string |
Gets the HTTP method. |
getUri(): string |
Gets the request URI. |
getQueryParams(): array |
Gets query parameters. |
getFormData(): array |
Gets form data. |
getJson(): mixed |
Gets JSON data from the body. |
getFiles(): array |
Gets uploaded files. |
isAjax(): bool |
Checks if the request is AJAX. |
isSecure(): bool |
Checks if the request is secure (HTTPS). |
Response
Method | Description |
---|---|
__construct(int $statusCode = 200, array $headers = []) |
Initializes the response. |
setStatusCode(int $code): self |
Sets the HTTP status code. |
getStatusCode(): int |
Gets the HTTP status code. |
setHeader(string $name, string $value): self |
Sets an HTTP header. |
removeHeader(string $name): self |
Removes an HTTP header. |
getHeaders(): array |
Gets all headers. |
setContentType(string $contentType): self |
Sets the content type. |
getContentType(): string |
Gets the content type. |
setBody(string $body): self |
Sets the response body. |
getBody(): ?string |
Gets the response body. |
withHtml(string $html): self |
Sets HTML content. |
withTemplate(string $templatePath, array $data = []): self |
Sets content from a template. |
withJson($data, int $options = JSON_PRETTY_PRINT): self |
Sets JSON content. |
withText(string $text): self |
Sets plain text content. |
redirect(string $url, int $statusCode = 302): self |
Sets a redirect. |
withCookie(string $name, string $value, int $expire = 0, string $path = '/', string $domain = '', bool $secure = false, bool $httpOnly = true): self |
Sets a cookie. |
send(): void |
Sends the response. |
isSent(): bool |
Checks if the response has been sent. |
clear(): self |
Clears the response body and headers. |
withError(int $statusCode, string $message = '', ?callable $logger = null): self |
Sets an error response. |
withFile(string $filePath): self |
Sets the response to send a file. |
setCORS(array $options): self |
Configures CORS headers. |