vercy / zieex
A lightweight AJAX-first PHP framework with MVC, REST API, and modern features
Package info
Type:project
pkg:composer/vercy/zieex
v1.0.1
2026-07-06 12:26 UTC
Requires
- php: >=8.2
README
A lightweight, AJAX-first PHP framework with zero dependencies beyond PHP 8.2+.
Requirements
- PHP 8.2+
- MySQL 8+ / PostgreSQL / SQLite
- Composer
- Node.js + npm (optional — only for Tailwind CSS)
Installation
Via Composer (recommended)
composer create-project zieex/framework my-app
cd my-app
php zx install
php zx serve
The install command walks you through setup interactively in your terminal.
Via Git (browser installer)
git clone https://github.com/zieex/framework my-app
cd my-app
composer install
Then visit http://your-domain.com — the browser installer launches automatically.
Directory Structure
my-app/
├── app/
│ ├── Controllers/ # Your controllers
│ └── Models/ # Your models
├── config/ # app, database, mail, middleware
├── core/ # Framework internals (do not edit)
│ ├── Assets/ # interaction.js (auto-served)
│ ├── Auth/ # Auth + CSRF
│ ├── Cache/ # File cache
│ ├── Console/ # CLI commands
│ ├── Database/ # DB, QueryBuilder, Model
│ ├── Flash/ # Flash messages
│ ├── Http/ # Request, Response, Controller, UrlSigner
│ ├── Install/ # Browser + CLI installer
│ ├── Mail/ # Multi-driver mailer
│ ├── Middleware/ # Auth, Admin, CSRF, Throttle, Guest
│ ├── RateLimit/ # File-backed rate limiter
│ ├── Router/ # Router + Route
│ ├── Storage/ # File storage helper
│ ├── Template/ # View / template engine
│ └── Validation/ # Validator
├── database/
│ └── migrations/ # Migration files
├── public/
│ ├── assets/css/ # zieex.css + compiled Tailwind (optional)
│ ├── storage/ # Public uploaded files
│ ├── .htaccess
│ └── index.php
├── resources/
│ ├── css/app.css # Tailwind input (optional)
│ └── views/ # .ze.php template files
│ ├── auth/
│ ├── dashboard/
│ ├── errors/
│ ├── home/
│ └── layouts/
├── routes/
│ ├── web/index.php # Web routes
│ └── api/index.php # API routes (optional)
├── storage/
│ ├── cache/ # View + data cache
│ ├── logs/ # Daily log files
│ ├── private/ # Private file uploads
│ ├── rate/ # Rate limit counters
│ └── sessions/ # Session files (if file driver)
├── .env # Environment config (gitignored)
├── .env.example # Template
├── composer.json
├── index.php # Entry point
└── zx # CLI tool
CLI
# Setup php zx install # Interactive installer php zx serve # Dev server at http://127.0.0.1:8000 # Scaffolding php zx make:controller UserController php zx make:model User php zx make:migration create_posts_table # Database php zx migrate php zx migrate:rollback # Tailwind CSS (optional) php zx tailwind:install php zx tailwind:build php zx tailwind:build --watch # Maintenance php zx cache:clear php zx log:clear
Routing
// routes/web/index.php Router::get('/', [HomeController::class, 'index']); Router::post('/form', [HomeController::class, 'submit']); // Route parameters Router::get('/users/{id}', [UserController::class, 'show']); // Middleware Router::get('/profile', [UserController::class, 'profile']) ->middleware('auth'); // Rate limiting Router::post('/login', [AuthController::class, 'login']) ->rateLimit(5, 60); // Groups Router::group(['prefix' => '/admin', 'middleware' => ['auth', 'admin']], function () { Router::get('/dashboard', [AdminController::class, 'index']); }); // Resource routes (index, create, store, show, edit, update, destroy) Router::resource('/posts', PostController::class);
Views
Templates use .ze.php extension and support Blade-like directives.
{{-- Extend a layout --}}
@extends('layouts.main')
@section('content')
{{-- Escaped output --}}
<h1>{{ $title }}</h1>
{{-- Unescaped output --}}
{!! $htmlContent !!}
{{-- Directives --}}
@if($user)
<p>Hello, {{ $user['name'] }}</p>
@else
<p>Guest</p>
@endif
@foreach($items as $item)
<li>{{ $item['name'] }}</li>
@endforeach
{{-- Flash messages: alert | toast | modal --}}
@flash('success')
@flash('error', 'toast')
@flash('info', 'modal')
{{-- Auth checks --}}
@auth
<a href="/logout">Logout</a>
@endauth
@guest
<a href="/login">Login</a>
@endguest
{{-- CSRF field --}}
@csrf
{{-- Include partial --}}
@include('partials.nav')
{{-- Raw JSON --}}
@json($data)
@endsection
Controllers
namespace App\Controllers; use Zieex\Http\Controller; use Zieex\Http\Request; use Zieex\Http\Response; class PostController extends Controller { public function index(Request $request): Response { return $this->view('posts.index', ['posts' => []]); } public function store(Request $request): Response { $data = $this->validate($request, [ 'title' => 'required|min:3|max:255', 'content' => 'required|min:10', ]); // ... save $data flash('success', 'Post created.'); return response()->json(['success' => true, 'redirect' => '/posts']); } public function show(Request $request): Response { $id = $request->param('id'); $post = DB::table('posts')->find($id); if (!$post) abort(404); return $this->view('posts.show', ['post' => $post]); } }
Database
// Fluent query builder $users = DB::table('users') ->where('role', 'admin') ->where('status', 'active') ->orderBy('created_at', 'DESC') ->limit(10) ->get(); // Single row $user = DB::table('users')->find($id); $user = DB::table('users')->where('email', $email)->first(); // Insert DB::table('users')->insert([ 'uuid' => uuid(), 'email' => 'user@example.com', ]); // Update — requires where() or throws DB::table('users')->where('id', $id)->update(['name' => 'John']); // Delete — requires where() or throws DB::table('users')->where('id', $id)->delete(); // Pagination $result = DB::table('posts') ->where('status', 'active') ->orderBy('created_at', 'DESC') ->paginate(15, $page); // $result['data'], $result['total'], $result['last_page'], $result['current_page'] // Raw SQL $rows = DB::raw('SELECT * FROM posts WHERE id > ?', [5])->fetchAll(PDO::FETCH_ASSOC); // Transactions DB::transaction(function () use ($data) { DB::table('orders')->insert($data['order']); DB::table('order_items')->insert($data['items']); }); // Joins $orders = DB::table('orders') ->leftJoin('users', 'users.id', '=', 'orders.user_id') ->select('orders.*', 'users.name AS user_name') ->get();
Flash Messages
// In controller — set flash('success', 'Record saved.'); flash('error', 'Something went wrong.'); flash('info', 'Your session expires soon.'); flash('warning', 'This action is irreversible.'); // In view — render (style: alert | toast | modal) @flash('success') // inline alert @flash('error', 'toast') // top-right toast, 5s auto-dismiss @flash('info', 'modal') // centered modal, user must dismiss @flash('warning', 'toast')
use Zieex\Mail\Mail; Mail::driver('smtp') ->to('user@example.com') ->subject('Welcome!') ->html('<h1>Hello!</h1>') ->send(); // Use a view as email body Mail::driver('smtp') ->to($user['email']) ->subject('Order Confirmed') ->view('emails.order-confirmed', ['order' => $order]) ->send(); // Change driver at runtime Mail::driver('log')->to(...)->subject(...)->html(...)->send(); Mail::driver('resend')->to(...)->subject(...)->html(...)->send();
Mail drivers: smtp, log, resend, sendmail, api
Auth
use Zieex\Auth\Auth; // Login Auth::attempt($email, $password); Auth::attemptByUsername($username, $password); Auth::login($userArray, $remember); // Check Auth::check(); // bool Auth::guest(); // bool Auth::user(); // array|null Auth::id(); // int|string|null // Logout Auth::logout(); // Password Auth::hash($password); Auth::verify($password, $hash);
Cache
use Zieex\Cache\Cache; Cache::set('key', $value, 3600); // TTL in seconds Cache::get('key', $default); Cache::has('key'); Cache::forget('key'); Cache::forever('key', $value); // Remember pattern $users = Cache::remember('all_users', 300, fn() => DB::table('users')->get()); Cache::flush(); // clear everything Cache::clearExpired(); // clear only expired
Events
use Zieex\Event; // Register listener Event::listen('order.created', function (array $order) { Mail::driver('smtp') ->to($order['email']) ->subject('Order Confirmed') ->html('...') ->send(); }); // Fire event Event::fire('order.created', $orderData); // One-time listener Event::once('user.registered', fn($user) => sendWelcomeEmail($user));
Signed URLs
use Zieex\Http\UrlSigner; // Generate $url = UrlSigner::sign('/reset-password?email=user@example.com', 3600); // Verify if (!UrlSigner::verify($url)) { abort(403, 'Invalid or expired link.'); }
Storage
use Zieex\Storage\Storage; // Public files (web-accessible at /storage/...) $path = Storage::put('uploads/image.jpg', $contents, public: true); $url = Storage::url($path); // '/storage/uploads/image.jpg' // Private files (not web-accessible) Storage::put('invoices/inv-001.pdf', $pdfContents, public: false); Storage::exists('uploads/image.jpg', public: true); Storage::delete('uploads/image.jpg', public: true); Storage::get('invoices/inv-001.pdf');
Config
config('app.name'); // 'Zieex App' config('mail.from.address'); // 'hello@example.com' config('database.host'); // '127.0.0.1' config('missing.key', 'default');
Helpers
uuid() // RFC 4122 UUID v4 now() // 'Y-m-d H:i:s' slugify($text) // 'hello-world' old('field') // previous input value (after validation fail) errors('field') // field error HTML span asset('/img/logo.png') url('/about') dd($var) // dump and die dump($var) // dump and continue abort(404) abort(403, 'Forbidden')
Tailwind CSS (Optional)
# Install once php zx tailwind:install # Build for production php zx tailwind:build # Watch during development php zx tailwind:build --watch
The compiled public/assets/css/app.css is auto-injected by the framework.
Projects not using Tailwind can load it via CDN in their layouts instead — both work.
License
MIT