DEV Community

Mahedi Hasan
Mahedi Hasan

Posted on • Updated on

Developing New PHP Framework MII

How to use

About

MII, A basic PHP MVC framework design in a way that you feel like you are working in a Laravel application. In this framework you will get all the basic features of a web application needs like routing, middleware, dependency injection, eloquent relationship, model, blade template engine and interface injection and many mores. Test it and if you like, please give a star to it.

How to Install

We can easily setup and install this application with some few steps. Before using this application, minimum PHP 8.1 version is needed.

  • Step 1: git clone https://github.com/techmahedy/mi.git or download this application
  • Step 2: Go to project directory with this command cd mi and run composer update
  • Step 3: Copy .env.example to .env
  • Step 4: Start the development server by running this command php -S localhost:8000

Define Route

To define route, navigate to this file and update

routes/web.php


<?php

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

Route::get('/', [ExampleController::class, 'index']);
Route::get('/about', [ExampleController::class, 'about']);
Enter fullscreen mode Exit fullscreen mode

Binding Interface to Service Class

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

<?php

namespace App\Providers;

use App\Core\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')
       return $this->bind(PaymentServiceContract::class, StripePaymentService::class);
       //or
       return $this->singleton(PaymentServiceContract::class, StripePaymentService::class);
    }
}
Enter fullscreen mode Exit fullscreen mode

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 App\Core\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');
    }
}
Enter fullscreen mode Exit fullscreen mode

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 App\Core\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,
    ) {}
}
Enter fullscreen mode Exit fullscreen mode

Model

Now look at Model, how you can use it


use App\Core\Model;

class User extends Model
{   
    /**
     * Use any features of Laravel.
     */
}
Enter fullscreen mode Exit fullscreen mode

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=

Enter fullscreen mode Exit fullscreen mode

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 App\Core\Route;
use App\Core\Application;

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

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

<h1>{{ $version }}</h1>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 App\Core\Route;
use App\Http\Controllers\ProfileController;

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

Now accept this param in your controller like:

<?php

namespace App\Http\Controllers;

class ProfileController extends Controller
{
    public function index($id)
    {
        return $id;
    }
}
Enter fullscreen mode Exit fullscreen mode

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']);
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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 App\Core\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');

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

        //or
        if(request()->has('name')){

        }

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

Enter fullscreen mode Exit fullscreen mode

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,
];
Enter fullscreen mode Exit fullscreen mode

Now update your middleware like

<?php

namespace App\Http\Middleware;

use Closure;
use App\Core\Request;
use App\Core\Middleware\Contracts\Middleware;

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

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,
];
Enter fullscreen mode Exit fullscreen mode

And update your route like:

<?php

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

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

Now update your middleware like

<?php

namespace App\Http\Middleware;

use Closure;
use App\Core\Request;
use App\Core\Middleware\Contracts\Middleware;

class Authenticate implements Middleware
{
    /**
     * handle.
     *
     * @param   Request $request    
     * @param   Closure $next       
     * @return  mixed
     */
    public function handle(Request $request, Closure $next)
    {
        /**
         * code
         */
        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

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 App\Core\Container;

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

And now we can call it in a blade file like

{{ capitalize('hello') }}
Enter fullscreen mode Exit fullscreen mode

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 App\Core\Route;
use App\Http\Controllers\ExampleController;

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

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

<?php

namespace App\Http\Controllers;

use App\Core\Request;
use App\Core\Controllers\Controller;

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

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

        //save the data

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

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Collection & Macro

Like Laravel framework, in this MII 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 App\Core\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);
            });
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

And now we can use it like:

<?php

use App\Core\Route;

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

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

    return $upper; //output ["FIRST","SECOND"]
});
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Log

We can easily print important messages in a log file which is located inside storage\logs\mii.log. To print a message, mii provide logger() helper function, you just need to follow this

<?php

//logger() is a global helper function
logger()->info('Hello');

Enter fullscreen mode Exit fullscreen mode

Database and Migration

MII allow you to create migration. To create migration, MII 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'
        ]
    ]
];
Enter fullscreen mode Exit fullscreen mode

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 App\Core\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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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

MII allow you to create database seeder file to generate fake date. To create seeder, MII 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();
    }
}

Enter fullscreen mode Exit fullscreen mode

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.

Github link:

Repository Link

Author Blog link:

Blog

Top comments (15)

Collapse
 
tanzimibthesam profile image
Tanzim Ibthesam • Edited

I appreciate your effort and hardwork but saying
If Laravel is slow arent there already options like Slim PHP. Currently though still early days there is a framework called Xframework which supports things like non blocking io of NodeJs.
If I need feel of Laravel and need something lightweight There will be need of an ORM,template engine and many more.
With Laravel 11 many things will be sorted out since there will be changes in skeleton.
Great work for practice and improving skills but just feel there are many great alternatives

Collapse
 
mahedi1501 profile image
Mahedi Hasan

Thanks for your compliment

Collapse
 
antonday07 profile image
Adam Tong

The question is why I should use this framework instead of Laravel

Collapse
 
mahedi1501 profile image
Mahedi Hasan

Hi, I am the author of this Framework. Your question is right about why you should use this framework. Look, I am a big fan of Laravel. But you know that Laravel is a little bit slower. So, when we need to create a small PHP application and there is no complex task, in this case, you can use this. In the other case, you can follow any framework.

Collapse
 
afmpjr profile image
Antonio Francisco

I was about to ask this.

Congratulations for the project!
but... am I missing something?

Collapse
 
mahedi1501 profile image
Mahedi Hasan

Hi, I am the author of this Framework. Your question is right about why you should use this framework. Look, I am a big fan of Laravel. But you know that Laravel is a little bit slower. So, when we need to create a small PHP application and there is no complex task, in this case, you can use this. In the other case, you can follow any framework.

Collapse
 
tjhunkin profile image
tjhunkin

Why would you create a clone of Laravel that is much worse than the original framework? What problem are you solving? This is just dumb.

Collapse
 
mahedi1501 profile image
Mahedi Hasan

Hi, I am the author of this Framework. Your question is right about why you should use this framework. Look, I am a big fan of Laravel. But you know that Laravel is a little bit slower. So, when we need to create a small PHP application and there is no complex task, in this case, you can use this. In the other case, you can follow any framework.

Collapse
 
tjhunkin profile image
tjhunkin

Think about it man, why would a person learn a new framework that is maintained by one person because of speed. Laravel can be optimised as far as you want it to be optimised using things like octane etc.

Thread Thread
 
tjhunkin profile image
tjhunkin

Your framework doesn't even have tests, it's a joke.

Thread Thread
 
mahedi1501 profile image
Mahedi Hasan

ok

Collapse
 
rafaacioly profile image
Rafael Acioly

Wouldn't be better to continue the development on Lumen instead of creating another framework?

Collapse
 
thomasmoors profile image
Thomas Moors

Hi, nice job. Don't feel bad about the non-constructive comments! They should be ashamed of themselves.

Collapse
 
lexisvar profile image
Alexis Vargas

In your about you wrote "... that you feel like you are working in a Laravel application" but why I would want to feel that I'm using Laravel using another?

Collapse
 
mahedi1501 profile image
Mahedi Hasan

Hi, I am the author of this Framework. Your question is right about why you should use this framework. Look, I am a big fan of Laravel. But you know that Laravel is a little bit slower. So, when we need to create a small PHP application and there is no complex task, in this case, you can use this. In the other case, you can follow any framework.