Phalcon 的那些坑

2017-07-09

Phalcon route路由只能访问根路径的问题

问题现象

我用Phalcon-dev-tools创建了一个空的项目,然后比着文档一步一步往下做。刚开始还行,修改IndexController中的indexAction方法啥的都没有问题,可是,一旦自己写一个Controller或者在原有的Controller上添加其他Action方法,还是返回根路径/下的内容。
这tm是咋回事儿?
反复寻找答案,看文档的每一个细节,是否漏了某些代码?
环境没问题吧?这都跑起来了,肯定是代码问题吧?

问题原因以及解决

还真就是环境问题,不是php版本、Phalcon版本bug,是nginx配置问题!
坑!
Phalcon默认的URI信息是从$_GET['_url']获得,也可以设置为$_SERER['REQUEST_URI']获取。
使用这两种不同方法获取,还得要不同的nginx配置!!(详情请看Phalcon文档 Phalcon nginx配置
这特么也得配置!
使用$_GET['_url'](默认):

location / {
        try_files $uri $uri/ /index.php?_url=$uri&$args;
}

使用$_SERVER['REQUEST_URI'],nginx配置:

location / {
        try_files $uri $uri/ /index.php;
}

想要正常使用$_SERVER['REQUEST_URI']的方式,nginx配置完了还不要紧,还得在php代码里修改:

use Phalcon\Mvc\Router;
$router->setUriSource(Router::URI_SOURCE_SERVER_REQUEST_URI);

Phalcon3.1版本无法结合mongodb的问题

问题现象

我在php5.6、Phalcon2.0的环境下按照如下方式把mongodb服务注入到Phalcon中是没问题的:

1
2
3
4
5
6
7
8
9
$di->set(
"mongo",
function () {
$mongo = new MongoClient();

return $mongo->selectDB("store");
},
true
);

但是在php7.1、Phalcon3.1的环境下,却报错了。说找不到mongo类…

问题原因以及解决

左思右想,谷歌百度,刷新调试。原来,MongoClient这个类用到了php5.6的mongo这个扩展,PHP官方文档提示mongo扩展将由mongodb所替代,而mongo这个扩展在php7中已经不支持。php7中只支持mongodb这个扩展。
但是Phalcon3.1封装的类库MongoClient却引用了mongo这个库,所以报了错…

Phalcon研发组发现了这个问题,所以提供了一个php类库:"phalcon/incubator"
只需要在composer包中加入此类库,用类库的类替代原来的类就可以了:

  • 在composer中加入(Phalcon2.x版本对应2.x版本,Phalcon3.x版本对应3.x版本)

    1
    "phalcon/incubator": "^3.1"
  • 代码中引入use Phalcon\Db\Adapter\MongoDB\Client,然后代码中这样注入:

    1
    2
    3
    4
    5
    $di->setShared("mongo", function(){
    $mongoConfig = $this->getConfig()->mongo;
    return (new Client("mongodb://{$mongoConfig->host}:{$mongoConfig->port}/?replicaSet={$mongoConfig->replicaSet}"))
    ->selectDatabase($mongoConfig->database);
    });
  • 如果想使用Phalcon中的ODM,原来是引用use Phalcon\Mvc\Collection;并继承。现在是引用以下:

    1
    use Phalcon\Mvc\MongoCollection;

并继承它。

这是由于加的包"phalcon/incubator": "^3.1"对于PHP 7.1.0版本以上支持的不好,每次对实体保存的时候都会在mongo中保存一下一些脏数据

1
2
3
4
5
6
7
8
"_dependencyInjector" : {},
"_modelsManager" : {},
"_source" : null,
"_operationMade" : 1,
"_dirtyState" : 1,
"_connection" : {},
"_errorMessages" : [],
"_skipped" : false,

然后再次存数据的时候,就报错了。
解决办法就是重写save()方法:

1
2
3
4
5
6
7
8
9
public function save()
{
if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
$this->_dependencyInjector = \Phalcon\Di::getDefault();
$this->_modelsManager = $this->_dependencyInjector->getShared("collectionManager");
$this->_modelsManager->initialize($this);
}
return parent::save();
}

Phalcon无法使用redis或者memcached作为Phalcon的缓存服务

问题现象

使用Phalcon封装好的缓存类,只需要如下操作就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Phalcon\Cache\Frontend\Data as FrontData;
use Phalcon\Cache\Backend\Redis as BackendData;

$di->setShared('redis', function(){
$config = $this->getConfig();
$redisConfig = $config->sess_redis;

$frontend = new FrontData(['lifetime' => $config->cache->lifetime]);
$cache = new BackendRedis($frontend,
[
"host" => $redisConfig->host,
"port" => $redisConfig->port,
"persistent" => $redisConfig->pconnect
]
);
return $cache;
});

但是有时候报错,找不到redis类…

问题原因以及解决

这是因为Phalcon扩展在封装缓存类的时候,引用了PHP的redis扩展,而redis扩展并不是php的标准扩展包,so…
只需要安装好redis扩展就好。
同理,假如报memcache找不到,只需要安装相应扩展就好。

Phalcon ORM使用Join语句出现The column 'XXX' is ambiguous或者 Model 'XXX' could not be loaded的问题

问题现象以及解决

在使用Phalcon的ORM进行Join语句的时候,需要如下操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$infos = User::query()->inWhere("tkey", $uids)
->leftJoin("RealData", "User.tkey=RealData.tkey")
->leftJoin("UserFace", "User.tkey=UserFace.tkey")
->columns([
"RealData.company",
"RealData.status",
"RealData.post",
"RealData.area",
"User.name",
"User.flag",
"User.sex",
"User.tkey",
"User.sequence",
"User.mobile",
"User.othermobile",
"User.integrity",
"UserFace.filename"
])
->execute();

Model 'XXX' could not be loaded的错误,看看leftJoin方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

/**
* Adds a LEFT join to the query
*
* <code>
* $criteria->leftJoin("Robots", "r.id = RobotsParts.robots_id", "r");
* </code>
*
* @param string $model
* @param mixed $conditions
* @param mixed $alias
* @return Criteria
*/
public function leftJoin($model, $conditions = null, $alias = null) {}

感觉没毛病,细细想来,发现Model参数应该是该写的是namespace吧,于是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$user = User::class;
$realdata = RealData::class;
$userface = UserFace::class;

$infos = User::query()->inWhere("tkey", $uids)
->leftJoin($realdata, "$user.tkey=$realdata.tkey")
->leftJoin($userface, "$user.tkey=$userface.tkey")
->columns([
"$realdata.company",
"$realdata.status",
"$realdata.post",
"$realdata.area",
"$user.name",
"$user.flag",
"$user.sex",
"$user.tkey",
"$user.sequence",
"$user.mobile",
"$user.othermobile",
"$user.integrity",
"$userface.filename"
])
->execute();

但是又出现了新问题:The column 'tkey' is ambiguous,,将SQL解析后发现:SQL语句中是不允许先”where 然后 join”的,但是Phalcon帮我们封装的时候,帮我们避开了这个问题:where 语句可以在Join之前写,但是最终解析的sql还是在join之后,既然在join之后,那么tkey当然就不明确咯,改成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$user = User::class;
$realdata = RealData::class;
$userface = UserFace::class;

$infos = User::query()->inWhere("$user.tkey", $uids)
->leftJoin($realdata, "$user.tkey=$realdata.tkey")
->leftJoin($userface, "$user.tkey=$userface.tkey")
->columns([
"$realdata.company",
"$realdata.status",
"$realdata.post",
"$realdata.area",
"$user.name",
"$user.flag",
"$user.sex",
"$user.tkey",
"$user.sequence",
"$user.mobile",
"$user.othermobile",
"$user.integrity",
"$userface.filename"
])
->execute();

问题就解决了

Phalcon 设置model之间的Realaction 通过一个model的实体找不到另一实体

model之间的对应关系,通常是在model的initialize方法中实现,但是在实现的时候,要特别注意:Phalcon中参数都为定义的Model类,而不是MySQL中的table名字,这就带来一个问题:类名必须加namespace,生成的model的property名字奇怪或者引用不了。解决方法就是加上一个别名alias,代码如下:

1
2
$this->hasMany("order_no", OrderDetail::class, "order_no", 
["alias" => 'orderDetail']);

Phalcon 请求处理中getPost(“”)找不到数据和处理json数据的问题

在Phalcon中,发送post请求的时候,如果post请求中的Content-Type是以application/json 的json形式发送请求数据,那么,使用$this->request->getPost("xxx")是拿不到任何数据的,必须使用$this->request->getJsonRawBody()这个方法。

  • $this->request->getJsonRawBody(true) 拿到的是一个请求数组
  • $this->request->getJsonRawBody() 拿到的是一个object

Phalcon 中使用where查询

连续几个where条件,不能和laravel等其他框架一样,好几个where并排,只能有一个 where,剩下的都是andWhere

可能习惯了其他框架(例如:laravel 的查询,喜欢连续的where),如下:

1
2
3
4
5
6
7
8
$members = Member::query()
->where("id = :uid:")
->where("member_id = :memberId:")
->bind([
"uid" => 1,
"memberId" => 1
])
->execute();

感觉没毛病,但是一运行就报错:SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens,通过打印出来(语句为$this->di->get('profiler')->getLastProfile()->getSQLStatement())的SQL可以看出来,上条语句转义的SQL语句中连续写了两个where,并不是 where ... AND ...这就和预期不太一致。改为如下代码,就正常了:

1
2
3
4
5
6
7
8
$members = Member::query()
->where("id = :uid:")
->andWhere("member_id = :memberId:")
->bind([
"uid" => 1,
"memberId" => 1
])
->execute();

inWhere的情况下,bind要写在where或者andWhere语句之后,不能和inWhere掺和在一起

如下语句:

1
2
3
4
5
6
7
8
9
$members = Member::query()
->where("id = :uid:")
->andWhere("member_id = :memberId:")
->inWhere("country", [86])
->bind([
"uid" => 1,
"memberId" => 1
])
->execute();

运行后报错:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
原因是:bind没有和连续的where语句“绑”在一起。

应该写成:

1
2
3
4
5
6
7
8
9
$members = Member::query()
->where("id = :uid:")
->andWhere("member_id = :memberId:")
->bind([
"uid" => 1,
"memberId" => 1
])
->inWhere("country", [86])
->execute();

where语句中有时间的比较的时候,时间单位若为Y-m-d H:i:s格式,一定要把在时间上加上引号

1
2
3
4
5
$orderPayment = OrderPayment::query()
->where("order_no = :orderNo:")
->andWhere("add_time >= 2017-07-08 15:51:53")
->bind(["orderNo" => 1707080031])
->execute();

以上代码报错:Syntax error, unexpected token INTEGER(15), near to ':51:53)'

因为Y-m-d H:i:s格式的时间,中间有个空格,导致Phalcon无法正确的解析加上引号就好了。

1
2
3
4
5
$orderPayment = OrderPayment::query()
->where("order_no = :orderNo:")
->andWhere("add_time >= '2017-07-08 15:51:53'")
->bind(["orderNo" => 1707080031])
->execute();

Phalcon update更新语句问题

Phalcon一般的update方式为:先查询,拿到实体model;然后实体model的字段赋值$model->xxxx = xxxx,最后运行$model->update(),并判断update方法的返回值是true还是false,来判断更新是否成功。
但是,在高并发条件下,最好是update table set columnA = xxx where colum = AND ...这种形式。
代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//项目启动的时候先注入
$di->set('modelsManager', function() {
return new ModelsManager();
});

...
//业务代码中
$sql = "update $model set wallet_money = $remainMoney
where id = {$requestList['uid']}
AND wallet_money = {$user->wallet_money}";

$manager = $this->modelsManager->executeQuery($sql);

if($manager->success() == false)//插入失败
{
//todo
}

可是这却存在一个问题:
假如说where语句得到的是一个空行,不存在的数据,那么以上语句执行之后,还是会返回true。
也就是说其实Phalcon仅仅是判断的是否SQL返回正确,并没有判断改变了多少行,所以在用的时候要特别小心。尽量加上redis制作一个分布式锁。

