Skip to content

techmahedy/zunoo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

About Zuno

Zuno is a robust and versatile web application framework designed to streamline the development process and enhance productivity. It provides a comprehensive set of tools and features that cater to modern web development needs, integrating seamlessly with Adobe technologies to offer a cohesive development experience.

Key Features

  • Route Management: Zuno simplifies route definition and management, allowing developers to easily set up routes, handle parameters, and manage requests with minimal configuration. It supports both single and multiple route parameters to cater to complex routing needs.

  • Database Integration: The framework offers powerful database tools, including support for migrations, seeding, and query building. Zuno’s database layer ensures efficient data management and interaction, with built-in support for models and database connections.

  • View Handling: With Zuno, creating and managing views is straightforward. The framework supports custom Blade directives, allowing developers to extend view functionalities as needed.

  • Middleware Support: Zuno includes support for both global and route-specific middleware, giving developers the flexibility to implement application-wide and route-specific logic easily.

  • Validation and Security: The framework provides robust validation mechanisms and security features such as CSRF token management, ensuring that data handling and form submissions are both secure and reliable.

  • Dependency Injection: Zuno promotes clean and maintainable code with its dependency injection system, supporting both constructor and method injection, and allowing for easy binding of interfaces to service classes.

  • Session and Logging: Zuno offers comprehensive session management and logging capabilities, enabling developers to manage user sessions and track application logs effectively.

  • Collections and Macros: The framework includes support for collections and custom macros, providing developers with additional tools to manipulate data and extend functionality as required.

Zuno is designed to be intuitive and developer-friendly, with a focus on providing the essential features needed for modern web applications while integrating seamlessly with Adobe technologies for enhanced capabilities.

How to use

About

Zuno, A basic PHP MVC framework. In this framework, you will get all the basic features of a web application like routing, middleware, dependency injection, eloquent relationship, model, blade template engine interface injection, and many more. Test it and if you like, please give it a star.

How to Install

We can easily set up and install this application with a few steps. Before using this application, a minimum PHP 8.3 version is needed.

Create a new project

composer create-project zunoo/zunoo example-app

  • Step 1: Go to the project directory with this command cd example-app
  • Step 2: Start the development server by running this command php -S localhost:8000

Define Route

To define the route, navigate to this file and update

routes/web.php

<?php

use Zuno\Route;
use App\Http\Controllers\ExampleController;

Route::get('/', [ExampleController::class, 'index']);
Route::get('/about', [ExampleController::class, 'about']);

Model

Now look at Model, how you can use it

<?php

use Zuno\Model;

/**
 * User Model
 *
 * This class represents the User model in the Zuno framework. It extends the base Model class provided
 * by the Zuno framework, allowing you to interact with the 'users' table in the database using Eloquent-like
 * features.
 *
 * You can define your model-specific methods and properties here. This model can use various features
 * provided by the Zuno framework, similar to how Laravel models work.
 *
 * For example, you can define relationships, accessors, mutators, and more.
 *
 * @package App\Models
 */
class User extends Model
{
    /**
     * Model Table Name
     *
     * The name of the database table associated with this model. By default, Zuno will assume the table
     * name is the plural form of the model name. You can override this property if your table name does
     * not follow this convention.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * Mass Assignable Attributes
     *
     * Specify which attributes should be mass-assignable. This helps protect against mass assignment
     * vulnerabilities. Define the attributes you want to be mass-assignable in this property.
     *
     * @var array
     */
    protected $fillable = ['name', 'email', 'password'];

    /**
     * Hidden Attributes
     *
     * Specify which attributes should be hidden from arrays. For example, you might want to hide sensitive
     * information such as passwords from the model's array or JSON representation.
     *
     * @var array
     */
    protected $hidden = ['password', 'remember_token'];

    /**
     * Date Casting
     *
     * Specify attributes that should be cast to native types. For example, if you have a 'created_at'
     * attribute in your table, you can cast it to a DateTime object for easier manipulation.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * Relationships
     *
     * Define any relationships this model has with other models. For example, if a user has many posts,
     * you can define a relationship method here.
     *
     * Example:
     * public function posts()
     * {
     *     return $this->hasMany(Post::class);
     * }
     */
}

Database Connection

Connect your database like that, just pass your credentials to .env

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=

And you can print configuration value like $_ENV['DB_CONNECTION'] or you can use env('DB_CONNECTION')

Views

To work with views, default view file path inside resources/views. Now passing data with views like

<?php

use Zuno\Route;
use Zuno\Application;

