The PHP framework behind SitioYa

Koshkil documentation

Core

Routing and URL rewriting

From URL to controller

Route::parseURI() splits the URL without any route declarations:

  1. It lower-cases everything and replaces - with _ (/reset-passwordreset_password).
  2. If the first segment is admin and there are more segments, it removes it and marks the route as an admin one (looked up in Controllers/Admin/).
  3. If the next segment is the name of an active plugin, it removes it and looks for controllers in plugins/<plugin>/src/Controllers/.
  4. While the segments match sub-folders of Controllers/, they are consumed as folders.
  5. What is left is controller/action/arguments….
URL Controller Action Arguments
/ IndexController index
/cuentas CuentasController index
/cuentas/reset-password CuentasController resetPassword
/productos/ver/15/rojo ProductosController ver 15, rojo
/ajax/cuentas/login Ajax/CuentasController login
/admin/sistema/usuarios/editar/7 Admin/Sistema/UsuariosController editar 7
/noticias/listado (noticias plugin) plugins/noticias/…/ListadoController index

Arguments are passed to the action as parameters, in order:

public function ver($id = null, $color = null) {
    $producto = TMProductos::find(intval($id));
    if (!$producto) {
        $this->Response->setCode(404);
        return;
    }
    $this->set(['producto' => $producto]);
}

Action names

The action is converted to camelCase (reset_passwordresetPassword). The template, however, uses the original lower-case name: cuentas/reset_password.tpl.

Special parameters

  • ?action=name in the query string (or the POST data) replaces the URL's action.
  • ?subaction=name is available in Route::get()['subaction'].

Reading the current route

$route = Route::get();
// ['uri'=>'productos/ver/15', 'qs'=>'', 'plugin'=>'', 'controller'=>'Productos',
//  'action'=>'ver', 'raw_action'=>'ver', 'arguments'=>['15']]

Route::getDomain();     // 'https://mysite.com'
Route::setAction('other');

Plugin static files

If the URL starts with a plugin name and ends in .js, .css, .map or an image extension, Koshkil serves the file straight from plugins/<plugin>/webroot/. A .js URL that is not a real file is treated as the controller's javascript action, which is handy for generating JavaScript from PHP.

Rewrite rules

For URLs that don't follow the convention, define rules in config/rewrite.php (or the domain's) under System.Web.Rewrite. They are evaluated before routing, in order, against the URL without its leading slash.

<?php
$CONFIG = [
    'System' => [
        'Web' => [
            'Rewrite' => [
                // /negocios/lopez-bakery  ->  /negocios?nombre=lopez-bakery
                ['rule' => '^negocios/([a-z\-0-9]*)$', 'target' => '/negocios?nombre=$1', 'flags' => 'L,NC'],
                // alias for an admin section
                ['rule' => '^admin/usuarios/(editar|listado)$', 'target' => '/admin/sistema/usuarios/$1?subusuarios=1', 'flags' => 'L,NC'],
            ],
        ],
    ],
];
Part Meaning
rule Regular expression (~ delimiter).
target New URL. $1, $2… are replaced by the captured groups. The target's query string is added to $_GET.
flags L: last rule, stop evaluating. NC: case-insensitive. RW: keep evaluating the following rules against the rewritten URL.

Rules can also be added from code, before routing:

RewriteManager::addRule('^ofertas$', '/productos/listado?oferta=1', 'L,NC');
RewriteManager::addRule('^promo$', '/productos/promo', 'L', true); // true: at the top

Plugins declare their rules in plugins/<plugin>/config/rewrite.php (a $RULES array), added after the global ones. With Debug.Rewrite at 5 or more, every evaluation is logged to tmp/logs/rewrite.log.

Never hard-code absolute paths: the project may live in a sub-folder.

Method Returns
Koshkil::getLink('/productos/ver/15') A page URL (prepends Web.Path).
Koshkil::getPath('/css/app.css') A file's web path. With true as the second argument, its physical path on disk.
Koshkil::getThemePath('css/theme.css') A file inside the active theme.
Koshkil::redirect('/cuentas') Redirects and stops execution.
<a href="{Koshkil::getLink('/productos/ver/'|cat:$producto->prd_codigo)}">View</a>
<link rel="stylesheet" href="{Koshkil::getThemePath('css/theme.css')}">