Phalcon model 查询方法find()findFirst()的使用

find的使用

1
2
3
4
5
6
$member = Member::find(
[
"conditions" => "mobile = 15166266859 AND country = 86 AND role_id = 2",

]
);

或者:

1
2
3
4
5
6
7
8
9
10
11
$member = Member::find(
[
"conditions" => "mobile = ?1 AND country = ?2 AND role_id = ?3",
"bind" => [
1 => "15166266859",
2 => 86,
3 => 2
]

]
);

或者

1
2
3
4
5
6
7
8
9
10
11
$member = Member::find(
[
"conditions" => "mobile = :mobile: AND country = :country: AND role_id = :role:",
"bind" => [
"mobile" => "15166266859",
"country" => 86,
"role" => 2
]

]
);

findFirst()的使用

find()能使用的,findFirst()都可以使用,而且,findFirst()还多了一种:

1
$member = Member::findFirst(4)

特别注意:Phalcon中ODMORMfindfindFirst的操作是不相同的

ODM中可以这么用:

1
2
3
4
5
6
7
8
$action = UserAction::findFirst(
[
"conditions" => [
"share_info.promo_code" => "promo_code_1"
]
]
);
// conditions 是一个数组,包含查询条件,但是,ORM不可以这么用!

conditions 是一个数组,包含查询条件,但是,ORM不可以这么用!我想,可能是因为SQL语句中不仅包含AND还包含OR,直接用数组的话… (^__^)

ODM中还可以直接这么用:

1
2
3
4
5
6
7
$action = UserAction::findFirst(
[
[
"share_info.promo_code" => "promo_code_1"
]
]
);

Mac本地环境下不缺少类,但是一发布到测试环境,就报错xx class cannot be loaded

可能存在这样的原因:
你的包名为小写,而你的包下的namespace为大写,而你在引用了包的时候,仅仅引入了某个父包的namespace,而没有引入父包下每个子包的包名,在Mac下,不区分大小写,可以正常跑,但是到了Linux环境中,区分大小写,系统只会通过namespace的大写来找目录,而包名又都是小写,因而会找不到~
所以,应该避免如下简写:

1
2
3
4
5
6
$loader = new \Phalcon\Loader();
$loader->registerNamespaces([
'API' => BASE_PATH . '/apps/api/',
'Common' => BASE_PATH . '/apps/common/',
'JiYu' => BASE_PATH . '/apps/jiyu/',
])->register();

而是将各个父目录下的子目录都给它引上

1
2
3
4
5
6
7
8
9
10
11
12
$loader = new \Phalcon\Loader();
$loader->registerNamespaces([
'API' => BASE_PATH . '/apps/api/',
'Common' => BASE_PATH . '/apps/common/',
'JiYu' => BASE_PATH . '/apps/jiyu/',
'JiYu\Model' => BASE_PATH . '/apps/jiyu/model/',
'API\Controller' => BASE_PATH . '/apps/api/controller/',
'Common\Model' => BASE_PATH . '/apps/common/model/',
'Common\Validator' => BASE_PATH . '/apps/common/validator/',
'Common\Observer' => BASE_PATH . '/apps/common/observer/',

])->register();

cli程序必须跑到指定的目录下

假如你想使用phalcon的 task功能,然后使用crontab来作定时脚本任务,会经常出现“路径找不到的问题”…

细究其原因,是因为 run文件中的诸多文件都是以 run文件所在的位置作为“中心”来查找其他文件的,假如运行的当前目录中没有 run文件,那么就会报找不到某某某文件的错误。解决这个问题的途径也很简单,只需要让当前目录移动到run文件所在的目录就好了。

哪怕使用PHQL ,重写 model的update方法也会有问题

假如你重写了modelupdate方法,而你又想用偏原生SQL的方式去避开modelupdate方法带来的修改,例如你选择Phalcon自带的PHQL来躲避update方法的修改。那么,你就大错特错了,使用PHQL最终会调用excute方法,实际上就是拼出了model name,然后find相关的model;之后的操作,都是针对model的,也就是说,最终还会调用你重写的model的update方法…