Route::get('/', function () {
    $version = Application::VERSION;
    return view('welcome', compact('version'));  //for nested folder file view: home.index
});

This will load welcome.blade.php file. We can print this value like

<h1>{{ $version }}</h1>

Avaiable blade systex

@section('looping-test')
  <p>Let's print odd numbers under 50:</p>
  <p>
    @foreach($numbers as $number)
      @if($number % 2 !== 0)
        {{ $number }} 
      @endif
    @endforeach
  </p>
@endsection

For mastering template

@include('shared.header')
<body>
  <div id="container">
    <h3>Welcome to <span class="reddish">{{ $title }}</span></h3>
    <p>{{ $content }}</p>

    <p>Master file</p>

    @yield('looping-test')
  </div>
  @include('shared.footer')
</body>

You can use any blade systex as you want like laravel framework

Route Parameters

You can pass single or multiple parameter with route as like below

<?php

use Zuno\Route;
use App\Http\Controllers\ProfileController;

Route::get('/user/{id}', [ProfileController::class, 'index']);

Now accept this param in your controller like:

<?php

namespace App\Http\Controllers;

class ProfileController extends Controller
{
    public function index($id)
    {
        return $id;
    }
}

Naming Route

Zuno support convenient naming route structure. To create a naming route, you can do

use Zuno\Route;
use App\Http\Controllers\UserController;

Route::get('/user/{id}/{name}', [UserController::class, 'profile'])->name('profile');

Now use this naming route any where using route() global method.

 <form action="{{ route('profile', ['id' => 2, 'name' => 'mahedy']) }}" method="post">
    @csrf
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

If there is single param in your route, just use

Route::get('/user/{id}', [UserController::class, 'profile'])->name('profile');

Now call the route

{{ route('profile', 2) }}

That's it.

Multiple Route Parameters

You can pass multiple parameter with route as like below

<?php

use App\Http\Controllers\ProfileController;

Route::get('/user/{id}/{username}', [ProfileController::class, 'index']);

Now accept this multiple param in your controller like:

<?php

namespace App\Http\Controllers;

class ProfileController extends Controller
{
    public function index($id, $username)
    {
        return $id;

        return $username;
    }
}

Request

Request is most important thing when we work in a web application. We can use Request in this application like

<?php

namespace App\Http\Controllers;

use Zuno\Request;

class ExampleController extends Controller
{
    public function store(Request $request)
    {
        // Asume we have a url like http://www.example.com/?name=mahedi. Now we can check.
        if($request->has('name')){

        }

        // We can also check form request data like
        if($request->has('name') && $request->has('email')){

        }

        // Now get the value from request like:
        $name = $request->input('name');
        $email = $request->input('email');

        // Also you can get the value like
        $name = $request->name;
        $email = $request->email;

        // You can get the file data like
        $image = $request->file // Here file is the html form input name

        // You can also use global request() helper like:
        $name = request()->input('name');

        // Or using global request helper
        if(request()->has('name')){

        }

        //get all the input as an array
        $input = $request->all();
        dd($input);
    }
}

File Request

To upload or handle file in Zuno, you can simply use this $request type

public function upload(Request $request)
{
    // You can get the file data like
    $image = $request->file // Here file is the html form input name

    // Returns a file object to get extra information like file name, size, path type etc
    $file = $request->file('file');

    if ($file && $file->isValid()) {
        $file->getClientOriginalName(); // Original Name:
        $file->getClientOriginalPath(); // Temporary Path
        $file->getClientOriginalType(); // MIME Type
        $file->getSize(); // File Size
    }
}

Get Logged in User Data

Zuno has its own authentication mechanism. if you want to get logged in user data, Auth::user() is the way to get the logged in user data.

use Zuno\Auth\Security\Auth;

// This $user is an object.
$user = Auth::user();

// If you want to get the logged in user ID
$id = Auth::user()->id;

// If you want to check user is logged in or not, just use
if(Auth::check()){
    // User is logged in
}

// Zuno has a global auth check function and that is
if(isAuthenticated()){
    // User is logged in
}

Login

Zune has Auth traits to make it easier to you so that you can create authentication very easily. just need use use this Auth and to create a authentication manually, you can use attempt() method like that

<?php

namespace App\Http\Controllers\Auth;

use Zuno\Auth\Security\Auth;
use Zuno\Request;
use App\Http\Controllers\Controller;
use App\Models\User;

class LoginController extends Controller
{
    use Auth;

    public function login(Request $request)
    {
        $user = User::where('email', $request->email)->first();

        if ($user) {
            if (Auth::attempt($request->all())) {
                // You are logged in
                return redirect()->url('/home');
            }
            // Email or password is not matched
            return redirect()->back();
        }

        // user does not exists
    }
}

