Bikin laravel versimu sendiri? Siapa takut!

PHP Laravel

December 10, 2019

Pada kesempatan kali ini mari kita coba membuat laravel versi kita sendiri. Tujuan nya adalah agar kita paham bagaimana cara kerja dari sebuah webframework itu.

Pada artikel ini kita akan menerapkan beberapa fitur dasar yang ada di laravel. Diantara nya adalah.

  • Container ( Dependency Injection )
  • Facade
  • Router
  • Controller

Dan struktur projectnya kira - kira akan seperti ini :

├── app
│   └── Http
│       └── Controllers
│           └── HomeController.php
├── bootstrap
│   └── app.php
├── composer.json
├── config
│   └── app.php
├── libs
│   ├── Application.php
│   ├── Container
│   │   └── Container.php
│   ├── Facade
│   │   └── Facade.php
│   ├── Http
│   │   ├── Request.php
│   │   └── Response.php
│   └── Router
│       ├── Route.php
│       └── Router.php
├── public
│   └── index.php
└── routes
    └── web.php

Disini kita tidak akan mengggunakan library apapun, kita akan menggunakan php murni saja. Kita akan menggunakan composer sebagai autoloader.

Setup project

Mari kita mulai pertualangan ini dengan menyiapkan struktur projectnya. Yang pertama jangan lupa kamu sudah menginstall composer. Berikut ini versi composer yang di gunakan untuk menulis tutorial ini. Pastikan kamu menggunakan versi yang sama atau yang lebih tinggi.

composer --version
Composer version 1.9.1 2019-11-01 17:20:17

Kemudian untuk versi php nya adalah :

php --version
PHP 7.3.12-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Nov 28 2019 07:37:16) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.12, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.12-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies

Kemudian buat folder dan inisial file seperti berikut ini :

.
├── bootstrap
│   └── app.php
├── composer.json
├── libs
│   └── Application.php
└── public
    └── index.php

Buat file composer.json kamu juga bisa generate dengan menjalankan perintah composer init lalu enter sampai selesai. Kemudian tambahkan autoload seperti berikut ini :

{
    "name": "ahmadrosid/mweb",
    "authors": [
        {
            "name": "ahmadrosid",
            "email": "alahmadrosid@gmail.com"
        }
    ],
    "require": {},
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "System\\": "libs/"
        }
    }
}

Kemudian file index.php seperti berikut ini :

<?php 

require_once("../vendor/autoload.php");

$app = require_once("../bootstrap/app.php");

$app->run();

Kemudian untuk file bootstrap/app.php seperti berikut ini :

<?php 

$app = new System\Application(
    __DIR__
);

return $app;

Dan yang terakhir untuk file libs/Application.php seperti berikut ini :

<?php 

namespace System;

/**
 * Application
 */
class Application
{
    protected $dir;

    public function __construct($dir)
    {
        $this->dir = $dir;
    }

    public function run()
    {
        echo "Hello world!";
    }
}

Sekarang jalankan perintah berikut ini .

php -S localhost:8000 -t public

Kalau sekarang kita kunjungi https://localhost:8000 maka akan muncul text Hello world!. Dengan begini kita sudah selesai dengan inisial project nya, selanjutnya kita akan menambahkan beberapa fitur kedalam framework kita.

Container ( Dependency Injection )

Container atau dependency injection adalah salah satu fitur yang membuat kita mudah dalam menginstansiasi sebuah object. Hal ini juga yang membuat framework laravel itu banyak magic nya. Nah mari kita coba bikin dependency injection sederhana pada framework kita ini.

OK langsung kita lanjut bikin kelas container libs/Container/Container.php dan berikut ini isinya.

<?php 

namespace System\Container;

use Closure;
use Exception;
use ReflectionClass;

/**
 * Class Container
 * @package System\Container
 */
class Container
{
    /**
     * @var array
     */
    protected $instances = [];

    /**
     * @param $abstract
     * @param null $concrete
     */
    public function set($abstract, $concrete = null)
    {
        if ($concrete == null) {
            $concrete = $abstract;
        }
        $this->instances[$abstract] = $concrete;
    }

