nebiros/yasc

Yet Another Sinatra Clone

v0.1.21 2015-02-17 21:55 UTC

This package is not auto-updated.

Last update: 2024-12-21 17:27:44 UTC


README

Yet Another Sinatra Clone

yasc is a sinatra (kind of) clone written in php and highly influenced by zend framework 1.x, the routing system is based on limonade's code, is a tiny framework that uses user defined functions as actions (like in the MVC pattern) and annotations to route the requested url to a function.

Features

  • Functions based.
  • RESTful.
  • Tiny ^_^.
  • Routing system based on regex patterns, named params, pairs param (like zend framework 1.x).
  • View helpers.
  • Function helpers.
  • Layouts support.
  • Class autoloading based on the Framework Interop Group.
  • Models support.

Default project structure

yasc uses this project structure:

app.php
views/
    helpers/
models/

If you create these folders in your project they are auto-loaded in your application. Default namespaces:

helpers/Helper_*
models/Model_*

Accessors

I saw that invoking the requested function with a lot of arguments is too ugly and a little bit verbose (just a little?), so I add some accessors to be used in each function.

<?php

Yasc_App::view() // Access the view object inside your function.
Yasc_App::params() // Get all route params.
Yasc_App::params("key") // Get param "key" value, also, you can specify a default value as the second argument of this static method.
Yasc_App::config() // App config.
Yasc_App::viewHelper("url") // Get a view helper, like Layout, Url, or some of your own.
Yasc_App::functionHelper("flash") // Get a function helper, like Flash, this helper is a stack of messages, errors, notices, etc.

Prerequisites

yasc requires PHP 5.2.4 or later.

Installation

Easiest way to install yasc is through composer,

php composer.phar require nebiros/yasc "~0.1.18"

or add it as a git submodule.

Create a index.php or a app.php file and include yasc, like:

namespace My\App;

// composer autoload.
require_once "vendor/autoload.php";
// yasc runtime.
require_once "vendor/nebiros/yasc/src/Yasc.php";

$yasc = new \Yasc();
$yasc->run(__NAMESPACE__);
unset($yasc);

Add a function and a pattern using an annotation for your application index, your script will be something like this:

<?php
namespace My\App;

// composer autoload.
require_once "vendor/autoload.php";
// yasc runtime.
require_once "vendor/nebiros/yasc/src/Yasc.php";

$yasc = new \Yasc();
$yasc->run(__NAMESPACE__);
unset($yasc);

/**
 * @GET("/")
 */
function index() {
    echo "Hello world!";
}

Now, go to your favorite browser and run your script:

http://localhost/app.php

Follow the examples and you'r done :).

Setup

Maybe you want to hide your script file from the URL, http://app.com/app.php/some/thing (I know, pretty URLs, SEO shut, blah, blah) and get something fancier like: http://app.com/some/thing, ok, well, create a VirtualHost and add a .htaccess file to your application folder like this:

Virtual host configuration

<VirtualHost *:80>
   DocumentRoot "/path/to/your/application/public"
   ServerName app.com
   <Directory "/path/to/your/application/public">
       Options -Indexes MultiViews FollowSymLinks
       AllowOverride All 
       Order allow,deny
       Allow from all 
   </Directory>
</VirtualHost>

.htaccess file

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

NOTE: Your app file must be named index.php in order to work with this .htaccess file.

Simple Example

<?php
namespace My\App;

// composer autoload.
require_once "vendor/autoload.php";
// yasc runtime.
require_once "vendor/nebiros/yasc/src/Yasc.php";

$yasc = new \Yasc();
$yasc->run(__NAMESPACE__);
unset($yasc);

/**
 * @GET("/")
 */
function index() {
    echo "Hello world!";
}

/**
 * @POST("/")
 */
function create() {
    // save something.
}

/**
 * @PUT("/")
 */
function update() {
    // update something.
}

/**
 * @DELETE("/")
 */
function destroy() {
    // delete something.
}

Configuration

<?php

/**
 * Function to configure some yasc options. This function is optional you don't
 * need to write it in your app script if you don't want.
 */
