帮助你开始使用 phalcon 的简易指南。
简介
Phalcon 2将于2015年4月17日发布,这个版本大约85%的代码是基于 Zephir 语言重写的。Zephir是开源的,使用类似PHP语法的语言,生成C语言代码,并编译成PHP扩展。这提高了PHP扩展的开发效率,并降低了框架的后期维护成本。
phalcon-devtools
安装 https://github.com/phalcon/phalcon-devtools 之后可以帮助自动生成目录结构和代码。同时把ide目录下的相应版本加入IDE的External libraries之后,可以帮助IDE自动完成代码。
nginx配置
配置nginx的时候,建议用$_SERVER[‘REQUEST_URI’]方式,这样可以防止自动加入$_GET[‘_url’]的隐规则,在参数签名时,如果你忘记这个隐规则会导至签名验证失败。
参考配置:
server {
listen 80;
server_name www.example.com;
index index.html index.htm index.php;
root $root_path/example/public;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
这样设置后,代码也要参考URI Sources调整
$router->setUriSource(Router::URI_SOURCE_SERVER_REQUEST_URI);
代码结构
phalcon框架对代码结构并没有约定,你可以按自己的实际需要自行组织代码结构,这里有一个供参考的例子:https://github.com/phalcon/mvc。
实际项目中,代码分层至少要分三层,controller/view -> services -> models
services和models为了重用,可以参考composer的组织方式,放到vendor。
使用PSR-4规范的namespace自动加载。
处理404
404要处理3个场景
-
没有匹配route
-
dispatch时没找到controller文件
-
dispatch时没找到action方法
当没有匹配route时,会使用用默认的namespace,默认的module,默认的controller,默认的action,默认的params。此时你或许会困惑为何一个请求会显示默认首页。
参考not found paths,配置route
// Not Found Paths
$router->notFound([
'controller' => 'errors',
'action' => 'route404'
]);
参考handling not found exceptions在注入dispatcher服务时初始化代码如下
$di->setShared('dispatcher', function () {
$eventsManager = new EventsManager();
$eventsManager->attach("dispatch:beforeException", function ($event, $dispatcher, $exception) {
if ($exception instanceof DispatcherException) {
switch ($exception->getCode()) {
case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
$dispatcher->forward([
'controller' => 'errors',
'action' => 'show404',
'params' => array('message' => $exception->getMessage())
]);
return false;
}
}
$dispatcher->forward([
'controller' => 'errors',
'action' => 'show500'
]);
return false;
});
$dispatcher = new MvcDispatcher();
$dispatcher->setDefaultNamespace('\namespace\Controllers');
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
以代码由于还拦截了其它异常,且没有throw $exception,会中断异常调用链,可以考虑把异常$exception->getMessage(), $exception->getFile(),$exception->getLine(),$exception->getCode()信息传给view,判断是开发模式就显示,生产模式就不显示。
使用原生 SQL
原则上是不允许使用原生的SQL的,某些场景一定要用的情况下,建议把所有的sql放到config内,在service层读取使用。
第一种方式
$sql = "SELECT id, name FROM robots ORDER BY name";
$connection = \Phalcon\DI::getDefault()->get('db');
$result = $connection->query($sql);
$result->setFetchMode(Db::FETCH_ASSOC);
echo $connection->getSQLStatement(); // sql
$data = $result->fetchAll();
print_r($data);
第二种方式
// A raw SQL statement
$sql = "SELECT * FROM robots WHERE id > 0";
$robot = new Robots();
$result = $robot->getReadConnection()->query($sql);
$result->setFetchMode(Db::FETCH_ASSOC);
$data = $result->fetchAll();
print_r($data);
echo $robot->getReadConnection()->getSQLStatement();
使用PHQL
参考Phalcon Query Language (PHQL)
简单粗暴的理解是把models下的namespaceclass对应成数据库的表,类属性对应表字段。
理解了PHQL之后就可以读下Working with Models
理解事件驱动
model类的事件调用顺序如下:参考官方文档http://docs.phalconphp.com/en/latest/reference/models.html#events-and-events-manager
数据库分库,读写分离,负载均衡
用命名空间区分不同的数据库实例,对应代码结构上是不同的目录区分,在同一目录下基类负责初始化连接。连接来自初始化时注入的多个db服务
隐规则:
-
initialize()在每个请求期间只会调用一次
-
为每个 new 创建的实例执行初始化任务使用onConstruct()
namespace Company\Models\Notification;
/**
* Class BaseModel
*
* beforeSave()和afterFetch()成对使用,用于读写数据时自动转化数据。
* 例如自动执行serialize unSerialize
*
* save()发生时事件调用顺序是
* initialize,
* onConstruct,
* beforeValidation,
* beforeValidationOnCreate,
* afterValidationOnCreate,
* afterValidation,
* beforeSave,
* beforeCreate,
* afterCreate,
* afterSave,
*
* @package Company\Models\Notification
*/
class BaseModel extends \Phalcon\Mvc\Model
{
/**
* - initialize()在每个请求期间只会调用一次
* - 子类必需调用父类方法
* - 为每个 new 创建的实例执行初始化任务使用onConstruct()
*/
public function initialize()
{
$this->setConnectionService('db_notification');
//$this->setConnectionService('node1');
//$this->setConnectionService('node2');
//
//真实场景可能使用mysqlnd_ms扩展或者haproxy
//仅演示读负载均衡一种思路
//$dbSlave = ['node1', 'node2', 'node3'];
//$key = array_rand($dbSlave);
//$db = $dbSlave[$key];
//$this->setReadConnectionService($db);
//
//
//$this->setReadConnectionService('dbRead');
//$this->setWriteConnectionService('dbWrite');
}
}
表前缀与分表
model中提供的getSource()方法,合理运用即可。
class BaseModel extends \Phalcon\Mvc\Model {
public function getSource()
{
return 'v1'.str_tolower(get_class($this));
}
}
class User extends BaseModel {
...
}
class Robots extends Phalcon\Mvc\Model
{
public function getSource()
{
return "robots_" . date("Ym");
}
}
cookie
2.0.9之前的版本先set('key'),然后再正常的set('key','value')。也就是set两次绕过bug。
2.0.x分支已经修复这个bug,查看github源码
下面的单元测试可以重现这个bug。如果使用2.0.9之前的版本,建议采用两次set
public function testCookies()
{
// di factory
$di = new \Phalcon\Di\FactoryDefault();
$di->setShared('crypt', function () {
$crypt = new \Phalcon\Crypt();
// don't use PADDING_DEFAULT, Affect the cookie result
$crypt->setPadding(\Phalcon\Crypt::PADDING_ZERO);
$crypt->setKey('secret_key@123456789'); // Use your own key!
return $crypt;
});
// http cookies
$di->setShared('cookies', function () {
$cookies = new \Phalcon\Http\Response\Cookies();
$cookies->useEncryption(false);
return $cookies;
});
/** @var \Phalcon\Http\Response\CookiesInterface $cookies */
$cookies = \Phalcon\DI::getDefault()->get('cookies');
$_COOKIE['key'] = '1234567890';
$_COOKIE['key2'] = '0987654321';
//$cookies->set('key'); // ver <= 2.0.9 uncomment will test passed
//$cookies->set('key2'); // ver <= 2.0.9 uncomment will test passed
$this->assertEquals('1234567890', $cookies->get('key')->getValue());
$cookies->set('key', 'value', time() + 3600, '/', false, 'kinhom.com', true);
$cookies->set('key2', 'value2', time() + 3600, '/', false, 'kinhom.com', true);
$this->assertInstanceOf('\Phalcon\Http\Cookie', $cookies->get('key'));
/*
* phalcon ver <= 2.0.9
* Failed asserting that two strings are equal.
* Expected :'value'
* Actual :'1234567890'
*/
$this->assertEquals('value', $cookies->get('key')->getValue());
$this->assertEquals('value2', $cookies->get('key2')->getValue());
}
view 渲染顺序是
volt模板文件内 {{ content() }} 是联接各个模板的桥梁,完整的顺序是setMainView() -> setTemplateAfter() -> setLayout() -> setTemplateBefore -> pick()
即Main Layout -> Layout -> Action View
controller中若echo字符串,Action view内要写上{{ content() }}才会输出。
参考范例
背景知识
无论你是否使用phalcon框架,做为PHP开发者,有些背景知识是必需要了解的。