    /**
     * @param $abstract
     * @param array $parameters
     * @return mixed
     * @throws \ReflectionException
     */
    public function make($abstract, $parameters = [])
    {
        if (!isset($this->instances[$abstract])) {
            $this->set($abstract);
        }

        return $this->resolve($this->instances[$abstract], $parameters);
    }

    /**
     * @param $concrete
     * @param $parameters
     * @return mixed
     * @throws \ReflectionException
     */
    public function resolve($concrete, $parameters)
    {
        if ($concrete instanceof Closure) {
            return $concrete($this, $parameters);
        }

        $reflector = new ReflectionClass($concrete);
        if (!$reflector->isInstantiable()) {
            throw new Exception("Class {$concrete} is not instantiable");
        }

        $constructor = $reflector->getConstructor();
        if (is_null($constructor)) {
            return $reflector->newInstance();
        }

        $parameters = $constructor->getParameters();
        $dependecies = $this->getDependencies($parameters);

        return $reflector->newInstanceArgs($dependecies);
    }

    /**
     * @param $parameters
     * @return array
     * @throws Exception
     */
    public function getDependencies($parameters)
    {
        $dependecies = [];
        foreach ($parameters as $parameter) {
            $dependecy = $parameter->getClass();
            if ($dependecy == NULL) {
                if ($parameter->isDefaultValueAvailable()) {
                    $dependecies[] = $parameter->getDefaultvalue();
                }else{
                    throw new Exception("Can not resolve class dependency");
                }
            }else{
                $dependecies[] = $this->get($dependecy->name);
            }
        }

        return $dependecies;
    }
}

Ok sekarang kita test container nya. Buat file app/Http/Controllers/HomeController.php :

<?php 

namespace App\Http\Controllers;

class HomeController
{

    public function __invoke()
    {
        return "Invoke";
    }

    public function index()
    {
        return "Hello from controller";
    }
}

Sekarang di index.php kita buat seperti ini :

use System\Container\Container;
use App\Http\Controllers\HomeController;

$container = new Container;

$homeController = $container->make(HomeController::class);

echo $homeController->index();

Dengan begini kita bisa mengakses instance dari home controller melalui dependency injection.

Router

Router adalah fitur untuk menentukan kemana kita akan mengarahkan request yang masuk ke aplikasi kita. Ok disini kita akan membuat router sederhana. Buat file libs/Router/Router.php, seperti berikut ini :

<?php

namespace System\Router;

use Closure;
use System\Container\Container;

/**
 * Class Router
 * @package System\Router
 */
class Router
{

    /**
     * @var array
     */
    protected $routes = [];
    /**
     * @var Container
     */
    protected $container;
    /**
     * @var string
     */
    protected $controllerPath;

    /**
     * Router constructor.
     */
    public function __construct()
    {
        $this->container = new Container;
    }

    /**
     * @param $controllerPath
     */
    public function setControllerPath($controllerPath)
    {
        $this->controllerPath = $controllerPath;
    }

    /**
     * @param $path
     * @param $action
     */
    public function get($path, $action)
    {
        $url = rtrim($path, "/");
        $this->routes[$url] = $action;
    }

    /**
     * @return mixed|string
     * @throws \ReflectionException
     */
    public function dispatch()
    {
        $uri = $_SERVER['REQUEST_URI'];
        if (!array_key_exists(rtrim($uri, "/"), $this->routes)) {
            return "No route found";
        }

        $action = $this->routes[rtrim($uri, "/")];
        if ($action instanceof Closure) {
            return $this->container->resolve($action, []);
        }

        $controller = $this->getController($action);
        $method = $controller["method"];

        return $controller["instance"]->$method();
    }

    /**
     * @param $action
     * @return array
     * @throws \ReflectionException
     */
    private function getController($action)
    {
        $class = explode("@", $action);

        return [
            'instance' => $this->container->make($this->controllerPath . $class[0]),
            "method" => isset($class[1]) ? $class[1] : "__invoke"
        ];
    }
}

Sekarang kita bisa test di file index.php seperti berikut ini :

