codecraft63/limonade

此软件包最新版本(dev-master)没有可用的许可证信息。

一个PHP微型框架

dev-master 2016-05-20 18:54 UTC

This package is not auto-updated.

Last update: 2024-09-20 19:43:46 UTC


README

Limonade 是一个用于快速网页开发和原型设计的 PHP 微型框架。

它受到了 Ruby 中的 Sinatra 或 Camping 以及 Lua 中的 Orbit 等框架的启发。它旨在简单、轻量且极具灵活性。

Limonade 提供了补充 PHP 基本集的功能,同时保持与原生函数的一致性并在此基础上扩展。

Limonade 容易学习,并提供了现代框架(MVC、REST 等)所能提供的一切。

require_once 'lib/limonade.php';
dispatch('/', 'hello');
    function hello()
    {
        return 'Hello world!';
    }
run();

关于本文档

本文档提供了 Limonade 特性的快速但全面的指南。

有关更多信息,您可以查看 网站示例 以及当然的 源代码,这仍然是最好的文档。

还有一个 讨论组 可以用于更多交流。

要求

  • PHP 5.1.6 > (已成功测试与 PHP 5.1.6 兼容,但可能也适用于旧版本)

路由

路由结合

  • 一个 HTTP 方法
  • 与 URL 匹配模式
  • 以及回调参数

因此,它们在 URL + HTTP 方法与回调控制器中提供的代码之间起到粘合剂的作用。

dispatch('/', 'my_get_function');
# same as dispatch_get('my_get_function');
    function my_get_function()
    {
        // Show something
        // with the code of this callback controller
    }

dispatch_post('/', 'my_post_function'); 
    function my_post_function()
    {
        // Create something
    }
    
dispatch_put('/', 'my_update_function'); 
    function my_update_function()
    {
        // Update something
    }
    
dispatch_delete('/', 'my_delete_function'); 
    function my_delete_function()
    {
        // Delete something
    }

dispatch_patch('/', 'my_patch_function');
    function my_patch_function()
    {
        // Patch something
    }

路由按声明的顺序进行匹配。搜索通过浏览器 URL 提供的路径进行

https:///my_app/?u=/my/path
https:///my_app/?uri=/my/path
https:///my_app/index.php?/my/path
https:///my_app/?/my/path

当不支持 PUTDELETEPATCH 方法(如在 HTML 表单提交中)时,您可以在 POST 请求中使用 _method 参数:它将覆盖 POST 方法。

<form action="<?php echo url_for('profile_update'); ?>" method="post">
    <p><input type="hidden" name="_method" value="PUT" id="_method"></p>
    <p>... your form fields</p>
    <p><input type="submit" value="Update"></p>
</form>

路由模式和参数

模式可以包含命名参数。这些参数的关联值可以通过 params() 函数获得。

dispatch('/hello/:name', 'hello');
    function hello()
    {
        $name = params('name');
        return 'Hello $name';
    }

模式还可以包含通配符参数。关联值可以通过与模式相同的顺序的数字索引获得。

dispatch('/writing/*/to/*', 'my_letter');
    function my_letter()
    {
        # Matches /writing/an_email/to/joe
        $type = params(0); # "an_email"
        $name = params(1); # "joe"
        # ...
    }
    
dispatch('/files/*.*', 'share_files');
    function share_files()
    {
        # matches /files/readme.txt
        $ext = params(1);
        $filename = params(0).".".$ext;
        # ...
    }

与简单的通配符字符 * 不同,双通配符字符 ** 指定可以包含 / 的字符串

dispatch('/files/**', 'share_files')
    function share_files()
    {
        # Matches /files/my/own/file.txt
        $filename = params(0); # my/own/file.txt
    }

如果模式以 ^ 开头,则模式也可以是正则表达式

dispatch('^/my/own/(\d+)/regexp', 'my_func');
    function my_func()
    {
        # matches /my/own/12/regexp
        $num = params(0);
    }

通配符参数和正则表达式也可以命名。

dispatch(array('/say/*/to/**', array("what", "name")), 'my_func');
    function my_func()
    {
        # Matches /say/hello/to/joe
        $what = params('what');
        $name = params('name');
    }

您还可以提供默认参数值,这些值将与模式参数合并并由模式参数覆盖。

$options = array('params' => array('firstname'=>'bob'));
dispatch('/hello/:name', 'hello', $options);
    function hello($firstname, $name) # default parameters first
    {
        return 'Hello $firstname $name';
    }