Zuno supports email or username based authentication currently, so you can pass username also rather than email but password is a must needed field for authentication.

Logout

To add logout fucntionalities, just call the logout method like

use Zuno\Auth\Security\Auth;

public function logout()
{
    Auth::logout();
}

Global Middleware

We can define multiple global middleware. To define global middleware, just update the App\Http\Kernel.php file's $middleware array as like below

<?php

/**
 * Application global middleware
 */
public $middleware = [
    \App\Http\Middleware\ExampleMiddleware::class,
];

Now update your middleware like

<?php

namespace App\Http\Middleware;

use Closure;
use Zuno\Request;
use Zuno\Middleware\Contracts\Middleware;

class ExampleMiddleware implements Middleware
{
    public function __invoke(Request $request, Closure $next)
    {
        /**
         * Code goes here
         */
        return $next($request);
    }
}

Route Middleware

We can define multiple route middleware. To define route middleware, just update the App\Http\Kernel.php file's $routeMiddleware array as like below

<?php

/**
 * The application's route middleware.
 *
 * These middleware may be assigned to groups or used individually.
 *
 * @var array<string, class-string|string>
 */
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
];

And update your route like:

<?php

use Zuno\Route;
use App\Http\Controllers\ProfileController;

Route::get('/', [ProfileController::class,'index'])->middleware('auth');

Now update your middleware like

<?php

namespace App\Http\Middleware;

use Closure;
use Zuno\Request;