$router = new System\Router\Router;
$router->get("/home", function () {
    return "Home";
});

echo $router->dispatch();

Facade

Facade adalah salah satu fitur yang ada di laravel yang cukup kontroversi tapi juga bermanfaat bagi kita. Sebagai contohnya adalah di gunakan untuk router di laravel.

Route::get("/home", function () { 
  echo "Hello from route";
});

Sekarang kita buat file libs/Facade/Facade.php nya seperti berikut ini :

<?php

namespace System\Facade;

use RuntimeException;

abstract class Facade
{
    /**
     * @var \System\Application
     */
    protected static $app;

    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();
        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }
        return $instance->$method(...$args);
    }

    private static function getFacadeRoot()
    {
        $instance = static::$app->getInstance(static::name());
        return $instance;
    }

    public static function setApp($app) {
        static::$app = $app;
    }
}

OK selanjutnya buat file libs/Router/Route.php dan isi file nya seperti ini :

<?php

namespace System\Router;

use System\Facade\Facade;

/**
 * Class Route
 * @package System\Router
 */
class Route extends Facade
{
    /**
     * @return string
     */
    public static function name()
    {
        return "Router";
    }
}

Kemudian class Application kita update seperti ini :

<?php 

namespace System;

use Closure;
use System\Container\Container;

/**
 * Class Application
 * @package System
 */
class Application
{
    /**
     * @var string
     */
    protected $dir;
    /**
     * @var Container
     */
    public $container;
    /**
     * @var array
     */
    protected $instance = [];

    /**
     * Application constructor.
     * @param $dir
     */
    public function __construct($dir)
    {
        $this->dir = $dir;
        $this->container = new Container;
    }

    /**
     * @return string
     */
    public function getBaseDir()
    {
        return $this->dir;
    }

    /**
     * @param $config
     * @throws \ReflectionException
     */
    public function config($config)
    {
        foreach ($config['providers'] as $name => $class){
            class_alias($class, $name);
            $class::setApp($this);
        }

        foreach ($config['aliases'] as $name => $class){
            $this->instance[$name] = $this->container->make($class);
        }
    }

    /**
     * @param $name
     * @return mixed
     */
    public function getInstance($name)
    {
        return $this->instance[$name];
    }

    /**
     * @param array $array
     * @param Closure $callback
     */
    public function setRoutes(array $array, Closure $callback)
    {
        $this->getInstance("Router")
            ->setControllerPath($array['controllers']);
        call_user_func($callback);
    }

    /**
     * Run the application
     */
    public function run()
    {
        echo $this->getInstance("Router")->dispatch();
    }
}

Selanjutnya bikin class config/app.php seperti berikut ini :

<?php

return [
    'providers' => [
        'Route' => \System\Router\Route::class,
    ],
    'aliases' => [
        'Router' => \System\Router\Router::class
    ]
];

Kemudian di file bootstrap/app.php registrasikan config tadi :

$app->config(
    require_once("../config/app.php")
);

Sekarang jalankan server nya dan coba buka http://localhost:8000/home.

Routes & Controller

Kita tidak ingin membuat semua logic aplikasi kita di dalam file public/index.php nanti pasti akan banyak sekali kode yang menumpuk disitu. Untuk itu sekarang kita buat file routes/web.php.

<?php

Route::get("/", 'HomeController');
Route::get("/index", 'HomeController@index');
Route::get("/home", function () {
    return "Home router";
});

Dan sekarang kita daftarkan routes ini kedalam aplikasi kita melalui bootstap/app.php :

$app->setRoutes([
    'controllers' => "\\App\\Http\\Controllers\\"
], function () {
    require_once("../routes/web.php");
});

Nah sekarang kita bisa test dengan membuka link ini :

Nah, sampai disini dulu aja tutorial kali ini. Gimana gampang bukan. Next tutorial kita akan bahas tentang templating dan juga database orm. Tungguin ya next artikelnya. Termakasih sudah mampir.

Subscribe to My Newsletter

Thank you for your interest in my blog. Sign up to my newsletter to stay current on the latest news and information me and to be the first to see new blog posts.