Build your own PHP framework

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();
}
  • 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();
    }
}

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 localhost/users in your browser:

return [
    '/users' => [
        'middleware' => ['first', 'auth', 'second'],
        ['get' => 'User/UserController@list'],
        ['post' => 'User/UserController@create'],
        ['put' => 'User/UserController@put']
    ],
];
  • 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']
    ],
];
  • 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'],

    ],

];

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();
    }
}
  • 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',
    ],
];
  • 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']
    ],
];

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');
    }
}
  • You can define a route to this controller action like so:
'/users/add' => [

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

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

],

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

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

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());

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());

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
  • 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}%'],
    ];
}
  • You need specify the table name by defining a table property on your model:
protected $table = 'users';
  • 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'));
}

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>

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

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",
        ],
    ],
];

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