ArtiFrame Documentation
A lightweight, fast ecosystem for scalable native PHP projects, built with zero external dependencies, strict rules, and a powerful CLI. Free from dependency bloat and Composer package soup, allowing you to focus on your business logic.
Introduction & Philosophy
ArtiFrame is based on the "Convention over Configuration" principle. The power of a framework comes not from the richness of the tools it offers, but from the consistency of the structure it establishes.
Zero Overhead
Zero dependencies on Composer packages, framework core bloat, or 3rd-party libraries. Every line of code is yours—free from bloat and unnecessary abstraction layers.
Security First
XSS protection, CSRF verification, sanitization against SQL injection, and HTTP method control come built-in by default. Security is not an option, but a standard.
Strict Rule Set
A junior developer joining the project grasps the data-js architecture and directory structure within minutes. Team alignment is guaranteed at the framework level.
CLI First
No manual creation of View, API, or class files. The CLI generates them from stub templates, links assets, and keeps the project consistent.
Installation
ArtiFrame CLI is installed as a global PHP tool. Install once, use across all projects.
1. Install CLI Tool Globally
# Global installation via NPM
npm install -g @artilingo/artiframe-cli
# Verify installation
artiframe
2. Interactive Shell
Type simply artiframe in the terminal and press Enter. The CLI does not exit; an interactive shell opens that continuously listens for commands:
==================================================
ArtiFrame CLI Interactive Shell v1.0.0
==================================================
Type 'help' for commands, or 'exit' to quit.
artiframe>
3. Start a New Project
artiframe> new my-project
This command creates the my-project/ directory and copies the entire skeleton structure into it: app/, bin/, config/, public/, src/, .env.example, and the initial index.php page.
4. Environment Configuration
cp .env.example .env
Open your .env file and fill in database and application configuration. This file is never committed to version control.
/public/ folder. Other directories must never be exposed externally.
Directory Structure
The architecture generated when a project is initialized ensures a clear Separation of Concerns (SoC).
bin/ are the core of the framework. Project-specific business logic is not added here. Classes and services you add belong under src/, and infrastructure components belong under app/.
Bootstrapper Architecture Critical
ArtiFrame uses two completely independent bootstrappers. This architecture fundamentally prevents HTML header issues and security vulnerabilities.
View ViewControl.php
Used for HTML pages (view files). Starts session, loads ViewMethod.
// public/profile.php — AT THE VERY TOP, before outputting HTML
<?php
require_once $_SERVER['DOCUMENT_ROOT']
. '/../app/ViewControl.php';
use Bin\ViewMethod;
?>
<!DOCTYPE html>
...
API ApiControl.php
Used for API endpoint files. Sets JSON header, checks HTTP method, loads SystemMethod.
// public/api/user/get.php
<?php
// $allowedMethods MUST be defined before require
$allowedMethods = ['GET'];
require_once $_SERVER['DOCUMENT_ROOT']
. '/../app/ApiControl.php';
use Bin\SystemMethod;
Rule Set Standard
Rule 1: data-js Architecture Critical
JavaScript events can never be listened to via a class or id. These are visual / style identifiers. All JS interactions are managed using the data-js attribute. When CSS deletes a class, JavaScript never breaks.
<!-- ❌ Anti-Pattern — not supported -->
<button id="submitBtn" class="btn">Submit</button>
// JS: document.getElementById('submitBtn').addEventListener(...)
<!-- ✅ ArtiFrame Standard -->
<button class="btn btn-primary" data-js="login-submit">Submit</button>
// JS: document.querySelector('[data-js="login-submit"]').addEventListener(...)
Rule 2: Theme Architecture
Dark/Light mode and themes are managed via data-theme and data-mode attributes on the <html> tag. Body classes are not used.
<!-- HTML opening tag coming from view.stub template -->
<html lang="en" data-theme="default" data-mode="light">
/* Theme definition in app.css */
html[data-theme="default"][data-mode="dark"] {
--bg-color: #0b0c0e;
--text-main: #ffffff;
}
html[data-theme="default"][data-mode="light"] {
--bg-color: #ffffff;
--text-main: #111111;
}
Rule 3: Secure Data Flow
All data coming from the database is wrapped with display() before being output to the DOM. All data arriving at the API is cleaned with sanitizeString() or sanitizeInt() before processing.
Rule 4: Error Management with APP_ENV
The APP_ENV value in the .env file determines error visibility. In Production, error messages are not displayed to the user.
// config/app-version.php
define('APP_ENV', (int)$_ENV['APP_ENV']); // 1=Debug, 0=Production
if (APP_ENV === 1) {
ini_set('display_errors', 1);
error_reporting(E_ALL);
} else {
ini_set('display_errors', 0);
}
CLI Ecosystem
ArtiFrame CLI opens an interactive shell when artiframe is typed in the terminal. All commands run within this shell. Commands can also be executed as single-shot runs.
# Interactive mode (recommended)
artiframe
artiframe> make:view admin/users.php
# Single-shot mode
artiframe make:view admin/users.php
new CLI Command
Creates a new ArtiFrame project. Generates the entire directory skeleton, bootstrapper files, and initial index page.
artiframe> new project-name
Generated structure:
make:view CLI Command
Creates a new page (view) file and its dedicated CSS/JS assets. Assets are automatically linked to the page and cache-busting is applied via ?v=APP_VERSION.
artiframe> make:view admin/users.php
Generated files:
✔ public/admin/users.php
✔ public/assets/css/admin/users.css
✔ public/assets/js/admin/users.js
The generated view file comes with ViewControl required at the top, head/header/footer includes added, and CSS/JS links connected with cache-busting.
make:api CLI Command
Creates an API endpoint file by choosing from two different templates. Every new API file comes pre-configured with the $allowedMethods variable and ApiControl.php require statement.
standart — Single Action API
For endpoints performing a single task (login, submit, delete). Business logic is written directly.
artiframe> make:api standart api/auth/login.php
<?php
$allowedMethods = ['POST']; // Accept POST only
require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';
use Bin\SystemMethod;
// Business logic here...
jsonResponse(['status' => 'success'], 200);
switch-case — Multi-Action API
A structure managing CRUD operations for a module in a single endpoint. The action to perform is determined by the action parameter.
artiframe> make:api switch-case api/user/manage.php
<?php
$allowedMethods = ['POST'];
require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';
use Bin\SystemMethod;
$action = sanitizeString($_POST['action'] ?? '');
switch ($action) {
case 'create':
jsonResponse(['status' => 'success', 'message' => 'Created.'], 200);
break;
case 'update':
jsonResponse(['status' => 'success', 'message' => 'Updated.'], 200);
break;
case 'delete':
jsonResponse(['status' => 'success', 'message' => 'Deleted.'], 200);
break;
default:
jsonResponse(['status' => 'error', 'message' => 'Invalid action.'], 400);
}
make:class CLI Command
Creates a new PHP class file with namespace and class boilerplate ready.
artiframe> make:class classes/EmailService.php
version CLI Command
Updates the version number in config/app-version.php according to Semantic Versioning (SemVer) rules. Version format: MAJOR.MINOR.PATCH
| Command | Description | Example |
|---|---|---|
| version upgrade patch | Bug fix, minor improvement | 1.2.3 → 1.2.4 |
| version upgrade minor | Backward-compatible new feature | 1.2.3 → 1.3.0 |
| version upgrade major | Breaking change | 1.2.3 → 2.0.0 |
| version downgrade patch | Revert last patch | 1.2.4 → 1.2.3 |
| version downgrade minor | Revert last minor | 1.3.0 → 1.2.0 |
| version downgrade major | Revert last major | 2.0.0 → 1.0.0 |
artiframe> version upgrade minor
✔ Version updated from 1.2.0 → 1.3.0.
View Helpers — display() ViewMethod
Applies mandatory XSS protection when outputting data from database or user input to the DOM. Instead of throwing an error on null or empty values, it returns a default value.
string display(mixed $data, string $default = '')$defaultThe second parameter (
$default) is the fallback text rendered when data from the database is empty (null, false, empty string). For instance, for a user without a name entered, if you use display($name, 'Anonymous'), your page presents meaningful content instead of awkward blanks.
<!-- ❌ Unsafe — creates XSS vulnerability -->
<h1><?= $user['name'] ?></h1>
<!-- ✅ ArtiFrame Safe Usage -->
<h1><?= display($user['name'], 'Anonymous User') ?></h1>
View Helpers — escapeUrl() ViewMethod
Used when rendering links received from users (e.g. profile websites) inside <a href="..."> or <img src="...">. Prevents execution of code via links by neutralizing dangerous payloads like javascript:alert(1) (Stored XSS).
<!-- ❌ Unsafe — JS code can be leaked via URL -->
<a href="<?= $user['website'] ?>">Visit Site</a>
<!-- ✅ ArtiFrame Safe Usage -->
<a href="<?= escapeUrl($user['website']) ?>">Visit Site</a>
View Helpers — csrfField() ViewMethod
Adds a hidden token field to HTML forms against CSRF attacks. Mandatory to use on every page containing a POST form.
string csrfField()<form action="/api/save.php" method="POST">
<?= csrfField() ?> <!-- Mandatory for security -->
<input type="text" name="name">
<button type="submit" data-js="save-btn">Save</button>
</form>
View Helpers — Date Functions ViewMethod
Processes date strings in Y-m-d H:i:s format or UNIX timestamp values from the database. All textual outputs are localized according to language (tr, en, de, fr, es).
| Function | Sample Output | Description |
|---|---|---|
| day($date) | 24 | Day only |
| month($date) | 07 | Month only (numeric) |
| year($date) | 2026 | Year |
| timeOnly($date) | 14:30 | Hour:Minute |
| fulldate($date) | 24.07.2026 | Full date |
| formatDate($date, $format) | 24.07.2026 14:30 | Custom format |
| monthName($date, $lang) | July / Temmuz / Juli | Month name (by language) |
| fulldateName($date, $lang) | July 24, 2026 / 24 Temmuz 2026 | Full date (with month name, by language) |
| timeAgo($date, $lang) | 5 minutes ago / 5 dakika önce | Social media style (by language) |
$format and $lang
$format(Format): Used only with theformatDate()function. Accepts standard PHP date letters (e.g.'d/m/Y'➔ 24/07/2026, or'H:i'➔ 15:30). Allows building custom date formats when pre-built functions (day, year, etc.) are insufficient.$lang(Language): Used in functions containing text (month names, "ago" words) in their output. If left empty, the system defaults to'tr'(Turkish). For multi-language projects, simply pass the language code (tr, en, de, fr, es) as the second parameter. (e.g.timeAgo($date, 'en')➔ 5 minutes ago)
<!-- Assuming a UNIX timestamp retrieved with time() function -->
<?php $date = time(); ?>
<!-- Full date by language -->
<span><?= fulldateName($date, 'en') ?></span>
<!-- Output: July 24, 2026 -->
<!-- Social media style time display -->
<span><?= timeAgo($date, 'en') ?></span>
<!-- Output: 5 minutes ago -->
<span><?= timeAgo($date, 'de') ?></span>
<!-- Output: vor 5 Minuten -->
View Helpers — Text Formatting ViewMethod
truncate($text, $length, $append)
string truncate(string $text, int $length = 100, string $append = '...')
Truncates long texts (e.g. blog summaries) at the specified character limit without cutting off the last word, and appends the specified string to the end.
<p><?= truncate($post['content'], 160, '...') ?></p>
View Helpers — money() ViewMethod
Formats amount with currency symbol. Currency code (ISO) automatically determines symbol placement. Default currency is usd.
| Code | Output | Currency |
|---|---|---|
| usd | $1.250,00 | US Dollar |
| eur | 1.250,00 € | Euro |
| try / tl | 1.250,00 ₺ | Turkish Lira |
| gbp | £1.250,00 | British Pound |
| jpy | 1.250,00 ¥ | Japanese Yen |
| inr | 1.250,00 ₹ | Indian Rupee |
| rub | 1.250,00 ₽ | Russian Ruble |
| krw | 1.250,00 ₩ | South Korean Won |
| brl | R$1.250,00 | Brazilian Real |
| aed | 1.250,00 د.إ | UAE Dirham |
<span><?= money($product['price'], 'try') ?></span>
<!-- Output: 1.250,00 ₺ -->
<span><?= money($product['price'], 'usd') ?></span>
<!-- Output: $1.250,00 -->
System Helpers — jsonResponse() SystemMethod
Sets JSON header, sets HTTP status code, outputs payload safely with json_encode, and terminates script execution. All API responses must be delivered through this function.
void jsonResponse(array $data, int $statusCode = 200)// Successful response
jsonResponse(['status' => 'success', 'data' => $user], 200);
// Error response
jsonResponse(['status' => 'error', 'message' => 'Unauthorized access.'], 401);
System Helpers — verifyCsrf() SystemMethod
Verifies that incoming POST request originates from a legitimate form. Returns false if csrfField() is missing or token is invalid.
if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
jsonResponse(['status' => 'error', 'message' => 'Invalid CSRF token.'], 403);
}
System Helpers — Sanitize Functions SystemMethod
| Function | Description |
|---|---|
| sanitizeInt($value) | Strips all letters, symbols, and commas, keeping integers only. Used for IDs or limits. |
| sanitizeFloat($value) | Cleans everything except fractional (decimal) numbers. Used for monetary amounts or metrics. |
| sanitizeString($value) | Destroys all HTML and PHP tags (<script>, <iframe>, etc.) to prevent XSS and derived attacks. Leaves safe plain text. |
| sanitizeEmail($email) | Filters all invalid and dangerous characters that do not conform to email format. |
$id = sanitizeInt($_POST['id'] ?? 0);
$name = sanitizeString($_POST['name'] ?? '');
$email = sanitizeEmail($_POST['email'] ?? '');
System Helpers — HTTP & Request SystemMethod
| Function | Description |
|---|---|
| isPost() | Is request POST? |
| isGet() | Is request GET? |
| isPut() | Is request PUT? |
| isDelete() | Is request DELETE? |
| isAjax() | Is request via XHR/Fetch API? |
| getClientIp() | Real IP address (Cloudflare & Proxy supported) |
| redirect($url) | Redirects to given URL and terminates script |
// Block direct browser access
if (!isAjax()) {
jsonResponse(['error' => 'Direct access restricted.'], 403);
}
// Redirect if no session
if (!isset($_SESSION['user'])) {
redirect('/login.php');
}
$ip = getClientIp(); // Real IP behind Cloudflare as well
System Helpers — Security SystemMethod
| Function | Description |
|---|---|
| generateCsrf() | Generates new CSRF token and saves to session |
| generateToken($length) | Cryptographically secure random hex string (password reset, API key, etc.) |
| hashPassword($password) | Hashes password with bcrypt |
| verifyPassword($password, $hash) | Verifies password against hash |
// Secure token generation (API key, email verification link, etc.)
$token = generateToken(32);
// ⚠️ Note: 32 bytes of data generated, but converted to hex format
// resulting in a string exactly twice as long, i.e., 64 characters.
// Password registration
$hash = hashPassword($_POST['password']);
// Password verification
if (!verifyPassword($_POST['password'], $user['password_hash'])) {
jsonResponse(['error' => 'Incorrect password.'], 401);
}
API — HTTP Method Control ApiControl
The $allowedMethods array MUST be defined before ApiControl require. ApiControl reads this array and automatically rejects requests from non-allowed methods with 405 Method Not Allowed.
// Endpoint accepting only GET and POST
$allowedMethods = ['GET', 'POST'];
require_once ... . '/../app/ApiControl.php';
// Endpoint accepting only DELETE
$allowedMethods = ['DELETE'];
require_once ... . '/../app/ApiControl.php';
Preflight OPTIONS requests automatically return 200 for CORS and script terminates — no manual intervention needed.
API — CORS ApiControl (Commented Out)
If you want to allow API access from other domains or mobile apps, activate the CORS block inside ApiControl.php.
// Inside ApiControl.php — activate by uncommenting
header("Access-Control-Allow-Origin: https://your-domain.com");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
* (all domains) in Production environment. Specify domain address explicitly.
API — Rate Limiting ApiControl (Commented Out)
To prevent malicious users or bots from flooding the API, you can activate the Rate Limiting block inside ApiControl.php. Default rule: 60 requests / IP in 60 seconds.
\Src\Service\RedisService::getInstance() class. Before enabling this code block, make sure you have installed a Redis service in your project and created a connection class under src/Service/.
// If more than 60 requests arrive from the same IP in 60 seconds:
http_response_code(429); // Too Many Requests
echo json_encode(['error' => 'Too many requests. Please wait.']);
Full Workflow: Contact Form (End-to-End)
Let's walk step-by-step through developing a Contact Form feature from scratch using ArtiFrame standards.
-
Create the View File
artiframe> make:view contact.php ✔ public/contact.php ✔ public/assets/css/contact.css ✔ public/assets/js/contact.js -
Create the API Endpoint
artiframe> make:api standart api/contact/send.php -
Code the HTML Form
Add the following form topublic/contact.php:<form action="/api/contact/send.php" method="POST"> <?= csrfField() ?> <input type="text" name="name" placeholder="Your Name"> <input type="email" name="email" placeholder="Email"> <textarea name="message" placeholder="Your Message"></textarea> <button type="submit" data-js="contact-send">Send</button> </form> -
Connect Fetch API with JavaScript
Inpublic/assets/js/contact.jsfile:document.querySelector('[data-js="contact-send"]').addEventListener('click', async (e) => { e.preventDefault(); const formData = new FormData(e.target.closest('form')); const res = await fetch('/api/contact/send.php', { method: 'POST', body: formData }); const data = await res.json(); console.log(data); }); -
Complete Server Logic (Backend)
Inpublic/api/contact/send.phpfile:<?php $allowedMethods = ['POST']; require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php'; use Bin\SystemMethod; // 1. CSRF Verification if (!verifyCsrf($_POST['csrf_token'] ?? '')) { jsonResponse(['error' => 'Invalid token.'], 403); } // 2. Sanitize Data $name = sanitizeString($_POST['name'] ?? ''); $email = sanitizeEmail($_POST['email'] ?? ''); $message = sanitizeString($_POST['message'] ?? ''); // 3. Business Logic (Send mail, save to DB, etc.) // ... // 4. Send Response jsonResponse(['status' => 'success', 'message' => 'Your message has been received.'], 200);