回调控制器

回调可以是函数、对象方法、静态方法或闭包。有关回调伪类型的更多信息,请参阅 php 文档

# will call my_hello_function() function
dispatch('/hello', 'my_hello_function');

# Static class method call, MyClass::hello();
dispatch('/hello', array('MyClass', 'hello'));

# Object method call, $obj->hello();
dispatch('/hello', array($obj, 'hello'));

# Static class method call (As of PHP 5.2.3), MyClass::hello();
dispatch('/hello', 'MyClass::hello');

# Using lambda function (As of PHP 5.3.0)
dispatch('/hello', function(){
  return 'Hello World!';
});

回调控制器返回渲染的视图输出(请参阅 视图和模板)。

它们可以接受模式参数作为参数

dispatch('/hello/:firstname/:name', 'hello');
    function hello($firstname, $name)
    {
        # $firstname parameter equals params('firstname');
        # and $name parameter equals params('name');
        return 'Hello $firstname $name';
    }

由路由调用的回调可以在 run() 函数执行之前编写在任何地方。它们也可以分组在存储在 controllers/ 文件夹中的控制器文件中。

/                   # site root
 - index.php        # file with routes declarations and run()
 + controllers/
     - blog.php     # functions for blog: blog_index(), blog_show(),
                    #  blog_post()...
     - comments.php # comments_for_a_post(), comment_add()...

此文件夹位置可以通过 controllers_dir 选项进行设置。

option('controllers_dir', dirname(__FILE__).'/other/dir/for/controllers');

您还可以定义 autoload_controller 函数以自定义加载控制器

function autoload_controller($callback) 
{ 
   # If $callback, the callback function defined in matching route, 
   # begins with 'admin_', then we load controllers from
   # the admin sub-directory in the controllers directory.
   # Else we load controllers the normal way from 'controllers_dir'.
   
   $path = option('controllers_dir'); 
   if(strpos($callback, "admin_") === 0) $path = file_path($path, 'admin'); 
   require_once_dir($path); 
}

URL重写

从版本0.4.1开始,Limonade支持URL重写。

如果您使用Apache,在您的应用文件夹中创建一个.htaccess文件

<IfModule mod_rewrite.c>
  Options +FollowSymlinks
  Options +Indexes
  RewriteEngine on
  
  # if your app is in a subfolder
  # RewriteBase /my_app/ 

  # test string is a valid files
  RewriteCond %{SCRIPT_FILENAME} !-f
  # test string is a valid directory
  RewriteCond %{SCRIPT_FILENAME} !-d

  RewriteRule ^(.*)$   index.php?uri=/$1    [NC,L,QSA]
  # with QSA flag (query string append),
  # forces the rewrite engine to append a query string part of the
  # substitution string to the existing string, instead of replacing it.
</IfModule>

如果您使用Nginx,请将以下内容添加到您的服务器声明中

server {
    location / {
        
        try_files $uri $uri/ @rewrite;
    }
    location @rewrite {
        rewrite ^/(.*)$ /index.php?u=$1&$args;
    }
}

然后请记住在您的 configure() 函数中显式设置 option('base_uri')

option('base_uri', '/my_app'); # '/' or same as the RewriteBase in your .htaccess

您可以使用类似 http://your.new-website.com/my/limonade/path 的URL访问您的网站,而不是 http://your.new-website.com/?/my/limonade/path

视图和模板

模板文件默认位于 views/ 文件夹中。视图文件夹的位置可以通过 views_dir 选项进行设置。

option('views_dir', dirname(__FILE__).'/other/dir/for/views');

要将变量传递给模板,我们使用 set () 函数

set('name', 'John Doe');
render('index.html.php');

变量也可以直接传递

render('index.html.php', null, array('name' => 'John Doe' ));

set_or_default 函数允许传递一个变量,如果它为空,则传递默认值。这对于使用 params() 函数从URL中提取的可选参数的分配非常有用。

dispatch('/hello/:name', 'hello');
    function  hello()
    {
        # matching /hello/
        set_or_default('name', params('name'),'John');
        return render('Hello %s!'); // returns 'Hello John!' because params('name') was empty. Else it would have returned params('name') value.
    }

如您所注意到的,最终输出是由您的控制器返回的。因此,请记住在控制器中显式地使用 return 关键字返回您的视图!(这对Ruby程序员特别有帮助;-)

布局

模板可以嵌入到另一个模板中:一个布局。

可以使用 layout 函数设置布局

layout('default_layout.php');

或直接使用模板渲染函数

render('index.html.php', 'default_layout.php');

如果布局值为 null,则渲染将不使用任何布局。

render('index.html.php', null);

格式化字符串和内联模板

格式化字符串可以使用与 sprintf 相同的方式使用

set('num', 5);
set('where', 'tree');
return render('There are %d monkeys in the %s') // returns 'There are 5 monkeys in the tree'

还可以提供一个函数名作为模板。这样,例如,我们可以生成一个单文件应用程序。

function html_message($vars){ extract($vars);?>
    <h1>Title: <?php echo h($title); ?></h1>
    <p>Message:<br>
       <?php echo h($msg); ?></p>
<?}

// in a request handling function
set('title', 'Hello!');
set('msg', 'There are 100 monkeys in the Chennai and bangalore');
return render('html_message');

HTML模板

html 函数与 render 使用方式相同。一个标题指定了正确的HTTP Content-type(默认为 text/html)和通过选项定义的编码设置。

html('my_template.html.php');

模板XML

xml 函数与 render 使用方式相同。一个标题指定了正确的HTTP Content-type(默认为 text/xml)和通过选项定义的编码设置。

xml('my_template.xml.php');

模板CSS

css 函数与 render 使用方式相同。一个标题指定了正确的HTTP Content-type(默认为 text/css)和通过选项定义的编码设置。

css('screen.css.php');

模板JS

js 函数与 render 使用方式相同。一个标题指定了正确的HTTP Content-type(默认为 application/javascript)和通过选项定义的编码设置。

js('app.js.php');

模板TXT

txt 函数与 render 使用方式相同。一个标题指定了正确的HTTP Content-type(默认为 text/plain)和通过选项定义的编码设置。

txt('index.txt.php');

模板JSON

jsonjson_encode 函数使用方式相同,并返回一个包含值的JSON表示的字符串。一个标题指定了正确的HTTP Content-type(默认为 application/x-javascript)和通过选项定义的编码设置。

json($my_data);

文件服务

render_file 函数可以直接将文件渲染到输出缓冲区。

render_file(option('public_dir').'foo.jpg');

一个标题指定了根据文件扩展名正确的HTTP Content-type,对于文本文件,编码设置通过选项定义(默认为utf8)。

输出被缓存,以便可以轻松处理大文件。

部分

partial 函数是一个用于无布局渲染的快捷方式。对于管理可重用块并将它们保存在单独的文件中非常有用。

以下代码:

partial('my_posts.php', array('posts'=>$posts));

等同于:

render('my_posts.php', null, array('posts'=>$posts));

捕获:

content_for 函数允许您在视图中捕获一段文本。然后,捕获的块将可用于布局。这对于管理布局区域(如侧边栏)或设置针对视图特定的 JavaScript 或样式表文件非常有用。

例如,使用此布局:

<div id="content">
  <div id="main">
    <?php echo $content; ?>
  </div>
  <div id="side">
    <?php if (isset($side)) echo $side; ?>
  </div>
</div>

在你的视图中:

<p>My main content</p>

<?php content_for('side'); ?>
<ul>
  <li><a href="<?php echo url_for('/pages/item1')?>">Item 1</a></li>
  <li><a href="<?php echo url_for('/pages/item2')?>">Item 2</a></li>
</ul>
<?php end_content_for(); ?>

渲染结果是:

<div id="content">
  <div id="main">
    <p>My main content</p>
  </div>
  <div id="side">
    <ul>
      <li><a href="?/pages/item1">Item 1</a></li>
      <li><a href="?/pages/item1">Item 2</a></li>
    </ul>
  </div>
</div>

上述示例在这篇教程中有详细说明。

使用捕获与部分视图一起使用,可以帮助您组织视图,并避免多次复制/粘贴相同的代码。

钩子和过滤器:

Limonade 允许用户定义一些函数来增强 Limonade 的行为以适应自己的需求。

其中一些,如 before 钩子和 after 过滤器被广泛使用,而其他一些则仅供高级使用,可能需要很好地理解 Limonade 的内部结构。

在请求之前:

您可以在每个请求之前定义一个 before 函数。这对于定义默认布局或将常用变量传递给模板非常有用。

function before($route)
{
    layout('default_layout.php');
    set('site_title', 'My Website');
}

当前匹配的路由也会传递给 before 函数,因此您可以对其进行测试。它是一个数组,由内部 route_find 函数返回,包含以下值:

  • method(HTTP 方法)
  • pattern(正则表达式模式)
  • names(参数名称)
  • callback(回调函数)
  • options(路由选项)
  • params(当前参数)

在请求之后:

还提供了一个 after 输出过滤器。它在每个请求之后执行,可以对输出应用转换(除了直接发送到输出缓冲区的 render_file 输出)。

function after($output){
  $config = array('indent' => TRUE,
                  'output-xhtml' => TRUE,
                  'wrap' => 200);
  
  $encoding = strtoupper(str_replace('-','', option('encoding')));
  $tidy = tidy_parse_string($output, $config, $encoding);
  $tidy->cleanRepair();
  return $tidy;
}

当前执行的路由也可用于 after 函数。

在渲染之前:

您可以在渲染视图之前定义一个 before_render 函数来过滤视图。

前三个参数与传递给 render 函数的参数相同:

  • $content_or_func:视图字符串
  • $layout:当前布局路径
  • $locals:直接传递给 render 函数的变量

最后一个参数 $view_path 默认为 file_path(option('views_dir'), $content_or_func);

function before_render($content_or_func, $layout, $locals, $view_path)
{
  # Transform $content_or_func, $layout, $locals or $view_path.
  # Then return there new values
  return array($content_or_func, $layout, $locals, $view_path);
}

自动渲染:

您可以定义自己的 autorender 函数,根据当前匹配的路由来执行自动渲染。如果控制器返回 null 输出,则将执行该函数。

dispatch('/', 'hello');
function hello()
{
    # process some stuff...
    set('name', 'Bob');
    
    # but don't return anything
    # ( like if you were ending this function with return null; )
}

function autorender($route)
{
    $view = $route['callback'] . ".html.php";
    return html($view);
}

在下面的例子中,当调用 URL / 时,执行 hello(),然后 autorender() 渲染匹配的 hello.html.php 视图。

在退出之前:

如果您定义了 before_exit,则它在停止/退出过程开始时调用(Limonade 应用程序终止时自动调用 stop_and_exit 函数)。

function before_exit($exit)
{
    # $exit is the same parameter as the one passed to `stop_and_exit`.
    # If it's false, the exit process will not be executed, 
    # only the stop instructions
    # by default it is true
}

在发送头信息之前:

您可以在 Limonade 发出 header() 调用之前定义一个 before_sending_header 函数。这样,您就可以添加额外的头信息。

dispatch('/style.css', 'css');
function css()
{
    # Generate css file and output
    return css('style.css.php');
}

function before_sending_header($header)
{
    if (strpos($header, 'text/css') !== false)
    {
        # intercept text/css content-type and add caching to the headers
        send_header("Cache-Control: max-age=600, public");
    }
}

注意:小心不要从 before_sending_header 函数中重复调用 send_header() 导致循环!

配置:

您可以在应用程序启动时执行 configure,在 run 执行的开始。您可以在其中定义选项,例如数据库连接...

function configure()
{
    $env = $_SERVER['HTTP_HOST'] == "localhost" ? ENV_DEVELOPMENT : ENV_PRODUCTION;
    option('env', $env);
    if(option('env') > ENV_PRODUCTION)
	{
		options('dsn', 'sqlite:db/development.db'));
	}
	else
	{
	    options('dsn', 'sqlite:db/production.db'));
	}
    $GLOBALS['my_db_connexion'] = new PDO(option('dsn'));
}

包含在 option('lib_dir') 文件夹中的 PHP 文件(默认为 lib/)将在执行 configure 后通过 require_once 加载。因此,您可以在此文件夹中放置所有 PHP 库和函数,以便它们在应用程序启动时加载并可用。

选项:

option 函数允许您定义和访问应用程序的选项。

option('env', ENV_PRODUCTION);
option('env'); // return ENV_PRODUCTION value

如果没有指定选项名称,则返回所有设置的选项数组。

您可以使用它来管理Limonade选项和您自己的自定义选项。

默认的Limonade选项具有以下值

option('root_dir',        $root_dir); // this folder contains your main application file
option('base_path',          $base_path);
option('base_uri',           $base_uri); // set it manually if you use url_rewriting
option('limonade_dir',       dirname(__FILE__).'/'); // this fiolder contains the limonade.php main file
option('limonade_views_dir', dirname(__FILE__).'/limonade/views/');
option('limonade_public_dir',dirname(__FILE__).'/limonade/public/');
option('public_dir',         $root_dir.'/public/');
option('views_dir',          $root_dir.'/views/');
option('controllers_dir',    $root_dir.'/controllers/');
option('lib_dir',            $root_dir.'/lib/');
option('error_views_dir',    option('limonade_views_dir'));
option('env',                ENV_PRODUCTION);
option('debug',              true);
option('session',            LIM_SESSION_NAME); // true, false or the name of your session
option('encoding',           'utf-8');
option('x-sendfile',         0); // 0: disabled, 
                                 // X-SENDFILE: for Apache and Lighttpd v. >= 1.5,
                                 // X-LIGHTTPD-SEND-FILE: for Apache and Lighttpd v. < 1.5

会话

默认情况下,会话会自动启动。然后您可以通过$_SESSION数组像以前一样访问会话变量。

您可以使用session选项禁用会话。

查看代码片段示例

Flash

Flash是会话的特殊用途。Flash值仅在下一个请求中可用,并在之后被删除。它在表单中引发错误或通知成功操作时非常有用。

  • flash($name, $value...)为下一个请求定义一个Flash
  • 在视图中,您可以使用$flash数组或flash_now($name)函数获取当前Flash值。

查看代码片段示例

辅助工具

有关所有可用辅助工具的更多信息,请参阅源代码或API。

url_for

您可以使用url_for函数来渲染Limonade URL。它们将从您的应用程序安装的文档根目录中的任何文件夹生成。

# with option('base_uri', '?')
url_for('one', 'two', 'three'); # returns ?/one/two/three
url_for('one', 'two', array('page' => 1)); # returns ?/one/two&amp;page=2

如果您想使用URL重写,则需要明确设置base_uri选项(默认为/your_file_path/?

停止执行和错误处理

停止

您可以使用halt函数立即停止应用程序的执行。错误将由默认的Limonade错误处理程序或您定义的错误处理程序处理。

halt(NOT_FOUND);
halt("En error occured in my app...");

未找到

默认情况下,显示not_found错误输出函数并发送一个404 NOT FOUND HTTP头。

halt(NOT_FOUND);
halt(NOT_FOUND, "This product doesn't exists.");

要为此错误定义一个新的视图,您可以简单地声明一个not_found函数。

function not_found($errno, $errstr, $errfile=null, $errline=null)
{
    set('errno', $errno);
    set('errstr', $errstr);
    set('errfile', $errfile);
    set('errline', $errline);
    return html("show_not_found_errors.html.php");
}

服务器错误

默认情况下,显示server_error错误输出函数并发送一个500 INTERNAL SERVER ERROR HTTP头。

halt();
halt('Breaking bad!');
halt(SERVER_ERROR, "Not good...");
trigger_error("Wrong parameter", E_USER_ERROR);

PHP错误也会被捕获并发送到此错误处理程序输出。

要为此错误定义一个新的视图,您可以简单地声明一个server_error函数。

function server_error($errno, $errstr, $errfile=null, $errline=null)
{
    $args = compact('errno', 'errstr', 'errfile', 'errline');	
    return html("show_server_errors.html.php", error_layout(), $args);
}

错误布局

允许您定义和访问一个专用于错误的布局。

error_layout('error_layout.php');
error_layout(); // return 'error_layout.php'

错误处理

除了常见的NOT_FOUNDSERVER_ERROR错误显示之外,Limonade还可以将精确的错误重定向到您自己的函数。

error(E_USER_WARNING, 'my_notices')
    function my_notices($errno, $errstr, $errfile, $errline)
    {
        // storing php warnings in a log file
        // ...
        status(SERVER_ERROR);
        return html('<h1>Server Error</h1>');
    }

E_LIM_HTTP表示所有HTTP错误

error(E_LIM_HTTP, 'my_http_errors')
    function my_http_errors($errno, $errstr, $errfile, $errline)
    {
        status($errno);
        return html('<h1>'.http_response_status_code($errno).'</h1>');
    }

E_LIM_PHP表示所有PHP错误(由PHP发送或由用户通过trigger_error函数引发)。

其他有用函数

Limonade还提供了一套有用的函数,可以帮助您管理文件、HTTP等。有关这些实用工具的更多信息,请参阅源代码中的第7. 工具部分。

测试

[待办事项]

更多