DEV Community

Ngo Dinh Cuong
Ngo Dinh Cuong

Posted on

Build your own PHP framework

Atom Framework: https://github.com/cuongdinhngo/atom

Ezycrazy Project is powered by Atom framework: https://github.com/cuongdinhngo/ezycrazy

1.First things

Single Entry Point

The entry point for all requests to a Atom application is the public/index.php file. All requests are directed to this file by your web server (Apache / Nginx) configuration. The index.php file doesn’t contain much code. Rather, it is a starting point for loading the rest of the framework.

<?php

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../app/autoload.php';

use Atom\Http\Server;

try {
    $server = new Server(['env']);
    $server->handle();
} catch (\Exception $e) {
    echo $e->getMessage();
}
Enter fullscreen mode Exit fullscreen mode
  • The index.php file loads the Composer generated autoloader definition, and then initiates an Server instance (Atom/Http/Server.php) with specified configured files (config/env.ini)

2. Atom/Http/Server.php

  • The method signature for the Server’s handle method is simple: receive a Request, dispatch to Router and load Controller.
/**
* Handle progress
* @return void
*/
public function handle()
{
    try {
        $routeData = $this->router->dispatchRouter();
        $this->handleMiddlewares($routeData);
        $this->controllerMaster->loadController($routeData);
    } catch (\Exception $e) {
        echo $e->getMessage();
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Basic routing

  • The router will dispatch the request to a route or controller, as well as run any route specific middleware.

  • The routes are defined in your route files, which are located in the app/Routes directory. The web.php file defines routes that are for your web interface. The routes in routes/api.php are stateless.

  • You will begin by defining routes in your app/Routes/web.php file. The routes defined in routes/web.php may be accessed by entering the defined route’s URL in your browser. For example, you enter http://localhost/users in your browser:

return [
    '/users' => [
        'middleware' => ['first', 'auth', 'second'],
        ['get' => 'User/UserController@list'],
        ['post' => 'User/UserController@create'],
        ['put' => 'User/UserController@put']
    ],
];
Enter fullscreen mode Exit fullscreen mode
  • Routes defined in the app/Routes/api.php that the /api URI prefix is automatically applied so you do not need to manually apply it to every route in the file.
return [
    '/users' => [
        'middleware' => ['first', 'second'],
        ['get' => 'User/UserController@list'],
        ['post' => 'User/UserController@create'],
        ['put' => 'User/UserController@put']
    ],
];
Enter fullscreen mode Exit fullscreen mode
  • Sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user’s ID from the URL. You may do so by defining route parameters:
return [

    '/users/{uId}/skills/{sId}' => [

        ['get' => 'User/UserController@test'],

    ],

];
Enter fullscreen mode Exit fullscreen mode

Memo

Route parameters are always encased within {} braces and only consist of alphabetic characters. URL must contains only number /users/1/skills/22

4. Middleware

  • Middleware provides a convenient mechanism for filtering the requests entering your application. For example, Atom includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen or throw an error. If the user is authenticated, the middleware will allow the request to proceed further into the application.

  • To define a new middleware, you must create a class at app/Middlewares directory.

<?php

namespace App\Middlewares;

use Atom\Guard\Auth;
use Atom\File\Log;

class Authorization
{
    /**
    * Handle Authorize
    *
    * @return void
    */
    public function handle()
    {
        Log::info(__METHOD__);
        Auth::check();
    }
}
Enter fullscreen mode Exit fullscreen mode
  • If you would like to assign middleware to specific routes, you should first assign the middleware a key in your config/middleware.php file.
return [
    /**
    * The application's route middlewares
    */
    'routeMiddlewares' => [
        'first' => 'FirstMiddleware',
        'second' => 'SecondMiddleware',
        'auth' => 'Authorization',
        'phpToJs' => 'PhpToJs',
        'signed.url' => 'IdentifySignedUrl',
    ],

    /**
    * List of priority middlewares
    */
    'priorityMiddlewares' => [
        'auth' => 'Authorization',
        'signed.url' => 'IdentifySignedUrl',
        'phpToJs' => 'PhpToJs',
    ],
];
Enter fullscreen mode Exit fullscreen mode
  • Once the middleware has been defined, you may middleware to a route:
return [
    '/users' => [
        'middleware' => ['first', 'auth', 'second'],
        ['get' => 'User/UserController@list'],
        ['post' => 'User/UserController@create'],
        ['put' => 'User/UserController@put']
    ],
];
Enter fullscreen mode Exit fullscreen mode

5. Basic Controller

  • Below is an example of a basic controller class. Note that the controller extends the Base controller class.
<?php

namespace App\Controllers\User;

use Atom\Controllers\Controller as BaseController;
use Atom\Http\Response;
use Atom\IMDB\Redis;
use Atom\Http\Request;
use Atom\Db\Database;
use Atom\Validation\Validator;
use Atom\Guard\Token;
use Atom\File\Log;
use Atom\File\Image;
use Atom\File\CSV;
use Atom\Guard\Auth;
use Atom\Template\Template;
use App\Models\User;
use Atom\Http\Url;

class UserController extends BaseController
{
    private $user;
    private $log;
    private $template;

    /**
    * User Controller construct
    *
    * @param User $user User
    */
    public function __construct(User $user)
    {
        parent::__construct();
        $this->user = $user;
        $this->log = new Log();
        $this->template = new Template();
    }

    /**
    * Created User Form
    *
    * @return void
    */
    public function createForm()
    {
        return template('admin', 'admin.users.create');
    }
}
Enter fullscreen mode Exit fullscreen mode
  • You can define a route to this controller action like so:
'/users/add' => [

    'middleware' => ['signed.url'],

    ['get' => 'User/UserController@createForm'],

],
Enter fullscreen mode Exit fullscreen mode

6. Dependency Injection & Controllers

Constructor Injection

You are able to type-hint any dependencies your controller may need in its constructor. The declared dependencies will automatically be resolved and injected into the controller instance:

<?php

namespace App\Controllers\User;

use Atom\Controllers\Controller as BaseController;
use Atom\Http\Response;
use Atom\Http\Request;
use Atom\File\Log;
use Atom\Template\Template;
use App\Models\User;

class UserController extends BaseController
{
    private $user;
    private $log;
    private $template;

    /**
    * User Controller construct
    *
    * @param User $user User
    */
    public function __construct(User $user)
    {
        parent::__construct();
        $this->user = $user;
        $this->log = new Log();
        $this->template = new Template();
    }
}
Enter fullscreen mode Exit fullscreen mode

Method Injection

In addition to constructor injection, you may also type-hint dependencies on your controller’s methods. A common use-case for method injection is injecting the Atom\Http\Request instance into your controller methods:

<?php

namespace App\Controllers\User;

use Atom\Controllers\Controller as BaseController;
use Atom\Http\Response;
use Atom\Http\Request;
use Atom\File\Log;
use Atom\Template\Template;
use App\Models\User;

class UserController extends BaseController
{
    /**
     * Delete User
     *
     * @param mixed $request Request
     *
     * @return void
     */
    public function delete(Request $request)
    {
        $database = new Database();
        $database->enableQueryLog();
        $database->table('users')->where(['id', $request['id']])->delete();
        $this->log->info($database->getQueryLog());
        Response::redirect('/users');
    }
}
Enter fullscreen mode Exit fullscreen mode

7. Model

Database: Query Builder

Atom query builder provides a convenient, fluent interface to creating and running database queries. The table method returns a fluent query builder instance for the given table, allowing you to chain more constraints onto the query and then finally get the results using the get method

$database = new Database();

$database->enableQueryLog();

$users = $database->table('users')->select(['id', 'fullname', 'email', 'thumb'])->where(['gender', '=', 'male'])->orWhere(['gender', '=', 'female'])->get();

Log::info($database->getQueryLog());
Enter fullscreen mode Exit fullscreen mode

If you just need to retrieve a single row from the database table, you may use the first method. This method will return an array:

$database = new Database();

$database->enableQueryLog();

$user = $database->table('users')->select(['id', 'fullname', 'email', 'thumb'])

->where(['id', $request['id']])->first();

Log::info($database->getQueryLog());
Enter fullscreen mode Exit fullscreen mode

Retrieving Models

  • The Atom provides a simple ORM that applies ActiveRecord implementation for working with your database. Each database table has a corresponding “Model” which is used to interact with that table. Models allow you to query for data in your tables, as well as insert new records into the table.

  • Remember to configure database connection. The database configuration for your application is located at config/env.ini. In this file you may define all of your database connections:

; Database config
DB_CONNECTION = 'mysql'
DB_HOST = '172.18.0.2'
DB_USER = 'root'
DB_PASSWORD = 'root@secret123'
DB_NAME = 'app'
DB_PORT = 3306
Enter fullscreen mode Exit fullscreen mode
  • Let’s create a model. Models typically live in the app/Models directory and all models must extend App\Models\Model class.
<?php

namespace App\Models;

use Atom\Models\Model as BaseModel;
use Atom\Db\Database;
use Atom\Models\Filterable;

class User extends BaseModel
{
    use Filterable;
    protected $table = 'users';
    protected $fillable = [
        'fullname',
        'email',
        'password',
        'photo',
        'gender',
        'thumb',
    ];

    protected $filterable = [
        'gender',
        'fullname' => ['LIKE' => '%{fullname}%'],
    ];
}
Enter fullscreen mode Exit fullscreen mode
  • You need specify the table name by defining a table property on your model:
protected $table = 'users';
Enter fullscreen mode Exit fullscreen mode
  • Once you have created a model and its associated database table, you are ready to start retrieving data from your database. Since each Eloquent model serves as a query builder, you may also add constraints to queries, and then use the get method to retrieve the results:
/**
* Show user's detail
*
* @return [type] [description]
*/
public function show(Request $request)
{
    $this->user->enableQueryLog();
    $user = $this->user->select(['id', 'fullname', 'email', 'thumb'])where(['id', $request->id])->get();
    Log::info($this->user->getQueryLog());
    if (isApi()) {
        return Response::toJson($user);
    }

    return template('admin', 'admin.users.show', compact('user'));
}
Enter fullscreen mode Exit fullscreen mode

8. View and Template

View

  • Views contain the HTML served by your application and separate your controller / application logic from your presentation logic. Views are stored in the resources/views directory. A simple view might look something like this:
<!DOCTYPE html>
<html lang="ja">
  <head>
    <?php view('admin.header') ?>
  </head>
  <body id="">
    <?php view('admin.nav') ?>
    <div id="content">
      <h1>Welcome to Atom Framework!!!</h1>
    </div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

As you can see, the first argument passed to the view helper corresponds to the name of the view file in the resources/views directory. The second argument is an array of data that should be made available to the view.

return view('admin.users.list, $data);
Enter fullscreen mode Exit fullscreen mode
  • Views may also be nested within subdirectories of the resources/views directory. “Dot” notation may be used to reference nested views. For example, if your view is stored at resources/views/admin/users.php

Template

  • Template assists to show the structure for the comprehensive layout. The process of template is same the view
public function list()
{
    $params = [
        'gender' => 'female',
        'fullname' => 'ngo',
    ];
    $this->user->enableQueryLog();
    $users = $this->user->select(['id', 'fullname', 'email', 'thumb'])->get();
    if (isApi()) {
        return Response::toJson($users);
    }
    return template('admin', 'admin.users.list', compact('users'));
}
Enter fullscreen mode Exit fullscreen mode

The first argument corresponds to the name of the template file in the config/templates.php.

return [
    "admin" => [
        "template" => [
            "header" => "admin.header",
            "content" => null,
            "footer" => "admin.footer",
        ],
    ],
];
Enter fullscreen mode Exit fullscreen mode

The second argument is used to reference nested views as template content, and the last argument is data

Top comments (2)

Collapse
 
jovialcore profile image
Chidiebere Chukwudi

Would like to know: what resources did you use in building this framework, any video , book, etc. Thank you

Collapse
 
jovialcore profile image
Chidiebere Chukwudi

Really cool