Installation & Setup

Overview

JiFramework is a lightweight, plug-and-play PHP framework built for developers who want to move fast without sacrificing structure. The philosophy is simple: include the autoloader, instantiate App, and everything is ready.

Why JiFramework?

  • Zero boilerplate — two lines of code to have a fully working application with database, auth, cache, and logging ready.
  • No template engine required — use plain PHP files as views. No learning curve, no extra dependencies.
  • No CLI tools — everything works out of the box. No commands to run, no code generation steps.
  • Lazy loading — components are only created when you access them. Unused services cost nothing.
  • Powerful built-in components — QueryBuilder, Auth, Session, Cache, Validation, Rate Limiting, Encryption, File Manager, Router, Model layer, and more.
  • Flexible routing — optional URL router with file-based or closure handlers. Use it or skip it.
  • Production ready — structured logging, error handling, CSRF protection, IP/country blocking, and rate limiting included.

Requirements:

  • PHP 7.4 or higher
  • Composer (for autoloading)
  • MySQL / MariaDB (optional — only needed if using database features)

Installation via Composer

Install JiFramework using Composer:

composer require jahurul1/jiframework

After installation, the framework lives in vendor/jahurul1/jiframework/. All classes are autoloaded automatically via PSR-4.

Bootstrap

The entire bootstrap is two lines:

<?php
require __DIR__ . '/vendor/autoload.php';
$app = new App();

App::__construct() automatically:

  • Detects the project root directory
  • Loads config/jiconfig.php if it exists
  • Initialises the error handler and logger
  • Applies rate limiting and access control

No manual Config::initialize() call is needed.

Configuration File

Copy the example config to get started:

cp vendor/jiframework/jiframework/config/jiconfig.example.php config/jiconfig.php

The file must be placed at config/jiconfig.php relative to your project root (next to composer.json).

<?php
// config/jiconfig.php
return [
    'app_mode'   => 'development',   // 'development' or 'production'
    'timezone'   => 'UTC',
    'database'   => [
        'host'     => 'localhost',
        'dbname'   => 'my_database',
        'username' => 'root',
        'password' => '',
        'charset'  => 'utf8mb4',
    ],
];

If the file is absent, the framework uses safe defaults and logs a warning to storage/Logs/app.log.

Directory Structure

Recommended project layout:

your-project/
|-- config/
|   `-- jiconfig.php        (your configuration)
|-- models/                 (optional: auto-loaded at boot)
|   `-- User.php
|-- pages/                  (optional: router file handlers)
|   `-- home.php
|-- storage/                (auto-created by the framework)
|   |-- Cache/
|   |-- Logs/
|   `-- RateLimit/
|-- vendor/
|-- composer.json
`-- index.php               (your entry point)

The storage/ directory is created automatically. You do not need to create it manually.

All Configuration Keys

Full list of supported jiconfig.php keys. You only need to include the keys you want to override — everything else falls back to the framework defaults.

App

  • app_mode(string) development or production. Default: development
  • admin_email(string) Admin contact address. Default: [email protected]
  • timezone(string) PHP timezone identifier. Default: UTC
  • error_template(string|null) Absolute path to a custom HTML error page template. The variables $errorCode and $errorMessage are available inside it. Default: null (uses built-in page)

Database

  • database(array) Primary connection: host, database, username, password, driver, charset, collation, options
  • databases(array) Named extra connections keyed by name. Access via $app->db('name')

Router

  • router_enabled(bool) Enable the URL router. Default: false
  • router_base_path(string) Base path for subdirectory installs, e.g. /myapp. Default: ""

Cache

  • cache_driver(string) file or sqlite. Default: file
  • cache_path(string) Custom path for file cache. Default: storage/Cache/FileCache/
  • cache_database_path(string) Custom path for SQLite cache file. Default: storage/Cache/DatabaseCache/ji_sqlite_cache.db

File Uploads

  • upload_directory(string) Custom upload path. Default: storage/Uploads/
  • allowed_image_types(array) Accepted MIME types. Default: [image/jpeg, image/png, image/gif]
  • max_file_size(int) Max upload size in bytes. Default: 5242880 (5 MB)
  • image_max_dimension(int) Max image width/height in px. Default: 800

Rate Limiting

  • rate_limit_enabled(bool) Toggle rate limiting. Default: false
  • rate_limit_requests(int) Max requests per window. Default: 500
  • rate_limit_time_window(int) Window length in seconds. Default: 60
  • rate_limit_ban_enabled(bool) Ban repeat offenders. Default: true
  • rate_limit_ban_duration(int) Ban duration in seconds. Default: 3600
  • rate_limit_database_path(string) Custom path for rate limit SQLite file. Default: storage/RateLimit/rate_limit.db

Logging

  • log_enabled(bool) Enable file logging. Default: true
  • log_level(string) Minimum level: DEBUG | INFO | NOTICE | WARNING | ERROR | CRITICAL | ALERT | EMERGENCY. Default: DEBUG
  • log_file_name(string) Log filename. Default: app.log
  • log_file_path(string) Custom log directory. Default: storage/Logs/
  • log_max_file_size(int) Max log file size in bytes before rotation. Default: 5242880 (5 MB)
  • log_max_files(int) Number of rotated log files to keep. Default: 20

Access Control

  • ip_blocking_enabled(bool) Block specific IPs. Default: false
  • ip_block_list_path(string) Path to IP block list JSON. Default: storage/AccessControl/ip_block_list.json
  • country_blocking_enabled(bool) Block by country code. Default: false
  • country_block_list_path(string) Path to country block list JSON. Default: storage/AccessControl/country_block_list.json
  • allow_vpn_proxy(bool) Allow VPN/proxy traffic. Default: true
  • proxycheck_api_key(string) API key for proxycheck.io. Default: ""

IP Detection

These two keys control how the framework resolves the client IP address on every request. The resolved IP is used across the entire framework — by the rate limiter, access control, and any call to $app->request->getClientIp().

  • trusted_proxies(array) IP addresses of trusted reverse proxies (load balancers, CDNs, Nginx). When REMOTE_ADDR matches one of these, the real client IP is read from X-Forwarded-For. Leave empty (default) when your PHP server receives connections directly. Default: []
  • debug_ip(string|null) Override the detected client IP for local development. Useful for testing rate limiting and access control on localhost where REMOTE_ADDR is always 127.0.0.1. Only honoured when app_mode = development — silently ignored in production. Default: null
// Running behind Nginx or a load balancer:
'trusted_proxies' => ['127.0.0.1', '10.0.0.1'],

// Testing IP-based features on localhost:
'app_mode' => 'development',
'debug_ip'  => '203.0.113.42',

Multi-Language

  • multi_lang(bool) Enable multi-language support. Default: false
  • multi_lang_method(string) Detection method: url or cookie. Default: url
  • multi_lang_default_lang(string) Fallback language code. Default: en
  • multi_lang_key(string) URL/cookie key name. Default: lang
  • multi_lang_dir(string) Custom translations directory. Default: lang/

Session & CSRF

  • session_user_key(string) Session key for user auth data. Default: _ji_user_session
  • session_admin_key(string) Session key for admin auth data. Default: _ji_admin_session
  • csrf_token_key(string) Session key for CSRF token store. Default: _ji_csrf_token
  • flash_message_key(string) Session key for flash messages. Default: _ji_flash_messages
  • csrf_token_expiry(int) CSRF token lifetime in seconds. Default: 43200 (12 hours)
  • csrf_token_limit(int) Max simultaneous CSRF tokens per session. Default: 100
  • csrf_token_length(int) Random bytes used to generate each token. Default: 32

Auth Tables

  • auth_admin_table(string) Admin table name. Default: admin
  • auth_user_table(string) Users table name. Default: users
  • auth_token_table(string) Remember-me tokens table. Default: tokens

.htaccess Configuration

The framework ships with a ready-to-use .htaccess file at the project root. The right configuration depends on whether you are using router mode or file-based mode.

Router mode ON — recommended .htaccess

When router_enabled = true, all unknown requests must be funnelled through index.php. The file below handles routing and security in one place. This is the file that ships with the framework — you do not need to change anything.

# Disable directory listing
Options -Indexes

RewriteEngine On

# Block direct access to sensitive directories
RewriteRule ^(storage|src|config)/ - [F,L]

# Send all non-file, non-directory requests to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

# Block SQLite database files
<FilesMatch "\.db$">
    Require all denied
</FilesMatch>

# Block cache files
<FilesMatch "\.cache$">
    Require all denied
</FilesMatch>

# Block hidden files (.env, .gitignore, .htpasswd, etc.)
<FilesMatch "^\.(?!htaccess)">
    Require all denied
</FilesMatch>

Static assets (CSS, JS, images) are served directly by Apache — only requests for paths that do not match a real file or directory are forwarded to index.php.

Router mode OFF — security-only .htaccess

When router_enabled = false (the default), PHP files are accessed directly by the browser. You do not need the routing rewrite rules, but you should still protect sensitive files and directories. Replace the shipped .htaccess with this version:

# Disable directory listing
Options -Indexes

RewriteEngine On

# Block direct access to sensitive directories
RewriteRule ^(storage|src|config)/ - [F,L]

# Block SQLite database files
<FilesMatch "\.db$">
    Require all denied
</FilesMatch>

# Block cache files
<FilesMatch "\.cache$">
    Require all denied
</FilesMatch>

# Block hidden files (.env, .gitignore, .htpasswd, etc.)
<FilesMatch "^\.(?!htaccess)">
    Require all denied
</FilesMatch>
What each rule does
RuleWhy it matters
Options -Indexes Disables directory listing. Without this, Apache shows folder contents when no index file exists — exposing filenames, structure, and potentially sensitive files.
^(storage|src|config)/ — [F,L] Blocks direct browser access to storage/ (logs, uploads, rate-limit DB), src/ (framework source), and config/ (jiconfig.php with database credentials). Returns 403 Forbidden.
\.db$ Blocks download of SQLite .db files used by the rate limiter. Without this rule, anyone could download the database file directly.
\.cache$ Blocks access to .cache files written by the file cache driver.
^\.(?!htaccess) Blocks all hidden files (those starting with a dot) — .env, .gitignore, .htpasswd, etc. Critical for preventing credential leaks. .htaccess itself is excluded so Apache can read the rules without triggering a 403 loop.
Nginx equivalent

If your server runs Nginx instead of Apache, add the following to your server block:

server {
    # ... other config ...

    # Block sensitive directories
    location ~* ^/(storage|src)/ {
        deny all;
    }

    # Block sensitive file types
    location ~* \.(db|cache)$ {
        deny all;
    }

    # Block hidden files
    location ~ /\. {
        deny all;
    }

    # Router mode ON only -- send all requests to index.php
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
}