Skip to content

Instantly share code, notes, and snippets.

@emsifa
Last active February 5, 2016 22:58
Show Gist options
  • Save emsifa/6b67a2919ef748ad3298 to your computer and use it in GitHub Desktop.
Save emsifa/6b67a2919ef748ad3298 to your computer and use it in GitHub Desktop.
Codeigniter Strict Controller

Codeigniter StrictController

Pada controller ini method _remap di override sedemikian rupa untuk menangani routing controller pada framework Codeigniter 3.x.

Jika controller kamu meng-extends controller ini, maka terdapat peraturan baru dalam menulis controller di CI, dan terdapat penambahan, diantaranya:

  • Penamaan method HARUS menggunakan camelCase, bukan snake_case seperti biasanya.
  • Parameter pada method dihitung, sehingga routing akan lebih strict.
  • Fitur anotasi untuk memfilter http request method dan uri params.

Penamaan method menggunakan camelCase.

Penamaan method menggunakan camelCase ini termasuk standar penulisan code pada PSR-1. Contoh

<?php

class Welcome extends StrictController
{

    public function fooBarBaz()
    {
        exit("Foo!");
    }

}

pada contoh diatas, jika kamu mengakses http://localhost/appnya/welcome/foo-bar-baz akan memanggil method fooBarBaz pada controller Welcome.

Method pada controller yang menggunakan snake_case (contoh: foo_bar_baz()) tidak dapat diakses melalui uri, dan akan menampilkan halaman 404.

Parameter pada method dihitung

Dengan controller ini, pemberian parameter di belakang uri yg mewakili nama method tidak bisa sembarangan, tapi akan dihitung. Contoh:

<?php

class Product extends StrictController
{
    public function view($url_title)
    {
        echo "view product {$url_title}";
    }
    
    public function list($category, $page = 1)
    {
        echo "view list products in category {$category} at page {$page}";
    }
}

Pada contoh diatas, method view hanya menerima sebuah parameter, dan method list hanya menerima 1 atau 2 parameter. Contoh jika kamu mengakses:

  • /product/view/satu/dua => 404 (karena terdapat 2 parameter di uri tersebut)
  • /product/view => 404 (karena tidak memiliki parameter)
  • /product/view/satu => 200 (tepat 1 parameter)
  • /product/list/laptop/1/2/3 => 404 (karena terdapat 4 parameter)
  • /product/list/laptop/12 => 200 (karena tepat 2 parameter)
  • /product/list/laptop => 200 (karena parameter $page bersifat optional)

Filter Request Menggunakan Anotasi di DocComment

Controller ini punya kemampuan untuk membaca DocComment pada method di controller guna memfilter http request method dan parameter. Contoh:

<?php

class Product extends StrictController
{
    
    /**
     * Contoh filter http request method
     * ----------------------------------------------
     * @method POST|PUT
     */
    public function postCreate()
    {
        // handle post create product
    }
    
    /**
     * Contoh filter parameter
     * ----------------------------------------------
     * @param string $page /\d+/
     */
    public function list($page = 1)
    {
        // handle page list product
    }
     
    /**
     * Contoh filter method dan parameter
     * ----------------------------------------------
     * @method GET
     * @param int $id /\d+/
     */
    public function detail($id = 1)
    {
        // handle page detail product
    }

}

Pada contoh diatas, method postCreate hanya bisa diakses oleh http request POST /product/post-create, atau PUT /product/post-create, jika tidak akan menampilkan halaman 404. Method list dapat menerima semua http request method, tetapi hanya menerima parameter $page yang bernilai numerik (regex /\d+/), jika tidak akan menampilkan halaman 404. Dan pada method detail hanya menerima method GET, parameter $id harus berupa numerik, dan parameter $id nantinya akan otomatis menjadi integer.

<?php
class StrictController extends CI_Controller
{
public function __construct()
{
parent::__construct();
}
public function _remap($raw_method, $args)
{
$method = $this->camelCase(str_replace('-', '_', $raw_method));
if (method_exists($this, $method)) {
$ref = new \ReflectionMethod($this, $method);
// jika pemanggilan method pada URI menggunakan camel case, tampilkan 404
if($this->isCamelCase($ref->getName()) AND $this->isCamelCase($raw_method)) {
show_404();
}
// parse anotasi yg terdapat pada method
$doc_comments = $this->parseDocComments($ref);
// tampilkan 404 jika terdapat anotasi @method pada method di controler dan method tersebut tidak cocok
if($doc_comments['methods'] AND !in_array($_SERVER['REQUEST_METHOD'], $doc_comments['methods'])) {
show_404();
}
$params = $ref->getParameters();
$count_required_params = 0;
// loop parameter yg terdapat pada method yang akan dijalankan
foreach ($params as $i => $param) {
if(!$param->isOptional()) $count_required_params++;
$param_name = $param->getName();
// jika terdapat @param $param_name
if(array_key_exists($param_name, $doc_comments['params']) AND isset($args[$i])) {
$value = $args[$i];
// tampilkan 404 jika regex pada anotasi $param_name tidak cocok
if($doc_comments['params'][$param_name]['regex'] AND !preg_match('/^'.$doc_comments['params'][$param_name]['regex'].'$/', $value)) {
show_404();
} else {
// sesuaikan tipe data yang di dekarasikan pada anotasi
switch($doc_comments['params'][$param_name]['type']) {
case 'int': $args[$i] = intval($args[$i]); break;
case 'float': $args[$i] = floatval($args[$i]); break;
}
}
}
}
// jika count parameter tidak cocok dengan method yang akan dipanggil, tampilkan 404
if (count($args) < $count_required_params OR count($args) > count($params)) {
show_404();
} else {
call_user_func_array([$this, $method], $args);
}
} else {
show_404();
}
}
protected function parseDocComments(\ReflectionMethod $method)
{
$doc_comments = $method->getDocComment();
$doc_comments = str_replace("\n", " \n", $doc_comments); // add a space for regex param bug
preg_match("/\* \@method (?<methods>[^\n]+)/", $doc_comments, $matched_method);
$ws = "[ \\t]+";
$r_type = '(?<type>(string|int|float))';
$r_regex = '(\/(?<regex>[^\/]+)\/)?';
$r_varname = '\$(?<var>\w+)';
preg_match_all("/\* \@param{$ws}{$r_type}{$ws}{$r_varname}{$ws}{$r_regex}[^\n]+/", $doc_comments, $matched_params);
$methods = null;
if(!empty($matched_method)) {
$methods = explode('|', strtoupper(trim($matched_method['methods'])));
}
$params = [];
foreach($matched_params['var'] as $i => $varname) {
$params[$varname] = [
'type' => $matched_params['type'][$i],
'regex' => $matched_params['regex'][$i],
];
}
$result = [
'methods' => $methods,
'params' => $params,
];
if($result['methods'] AND in_array('GET', $result['methods'])) {
$result['methods'][] = 'HEAD';
}
return $result;
}
protected function isCamelCase($str)
{
return $this->snakeCase($str) != $str;
}
protected function snakeCase($value, $delimiter = '_')
{
$key = $value.$delimiter;
if (! ctype_lower($value)) {
$value = preg_replace('/\s+/', '', $value);
$value = strtolower(preg_replace('/(.)(?=[A-Z])/', '$1'.$delimiter, $value));
}
return $value;
}
protected function camelCase($value)
{
$key = $value;
$value = ucwords(str_replace(['-', '_'], ' ', $value));
return lcfirst(str_replace(' ', '', $value));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment