pfinal/leaf


README

安装

composer create-project pfinal/leafphp demo

交流QQ群

17778706

编码规范

  • 遵循PSR-1、PSR-2、PSR3、PSR-4、PSR-16 等标准
  • PHP代码文件必须以 <?php 标签开始,不写PHP结束标记: ?>
  • PHP代码文件必须使用 不带BOM的 UTF-8 编码
  • 命名空间与目录对应,文件名采用"类名.php"格式,目录大小写与命令空间相同,类名大小写与文件名相同(PSR-4规范)
  • 类的命名必须遵循大写开头的驼峰命名规范(例如UserController)
  • 方法名称如果是多个单词,必须遵循小写开头的驼峰命名规范(例如createOrder)
  • 类中的常量所有字母都必须大写,多个单词间用下划线分隔
  • 普通目录全小写格式,多个单词之间用中杠分隔(例如 views/member-profile)

基本概念约定

  • 项目 project
  • 应用 application
  • 模块 bundle
  • 控制器 controller
  • 动用 action
  • 路由 route

目录结构

  • config 配置文件目录
    • app.php 应用配置文件
    • routes.php 路由
  • runtime 运行时目录,存放缓存、日志、调式信息等,需要写权限
    • .gitignore 版本管理工具git忽略清单,请勿删除此文件
  • views 视图文件目录
  • document 文档
  • src PHP源代码
  • tests 测试
  • vendor 第三方代码 composer管理,请勿手动修改该目录内容
  • web 项目发布的根目录
    • static 第三方静态资源目录
    • themes 主题目录,项目的css、js、img等存放在此目录
    • index.php 前端控制器(MVC模式的入口文件)
    • assets 框架自动管理的资源文件,请勿手动修改该目录内容
    • temp 临时文件目录,需要写权限
    • uploads 文件上传目录,需要写权限
  • .gitignore 版本管理工具git忽略清单
  • console 命令行入口文件
  • phpunit 单元测试入口文件

开启调式模式

nginx配置

# ... 

server{
	
	# ... 

	root /wwwroot/project/web/;

	location / {
	     index index.html index.php;
	     try_files $uri @rewrite;
	}
	
	location @rewrite {
	     rewrite ^/(.*)$ /index.php/$1 last;
	}
	
	location ~ \.php(/|$) {
	    fastcgi_pass   127.0.0.1:9000;
	    fastcgi_split_path_info ^(.+\.php)(.*)$;
	    fastcgi_param   PATH_INFO $fastcgi_path_info;
	    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
	    include        fastcgi_params;
	}

}

路由

下面以demo项目为例

config/routes.php 中定义路由

基本 GET 路由,浏览器访问 http://localhost/demo/web

use Leaf\Route;
Route::get('/', function(){
    return 'Hello Leafphp!';
});

只允许POST请求的路由,以POST方法访问 http://localhost/demo/web/foo

Route::post('foo', function(){
    return 'post only';
});

注册路由响应任意方式的HTTP请求,不对请求方法进行限制,post或get均可,浏览器访问 http://localhost/demo/web/bar

Route::any('bar', function(){
    return 'any';
});

基础路由参数 浏览器访问 http://localhost/demo/web/user/1

Route::get('user/:id', function($id){
    return 'user id: ' . $id;
});

方法注入,框架将自动注入Leaf\Request实例,浏览器访问 http://localhost/demo/web/foo?name=leaf

use Leaf\Request;
Route::any('foo', function(Request $request){
    $name = $request->get('name');
    return $name; // leaf
});

支持注入的对象

Leaf\Request
Leaf\Application

生成URL

use Leaf\Url;
$url = Url::to('foo');                 /demo/web/foo
$url = Url::to('foo', ['id' => 1]);    /demo/web/foo?id=1
$url = Url::to('foo', true);           http://localhost/demo/web/foo

如果访问时url中没有隐藏入口文件index.php,则生成的url也会自动包含index.php

请求与响应

Request

use Leaf\Request;
Route::any('test', function (Request $request) {

    //GET POST PUT DELETE HEAD ...
    $method =  $request->getMethod();
    
	$bool = $request->isMethod('post')    // post request
	$bool = $request->isXmlHttpRequest()  // ajax request

	$id = $request->get('id');   // $_POST['id'] or  $_GET['id']
	$arr = $request->all();      // $_POST + $_GET

});

Response

use Leaf\View;
use Leaf\Response;
use Leaf\Json;
Route::any('/', function(){

    //string
    return 'Hello Leaf!';
    return new Response('Hello Leaf!');
    
    //view
    return View::render('home.twig'); // 输出views/home.twig模板中内容
    
    //json
    return Json::render(['status' => true, 'data' => 'SUCCESS']);
    return Json::renderWithTrue('SUCCESS');
    return Json::renderWithTrue('ERROR');
});

重定向

use Leaf\Redirect;
return Redirect::to('foo');                    // 跳转到 Url::to('foo') 
return Redirect::to('/');                      // 项目的根目录
return Redirect::to('http://www.example.com'); // 外部链接

中间件

编写中间件类 src/Middleware/TestMiddleware.php

<?php
namespace Middleware;
use Leaf\Request;
class TestMiddleware
{
    public function handle(Request $request, \Closure $next)
    {
        $id = $request->get('age');
        if ($id < 18) {
            return 'age error';
        }
        return $next($request);
    }
}

注册一个名为test的中间件

$app['test'] = 'Middleware\TestMiddleware';

为路由绑定这个test中间件

use Leaf\Request;
Route::any('info', function (Request $request) {
    return $request->get('age');
}, 'test');

浏览器访问

`http://localhost/demo/web/info?age=17`   被中间件拦截
`http://localhost/demo/web/info?age=18`   请求将被通过

为一组路由绑定中间件 (绑定多个中间件,传入数组即可)

use Leaf\Request;
Route::group(['middleware' => 'test'], function () {

    Route::any('info', function (Request $request) {
        return $request->get('age');
    });

    //other route
});

如果希望中间件,在执行之后生效,可以使用下面的方式, $next()返回值可能是Request\Response对象或string

use Leaf\Request;
public function handle(Request $request, \Closure $next)
{
    $response = $next($request);
    // your code ...
    return $response;
}

控制器

控制器类通常以Controller作为后缀,例如 src/Controller/SiteController.php

任何PHP类均可作为控制器

<?php
namespace Controller;
class SiteController
{
    public function home()
    {
        return 'Hello Leafphp!';
    }
}

路由指向控制器中的方法

Route::get('home','Controller\SiteController@home');

浏览器访问 http://localhost/demo/web/home

如果需要包含多个字词,建议在 URI 中使用中杠来分隔,例如user-profile

使用注解语法注册路由,自动注册路由。class上的Middleware,将对整个控制器生效

<?php
// src/Controller/UserController.php
/**
 * @Middleware auth
 */
class UserController
{
    /**
     * @Route user/info
     * @Method get
     */
    public function info()
    {
        // your code ...
    }
}

添加注解路由 Route::annotation('Controller\UserController');

视图

支持twig和blade模板引擎,跟据后缀自动识别。

twig http://twig.sensiolabs.org

blade http://laravel.com/docs/5.1/blade

use Leaf\View;

// views/index.twig
return View::render('home.twig', [
    'name' => 'Leaf',
]);

// views/index.blade.php
return View::render('home', [
    'name' => 'Leaf',
]);

将变量共享给所有模板

\Leaf\View::share('url', 'http://example.com');

扩展twig模板 增加一个count函数,用于在模板中统计数组成员数量(twig提供了length过滤器)

$app->extend('twig', function ($twig, $app) {
    /** @var $twig \Twig_Environment */
    $twig->addFunction(new \Twig_SimpleFunction('count', function ($arr) use ($app) {
        return count($arr);
    }));
    return $twig;
});

twig中使用自定义的count函数

{{ count([1, 3, 5]) }} 或  {{ count({"id":1, "name":"Ethan"}) }}

Bundle

Bundle可以更好的组织功能模块

目录结构

src/
    FooBundle/           Bundle总目录
        FooBundle.php    Bundle类文件
        Controller/      控制器
        resources/       资源目录
            routes.php   路由文件
            views/       视图目录

Bundle类,继承自Leaf\Bundle类即可,主要用于定位Bundle所在目录

// src/FooBundle/FooBundle.php
class FooBundle extends \Leaf\Bundle
{
}

注册Bundle

$app->registerBundle(new \FooBundle\FooBundle());

加载视图

Bundle的视图文件位于Bundle的resources/views目录中

return View::render('@FooBundle/home.twig');

顶级views目录中,与Bundle同名目录下的视图文件,将优先使用。 例如/views/FooBundle/home.twig,将替换src/FooBundle/resources/views/home.twig文件。

加载路由

Bundle注册后,Bundle的路由文件会自动加载,例如FooBundle/resources/routes.php

自动生成Bundle

php console make:bundle

数据库

基本用法

$conn = DB::getConnection();

$conn->execute()             // 执行SQL(INSERTUPDATEDELETE)
$conn->query()               // 执行SQL(SELECT)
$conn->queryScalar()         // 执行SQL,查询单一的值(SELECT COUNT)
$conn->getLastInsertId()     // 返回自增id
$conn->beginTransaction()    // 开启事务
$conn->commit()              // 提交事务
$conn->rollBack()            // 回滚事务
$conn->getLastSql()          // 最近执行的SQL

查询构造器

$query = DB::table()

// DBQuery返回数据的方法:

$query->findOne()
$query->findByPk()
$query->findOneBySql()
$query->findAll()
$query->findAllBySql()
$query->count()
$query->paginate($pageSize)

//DBQuery连惯操作方法,返回DBQuery对象:

$query->where()
$query->whereIn()
$query->limit()
$query->offset()
$query->orderBy()
$query->asEntity()
$query->lockForUpdate()
$query->lockInShareMode()

//分块操作
$query->chunk()
$query->chunkById()

手动分页

$query = DB::select('user')->where($condition, $params);

$page = new Pagination();
$queryCount = clone $query;
$page->itemCount = $queryCount->count();

$query->limit($page->limit)->findAll();

数据库操作详细文档

系统服务

会话,调用Session相关方法时,会自动开启Session

use Leaf\Session;

//设置Session
Session::set('username', 'Jack');

//获取Session
$username = Session::get('username');

//删除Session
Session::remove('username');

//获取Session,如果不存在,使用`guest`作为默认值
$username = Session::get('username', 'guest');

//设置闪存数据
Session::setFlash('message', 'success');

//是否有闪存数据
$bool = Session::hasFlash('message');

//获取闪存数据,闪存数据获取后将自动删除
$message = Session::getFlash('message');

表单验证

$data = [
    'username' => 'jack',
    'email' => 'jack@b.c',
    'age' => '18',
    'info' => 'abc',
];

$rules = [
    [['username', 'email'], 'required'],
    [['username'], 'match', 'pattern' => '/\w{3,15}/'],
    [['info'], 'string', 'length' => [3, 10]],
    [['email'], 'email'],
    [['age'], 'compare', 'type' => 'number', 'operator' => '>=', 'compareValue' => 0],
    [['age'], 'compare', 'type' => 'number', 'operator' => '<=', 'compareValue' => 150],
];

$labels = [
    'username' => '用户名',
];

if (!Validator::validate($data, $rules, $labels)) {
    var_dump(Validator::getFirstError());
    var_dump(Validator::getErrors());
}

验证器详细文档

错误

use Leaf\Exception\HttpException;

throw new HttpException(400, '您访问的页面不存在');
throw new HttpException(500, '服务器内部错误');

日志

use Leaf\Log;

Log::debug("日志内容");
Log::info("日志内容");
Log::warning("日志内容");
Log::error("日志内容");

验证码

//注册
$app->register(new \Leaf\Provider\CaptchaProvider());

//生成验证码图片
\Leaf\Route::get('captcha', function (\Leaf\Application $app) {
    return $app['captcha']->create();
});

//表单中使用验证码
\Leaf\Route::any('show', function () {
    $html = <<<TAG
<form method="post" action="{{ url('validate') }}">
    <img src="{{ url('captcha') }}" onclick="this.src='{{ url('captcha') }}?refresh='+Math.random()" style="cursor:pointer" alt="captcha">
    <input name="code">
    <button type="submit">提交</button>
</form>
TAG;
    return View::renderText($html);
});

//提交表单时验证输入是否正确
\Leaf\Route::post('validate', function (\Leaf\Application $app, \Leaf\Request $request) {
    if ($app['captcha']->validate($request->get('code'))) {
        // 'success';
    } else {
        // 'Verification code is invalid.';
    }
});


// 显示指定内容验证码图片
$obj = new \Leaf\Provider\CaptchaProvider();

return $obj->create('1234');
        

文件上传

use Leaf\UploadedFile;
$up = new UploadedFile($config);
if ($up->doUpload($name)) {
    //上传后的文件信息
    $file = $up->getFile();
}

认证

use Service\Auth
Auth::onceUsingId($userId);
Auth::loginUsingId($userId);

Auth::getUser()
Auth::getId()

权限

$app->register(new \Leaf\Auth\GateProvider(), ['gate.config' => ['authClass' => 'Service\Auth']]);

$app['gate'] = $app->extend('gate', function ($gate, $app) {

    /** @var $app \Leaf\Application */
    /** @var $gate \Leaf\Auth\Gate */

    //用户是否有执行某个task的权限
    $gate->define('task', function (\Entity\User $user, $taskCode) {
        // 判断权限
        if (xxx){
            return true;
        }else{
            return false;
        }
    });

    return $gate;
});

// 判断是否有权限
$bool = Auth::user()->can('task', $taskCode);

自定义分页样式

$app['Leaf\Pagination'] = function () {
    $page = new \Leaf\Pagination();
    $page->pageSize = 15;
    $page->prevPageLabel = '<span class="glyphicon glyphicon-chevron-left"></span>';
    $page->nextPageLabel = '<span class="glyphicon glyphicon-chevron-right"></span>';
    return $page;
};

命令行

php console down              # 维护模式
php console up                # 退出维护模式
php console make:bundle       # 创建bundle
php console make:entity       # 创建实体类
php console make:curd         # 创建增删改查控制器和视图
php console make:api          # 创建Api控制器
php console migrate           # 执行数据迁移
php console migrate:create    # 创建数据迁移文件
php console migrate:rollback  # 回滚数据迁移

更多扩展