function configure() {
    // * You can add a layout, a layout is just a .phtml file that represents
    // the site template.
    Yasc_App::config()->setLayoutScript(dirname(__FILE__) . "/layouts/default.phtml");
    
    // * If you want to use a stream wrapper to convert markup of mostly-PHP 
    // templates into PHP prior to include(), seems like is a little bit slow,
    // so by default is off.
    // ->setViewStream(true);
    // 
    // * You can add more than one folder to store views, each view script
    // is a .phtml file.
    // ->addViewsPath(dirname(__FILE__) . "/extra_views");
    // 
    // * You can add more than one path of view helpers and set a
    // class prefix for the path added.
    // ->addViewHelpersPath(dirname(__FILE__) . "/../library/My/View/Helper", "My_View_Helper");
    // 
    // or if you don't want a class prefix just leave it blank.
    // ->addViewHelpersPath(dirname(__FILE__) . "/extra_views/helpers");
    //
    // * Function helpers, second argument is a prefix class.
    // ->addFunctionHelpersPath(dirname(__FILE__) . "/extra_function_helpers");
    // 
    // * Add models folder, second argument is a prefix class.
    // ->addModelsPath(dirname(__FILE__) . "/models");
    // ->addModelsPath(dirname(__FILE__) . "/extra_models/My/Model", "My_Model");
    // 
    // * Add extra options to the configuration object, like some $mysql connection 
    // resource or a global flag, etc.
    // ->addOption("db", $mysql);
}

Pre- and Post-Dispatch Hooks

We provide two functions, pre_dispatch and post_dispatch, that may be called before and after an action is invoked.

Advanced Examples

<?php

/**
 * @GET("/")
 */
function index() {
    // Use layout view helper to disable the layout or use Yasc_Layout object
    // Yasc_Layout::getInstance()->disable(), Yasc_Layout uses singleton pattern.    
    Yasc_App::view()->layout()->disable();
    
    // Get the mysql resource from this app configuration option.
    // 
    // $mysql = Yasc_App::config()->getOption("db");
    // 
    // ... do some sql operation.
    
    echo "Hello world!";
}

/**
 * @POST("/")
 * 
 * You can route the same url to another function using a different request
 * method.
 */
function save_index() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "post: ";
    var_dump($_POST);
    echo "</pre>";
}

/**
 * @GET("/tales")
 */
function tales() {    
    // You can add variables to the view object and get his value on
    // the view script using the variable $this, like: $this->tales.
    Yasc_App::view()->tales = "oh! I'm a view variable!";

    // Render a view script, a view script is a .phtml file where you can mix
    // php and html, the V in the MVC model, in this example the view files
    // are stored in views/ folder.
    // 
    // This view calls a view helper 'Tales', so check views/helpers/Tales.php 
    // to see what it does.
    Yasc_App::view()->render("tales");
}

/**
 * @GET("/tales/:lol")
 * @POST("/woot") // Ignored, yasc only uses the first annotation found.
 * 
 * Named params, you can access those via Yasc_App::params() method.
 * 
 * Matches: /tales/foo and /tales/bar
 */
function tales1() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "lol value: " . Yasc_App::params("lol");
    echo "</pre>";
    Yasc_App::view()->tales = "oh! I'm a view variable!";
    
    // instance of a model.
    $foo = new Model_Foo();
    Yasc_App::view()->helloModel = $foo->doSomething();
    
    // Render a view without the layout.
    Yasc_App::view()->render("tales");
}

/**
 * @GET("/tales/:lol/id/:id")
 */
function tales2() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "lol value: " . Yasc_App::params("lol");
    echo "id value: " . Yasc_App::params("id");
    echo "</pre>";
}

/**
 * @POST("/tales3")
 */
function tales3() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "post: ";
    var_dump($_POST);
    echo "</pre>";
}

/**
 * @GET("/foo")
 */
function foo() {
    // Render view script foo, this view script calls the view helper class 'Foo',
    // this view helper render a view helper script inside and return his content
    // to this view, a view helper script is just another .phtml file, if you don't
    // want to create a whole html string inside the helper ;).
    Yasc_App::view()->render("foo");
}

