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.

ℹ️ AGPLv3 License: ArtiFrame is open-source. Derivative works you produce can be freely used provided the source code remains open. Copyright notices cannot be removed.

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.

⚠️ Web Server Configuration: Direct Apache/Nginx's document root to the /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).

project-name/ ├── app/ # Infrastructure layer │ ├── ViewControl.php # View (HTML page) bootstrapper │ ├── ApiControl.php # API endpoint bootstrapper │ ├── Database.php # PDO-based database connection │ ├── DotEnv.php # .env reader │ └── R2Manager.php # Cloudflare R2 file manager │ ├── bin/ # ⚠️ Core system — do not edit directly │ ├── SystemMethod.php # API/Backend global helpers │ ├── ViewMethod.php # View/Frontend global helpers │ └── stubs/ # Stub templates used by CLI │ ├── view.stub │ ├── api-standart.stub │ ├── api-switch-case.stub │ └── class.stub │ ├── config/ # Configuration files │ └── app-version.php # APP_VERSION and APP_ENV constants │ ├── public/ # ← Web server's only public directory │ ├── assets/ │ │ ├── css/ # View-specific CSS files │ │ └── js/ # View-specific JS files │ ├── includes/ # Shared components │ │ ├── head.php │ │ ├── header.php │ │ └── footer.php │ ├── api/ # API endpoint files │ └── index.php # Main page │ ├── src/ # Business logic and classes ├── .env # Environment variables (not in git) ├── .env.example # Template — committed to git └── guide.html # This document
🚫 Do not modify the bin/ directory: Files inside 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;
🚫 Never mix ViewControl and ApiControl: If ViewControl is required in an API file, it may return HTML headers instead of JSON headers, corrupting the entire API response. If ApiControl is required in an HTML page, the session won't start and the page will break.

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:

project-name/ ├── app/ (ViewControl.php, ApiControl.php, Database.php, DotEnv.php) ├── bin/ (SystemMethod.php, ViewMethod.php, stubs/) ├── config/ (app-version.php) ├── public/ (index.php, assets/, includes/, api/) ├── src/ ├── .env.example └── guide.html

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

CommandDescriptionExample
version upgrade patchBug fix, minor improvement1.2.3 → 1.2.4
version upgrade minorBackward-compatible new feature1.2.3 → 1.3.0
version upgrade majorBreaking change1.2.3 → 2.0.0
version downgrade patchRevert last patch1.2.4 → 1.2.3
version downgrade minorRevert last minor1.3.0 → 1.2.0
version downgrade majorRevert last major2.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 = '')
💡 Tip: Using $default
The 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).

string escapeUrl(string $url)
<!-- ❌ 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).

FunctionSample OutputDescription
day($date)24Day only
month($date)07Month only (numeric)
year($date)2026Year
timeOnly($date)14:30Hour:Minute
fulldate($date)24.07.2026Full date
formatDate($date, $format)24.07.2026 14:30Custom format
monthName($date, $lang)July / Temmuz / JuliMonth name (by language)
fulldateName($date, $lang)July 24, 2026 / 24 Temmuz 2026Full date (with month name, by language)
timeAgo($date, $lang)5 minutes ago / 5 dakika önceSocial media style (by language)
💡 Understanding Parameters: $format and $lang
  • $format (Format): Used only with the formatDate() 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.

string money(float $amount, string $currency = 'usd')
CodeOutputCurrency
usd$1.250,00US Dollar
eur1.250,00 €Euro
try / tl1.250,00 ₺Turkish Lira
gbp£1.250,00British Pound
jpy1.250,00 ¥Japanese Yen
inr1.250,00 ₹Indian Rupee
rub1.250,00 ₽Russian Ruble
krw1.250,00 ₩South Korean Won
brlR$1.250,00Brazilian Real
aed1.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.

bool verifyCsrf(string $token)
if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
    jsonResponse(['status' => 'error', 'message' => 'Invalid CSRF token.'], 403);
}

System Helpers — Sanitize Functions SystemMethod

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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");
⚠️ Do not use * (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.

⚠️ Redis Server Required: Rate Limiting feature works using Redis via \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.

  1. Create the View File
    artiframe> make:view contact.php
    ✔  public/contact.php
    ✔  public/assets/css/contact.css
    ✔  public/assets/js/contact.js
  2. Create the API Endpoint
    artiframe> make:api standart api/contact/send.php
  3. Code the HTML Form
    Add the following form to public/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>
  4. Connect Fetch API with JavaScript
    In public/assets/js/contact.js file:
    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);
    });
  5. Complete Server Logic (Backend)
    In public/api/contact/send.php file:
    <?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);
✅ Completed! You created a fully secure and standard-compliant form workflow with XSS protection, CSRF verification, HTTP method restriction, and data-js architecture.