//! Example middleware
class ExampleMiddleware
{
    /**
     * Handle an incoming request
     *
     * @param Request $request
     * @param \Closure(\Zuno\Request) $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next): mixed
    {
        // Example
        // $ip = $_SERVER['REMOTE_ADDR'] ??= '127.0.0.1';
        // if ($ip === '127.0.0.1') {
        //     return $next($request);
        // }

        // dd("{This $ip is bloked}");

        return $next($request);
    }
}

Buf if you want to define multiple middleware in a single route, you can define middleware like that

<?php

use Zuno\Route;
use App\Http\Controllers\ProfileController;

Route::get('/', [ProfileController::class,'index'])->middleware(['auth', 'is_subscribed']);

Route Middleware Parameters

We can define multiple route middleware parameters. To define route middleware, add a : after the middleware name. If there are multiple parameters, separate them with a , comma. See the example

<?php

use Zuno\Route;
use App\Http\Controllers\ExampleController;

Route::get('/', [ExampleController::class, 'index'])
    ->middleware(['auth:admin,editor,publisher', 'is_subscribed:premium']);
  • In this example:
    • The auth middleware receives three parameters: admin, editor, and publisher.
    • The is_subscribed middleware receives one parameter: premium.

How to Accept Parameters in Middleware

In the middleware class, define the handle method and accept the parameters as function arguments:

public function handle(Request $request, Closure $next, $admin, $editor, $publisher): mixed
{
    // Parameters received:
    // $admin = 'admin'
    // $editor = 'editor'
    // $publisher = 'publisher'

    // Middleware logic goes here

    return $next($request);
}

Here, Zuno automatically injects the parameters in the order they are passed in the route definition.

Handling a Single Parameter

For middleware that only requires one parameter (e.g., is_subscribed), the implementation remains the same:

public function handle(Request $request, Closure $next, $plan): mixed
{
    // $plan = 'premium'

    // Middleware logic goes here

    return $next($request);
}

This ensures flexibility when applying different conditions based on user roles, subscriptions, or any other parameters.

Zuno Config File

Zuno manages configuration files to bootstarp applications configuration. Configuration files located in config/ folder.

Get config data

if you want to fetch data from config file, then you need to follow like that,

// this will print the username from config/database.php file
$dbUsername = config('database.connections.mysql.username');

You can also use Config::get() method also like

use Zuno\Config;

// this will print the username from config/database.php file
$dbUsername = Config::get('database.connections.mysql.username');

Set config data

To set confile file data, you need to use

use Zuno\Config;

// this will print the username from config/database.php file
$dbUsername = Config::set('database.connections.mysql.username','admin');

Database Query Builder

Zuno has its own custom query builder for fetching database query. See the very simple example

<?php

namespace App\Http\Controllers;

use Zuno\Database\DB;
use App\Http\Controllers\Controller;

class ExampleController extends Controller
{
    public function index(Request $request)
    {
        $data = DB::table('users')
            ->select(['id', 'name', 'email'])
            ->where('status', '=', 'active')
            ->orderBy('name')
            ->limit(10)
            ->offset(0)
            ->get();

        return $data; // this is laravel collection. you can use any collection wrapper in this data.

        //you can check a data exists or not like that
        $data = DB::table('users')
            ->where('status', '=', 'active')
            ->exists();

        //you can also fetch first row of your table, it will return single collection
        $data = DB::table('users')
            ->where('id', '=', 1)
            ->first();
    }
}

Custom Blade Directive

We can define custom blade directive. To define it, update App\Providers\AppServiceProvider.php as like below

<?php

namespace App\Providers;

use Zuno\Container;

class AppServiceProvider extends Container
{
    public function register()
    {
        $this->directive('capitalize', function ($text) {
            return "<?php echo strtoupper($text) ?>";
        });
    }
}

And now we can call it in a blade file like

{{ capitalize('hello') }}

From Validation

We can validate from and can show error message in blade file very easily. To validate from , just assume we have two routes

<?php

use Zuno\Route;
use App\Http\Controllers\ExampleController;

Route::get('/register', [ExampleController::class, 'index']);
Route::post('/register', [ExampleController::class, 'store']);

And now we can update App\Http\Controllers\ExampleController.php like

<?php

namespace App\Http\Controllers;

use Zuno\Request;
use App\Http\Controllers\Controller;

class ExampleController extends Controller
{
    public function index()
    {
        return view('user.index');
    }

    public function store(Request $request)
    {
        $request->validate([
            'email' => 'required|email|unique:users|min:2|max:100', //unique:users -> here [users] is table name
            'password' => 'required|min:2|max:100',
        ]);

        //save the data

        return redirect()->url('/test'); //or redirect()->back()
    }
}

Now update the resources/user/index.blade.php like

<!-- Showing All Error Messages -->
@if (session()->has('errors'))
    @foreach (session()->get('errors') as $error)
        @foreach ($error as $item)
            <li>{{ $item }}</li>
        @endforeach
    @endforeach
@endif

<form action="/register" method="post">
    @csrf
    <div class="mb-3">
        <label for="exampleInputEmail1" class="form-label">Email address</label>
        <input type="email" class="form-control" name="email">
        <!-- Show Specific Error Message -->
        @if (session()->has('email'))
            {{ session()->get('email') }}
        @endif
    </div>
    <div class="mb-3">
        <label for="exampleInputPassword1" class="form-label">Password</label>
        <input type="password" class="form-control" name="password">
        <!-- Show Specific Error Message -->
        @if (session()->has('password'))
            {{ session()->get('password') }}
        @endif
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

CSRF Token

If you submit a post request form, then you must be provide csrf_token with your request like below, otherwise it will throw an exception error.

<form action="/" method="post">
    @csrf
    <input type="submit" value="submit">
</form>

Binding Interface to Service Class

To bind the interface with your service class, just update App\Providers\AppServiceProvider.php.

<?php

namespace App\Providers;

use Zuno\Container;
use App\Services\StripePaymentService;
use App\Contracts\PaymentServiceContract;

class AppServiceProvider extends Container
{
    public function register()
    {
        // Remember, the global request() helper is available here. You can get input value here like

        //request()->input('payment_type')

        $this->bind(PaymentServiceContract::class, function() {
            return new StripePaymentService();
        });

        // Register any custom blade directives, macro or your own custom builds
        //
        // Place service bindings or provider registrations here.
        //
        // Example:
        // $this->bind(SomeService::class, function() {
        //     return new SomeService();
        // });
    }
}

Dependency Injection

Now look at that, how you can use dependency injection.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Models\Post;
use Zuno\Request;
use App\Contracts\PaymentServiceContract;

class ExampleController extends Controller
{
    /**
     * You can pass as many class as you want as parameter
     */
    public function index(
        Request $request, //class dependency injection
        User $user, //class dependency injection
        Post $post, //class dependency injection
        PaymentServiceContract $payment //interface dependency injection
    ) {

       //Use any eloquent query of Laravel
    }

    public function about()
    {
        return view('about.index');
    }
}

Constructor Dependency Injection

Now look at that, how you can use dependency injection using constructor.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Models\Post;
use Zuno\Request;
use App\Contracts\PaymentServiceContract;

class ExampleController extends Controller
{
    /**
     * Look at that, we are passing interface, models. How cool it is
     */
    public function __construct(
        public PaymentServiceContract $payment,
        public User $user,
        public Post $post,
    ) {}
}

Collection & Macro