/**
 * @GET("^/regex/id/(\d+)/name/([a-z]+)")
 * 
 * You can create patterns using a regular expression, this kind of route must
 * begin with a '^'. Check limonade docs and code if you want more information, or
 * just use limonade framework :), it's a more robust framework.
 * 
 * http://www.limonade-php.net/README.htm
 * https://github.com/sofadesign/limonade/blob/master/lib/limonade.php
 * 
 * Matches: /regex/id/26/name/juan
 */
function regex() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "params: ";
    var_dump(Yasc_App::params());
    echo "</pre>";
}

/**
 * @GET("/say/*\/to\/*")
 * 
 * Patterns may also include wildcard parameters. Each value is associted 
 * through numeric indexes, in the same order as in the pattern.
 * 
 * Matches: /say/hello/to/world
 */
function single_asterisk() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "params: ";
    var_dump(Yasc_App::params());
    echo "hello: ";
    var_dump(Yasc_App::params(0)); // hello
    echo "world: ";
    var_dump(Yasc_App::params(1)); // world
    echo "</pre>";    
}

/**
 * @GET("/download/*.*")
 * 
 * Matches: /download/file.xml
 */
function download() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "params: ";
    var_dump(Yasc_App::params());
    echo "filename: ";
    var_dump(Yasc_App::params(0)); // file
    echo "ext: ";
    var_dump(Yasc_App::params(1)); // xml
    echo "</pre>";    
}

/**
 * @GET("/writing/*\/to\/**")
 * 
 * The double wildcard '**' matches a string like this one: my/friend/juan/badass,
 * this string is treated as pairs, this way: param1/value1/param2/value2 etc, like
 * Zend Framework does, so, via Yasc_App::params() method you can get those 
 * values using each key.
 * 
 * Matches: /writing/hello/to/my/friends/from/limonade/you_guys/roxor
 */
function pairs() {
    Yasc_App::view()->layout()->disable();
    
    echo "<pre>";
    echo "params: ";
    var_dump(Yasc_App::params());
    echo "single wildcard: ";
    var_dump(Yasc_App::params(0)); // hello
    echo "param my: ";
    var_dump(Yasc_App::params("my")); // friend
    echo "param from: ";
    var_dump(Yasc_App::params("from")); // limonade
    echo "param you_guys: ";
    var_dump(Yasc_App::params("you_guys")); // roxor
    echo "</pre>";    
}

/**
 * @GET("/update")
 */
function update() {
    /* @var $flash Yasc_Function_Helper_Flash */
    $flash = Yasc_App::functionHelper("flash");
    Yasc_App::view()->flash = $flash;
    
    return Yasc_App::view()->render("update");    
    
    // Use '_method' parameter in POST requests when PUT or DELETE methods 
    // are not supported.
    
    /*
    <form id="put" name="put" action="<?php echo $this->url(array("uri" => "/update")) ?>" method="post">
        <p>First name: <input type="text" name="first_name" /></p>
        <p>Last name: <input type="text" name="last_name" /></p>
        <p><input type="submit" value="Update" /></p>
        <input type="hidden" name="_method" value="PUT" id="_method" />
    </form>
    */
}

/**
 * @PUT("/update")
 */
function save_update() {
    Yasc_App::view()->layout()->disable();
    
    // $mysql = Yasc_App::config()->getOption("db");
    // $mysql->update("table1", array("first_name" => $_POST["first_name"], "last_name" => $_POST["last_name"]));
    
    /* @var $flash Yasc_Function_Helper_Flash */
    $flash = Yasc_App::functionHelper("flash");
    $flash->message("Done.");
    
    header("Location: " . Yasc_App::viewHelper("url")->url(array("uri" => "/update")));
}

/**
 * @DELETE("/delete")
 */
function destroy() {
    Yasc_App::view()->layout()->disable();
    
    // $mysql = Yasc_App::config()->getOption("db");
    // $mysql->delete("table1", "id = {$_POST["id"]}");
    
    header("Location: " . Yasc_App::viewHelper("url")->url(array("uri" => "/update")));
}

TODO

  • Support for PUT and DELETE methods.
  • Support regex in annotations.
  • Add PUT and DELETE annotations.
  • Add layouts support.
  • Add view helpers support.
  • Caching.
  • Tests.
  • Improve documentation.