vercy/zieex

A lightweight AJAX-first PHP framework with MVC, REST API, and modern features

Maintainers

Package info

github.com/vercypro/zieex

Homepage

Type:project

pkg:composer/vercy/zieex

Transparency log

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 0

v1.0.1 2026-07-06 12:26 UTC

This package is auto-updated.

Last update: 2026-07-06 12:35:38 UTC


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')

Mail

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