Like Laravel framework, in this Zuno framework, you can also work with Laravel collection and you can create your own custom macro. To create a custom macro, just update service provider App\Providers\AppServiceProvider.php like:

<?php

namespace App\Providers;

use Zuno\Container;
use Illuminate\Support\Collection;

class AppServiceProvider extends Container
{
    /**
     * register.
     *
     * Register any application services.
     * @return	void
     */
    public function register()
    {
        Collection::macro('toUpper', function () {
            return $this->map(function ($value) {
                return strtoupper($value);
            });
        });
    }
}

And now we can use it like:

<?php

use Zuno\Route;

Route::get('/', function () {

    $collection = collect(['first', 'second']);
    $upper = $collection->toUpper();

    return $upper; //output ["FIRST","SECOND"]
});

Session Flash Message

When we work with form submit then we need to show validation error message or success message. We can show session flash message very easily like

<?php

//Set the session flash value like
session->flash('key', 'value to be printed');

//Now you can print this value lie
if(session()->has('key')){
    echo session()->get('key');
}

Log

Zuno now supports three log channel, stack, daily, single. Configure is from config/log.php file and .env file. We can easily print important messages in a log file which is located inside storage\logs\zuno.log. To print a message, Zuno provide logger() helper function, you just need to follow this

<?php

// logger() is a global helper function. by default it uses stack channel.
logger()->info('Hello'); 

// You can use like that
logger()->info('Hello');
logger()->error('Hello');
logger()->warning('Hello');
logger()->debug('Hello');

Passing array as params

If you need to log array data, follow as like below

logger()->info('message', ['name' => 'Mahedi Hasan', 'spouse' => 'Nure Yesmin']);

Database and Migration

Zuno allow you to create migration. To create migration, Zuno uses CakePHP's phinx. So to create a migration file first you need to update the configuration file environments array like:

config.php

<?php

return [
    'environments' => [
        'default_migration_table' => 'phinxlog',
        'your_database_name' => [
            'adapter' => 'mysql',
            'host' => 'localhost',
            'name' => 'your_database_name',
            'user' => 'your_database_username',
            'pass' => 'your_database_password',
            'port' => '3306'
        ]
    ]
];

Now run the below command in your project terminal like: php vendor/bin/phinx create Post -c config.php

Here Post is the model name.

Now this command will generate a migration file in the following path with the empty change() method.

app\database\migration\20231111144423_post.php

<?php

declare(strict_types=1);

use Zuno\Migration\Migration;

final class Post extends Migration
{
    /**
     * Change Method.
     *
     * Write your reversible migrations using this method.
     *
     * More information on writing migrations is available here:
     * https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
     *
     * Remember to call "create()" or "update()" and NOT "save()" when working
     * with the Table class.
     */
    public function change(): void
    {
        $table = $this->table('posts');

        $table->addColumn('title', 'string', ['limit' => 20])
            ->addColumn('body', 'text')
            ->addColumn('cover_image', 'string')
            ->addTimestamps()
            ->addIndex(['title'], ['unique' => true]);

        $table->create();
    }
}

Now run the migration command:

php vendor/bin/phinx migrate -c config.php.

Now see the documentation of phinx Documentation to learn more.

Database Seeder

Zuno allow you to create database seeder file to generate fake date. To create seeder, Zuno uses CakePHP's phinx. So to create a seeder file first you need run below command. Assume we are going to create PostSeeder:

Now run the below command in your project terminal like: php vendor/bin/phinx seed:create PostSeeder -c config.php

Here PostSeeder is the seeder class name.

Now this command will generate a seeder file in the following path with the empty run() method.

app\database\seeds\PostSeeder.php

<?php

declare(strict_types=1);

use Phinx\Seed\AbstractSeed;

class PostSeeder extends AbstractSeed
{
    /**
     * Run Method.
     *
     * Write your database seeder using this method.
     *
     * More information on writing seeders is available here:
     * https://book.cakephp.org/phinx/0/en/seeding.html
     */
    public function run(): void
    {
        //you can use fake() helper here as well as your entire application 

        $data = [
            [
                'title'   => fake()->title(),
                'body'    => fake()->title(),
                'created_at' => date('Y-m-d H:i:s'),
            ]
        ];

        $posts = $this->table('posts');
        $posts->insert($data)
            ->saveData();

        // empty the table
        // $posts->truncate();
    }
}

Now run the seeder command:

php vendor/bin/phinx seed:run -c config.php.

Or you can run specific seeder class file lie

php vendor/bin/phinx seed:run -s PostSeeder -c config.php

Now see the documentation from phinx Documentation to learn more.