本文将记录我在Phalcon开发过程中遇到的问题,以及如何如何解决。

1. 正确地在控制器中获取参数

一般情况下,GET/POST请求获取参数:

$this->request->get(参数名);
$this->request->getPost("参数名")

路由模式下route获取参数要用dispatcher->getParam();
route下定义好了参数名称可以直接通过参数名称来获取:

this->dispatcher->getParam("参数名");

route没有定义好名称,只是规则中写了:params做匹配,可以在控制器中按顺序来获取:

class NewsController extends Controller {
    public function showAction($id, $testParam)
    {
        echo $id, '|' , $testParam;
    }
}

2. 为 url 定制路由

默认自动解析/:controller/:action/:params模式
在实例化时,不加false参数:

$router = new Router();

url将会自动进行/:controller/:action/:params参数的解析, 比如https://www.goozp.com/login将会解析成Login controller下的默认action。

当使用路由时,保留默认解析模式有时候会导致解析混乱,比较建议采用完全自定义路由模式
完全自定义路由,在new时加上false:

$router = new Router(false);

不自动解析/:controller/:action/:params这些规则, 具体的路由匹配规则自己来编写,例如:

$router->add('/login',
    [
        'module'     => 'admin',
        'controller' => 'login',
        'action'     => 'index',
    ]
)->setName('login');

这样不会因为自动解析而导致url混乱,但是所有url都要自己来定制路由规则。

3. flash提示重写后输出不正确 (未解决)

重写后输出的html标签是字符串,外面带””

4. Config 中 baseURI 的正确设置

因为有Apache+.htaccess文件重写规则 或者 nginx配置到public/index.php的重写规则,我们不需要项目中的url带有/publc/index.php。
但是默认是指到了/public/index.php中(比如$_SERVER[“PHP_SELF”]获取从根目录到当前页面本身的路径); 所以,如果有Apache重写规则或者nginx配置到public/index.php的重写配置,我们需要把url设置为不带public/index.php的,于是就有了官方的这个设置:
使用 $_SERVER[“PHP_SELF”],并且正则去除/public/index.php

'baseUri'        => preg_replace('/public([\/\\\\])index.php$/', '', $_SERVER["PHP_SELF"]),

这是动态写法,这种写法的问题在于 $_SERVER[“PHP_SELF”] 的不确定性,返回的值将根据 Apache 或 nginx 配置的 root,是否配置host或者域名,$_SERVER[“PHP_SELF”]会有不同的返回值。这样的话上面写法前面的正则并不是全部兼容的,所以这样写调试起来就稍麻烦。

简单一点,用静态写法:
设置host或者配置域名

'baseUri'        => '/',

如果是想在localhost下直接打开,则需要加上项目外层目录名,例如:

'baseUri'        => '/zphal/',

这样的话,我们在定义url服务的时候只需要把这个定义的配置传进去:

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

    $url = new UrlResolver();
    $url->setBaseUri($config->application->baseUri); // baseUri

    return $url;
});

以上写法的WebServer配置:

  • Apache:
    .hatccess按照官方配置就可以;配置host时配置到public下或者public外面一层的项目根目录也可以:
    <VirtualHost *:80>
      DocumentRoot "D:\phpStudy\WWW\zPhal\public"
      ServerName goozp.com
      ServerAlias
    <Directory "D:\phpStudy\WWW\zPhal\public">
        Options FollowSymLinks ExecCGI
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    </Directory>
    </VirtualHost>
    
  • Nginx
    大概的配置如下,配置到public下,并定义rewrite规则:

    server {
      listen        80;
      server_name www.goozp.com goozp.com;
    
      root /data/www/zPhal/public;
      index index.php index.html index.htm;
    
      charset utf-8;
      client_max_body_size 100M;
      fastcgi_read_timeout 1800;
    
      location / {
          # Matches URLS `$_GET['_url']`
          try_files $uri $uri/ /index.php?_url=$uri&$args;
      }
    
      location ~ \.php$ {
          try_files $uri =404;
    
          #fastcgi_pass  unix:/var/run/php/php7.0-fpm.sock;
          fastcgi_pass  php-fpm:9000;
    
          fastcgi_index /index.php;
    
          include fastcgi_params;
          fastcgi_split_path_info       ^(.+\.php)(/.+)$;
          fastcgi_param PATH_INFO       $fastcgi_path_info;
          fastcgi_param PATH_TRANSLATED /data/www/zPhal/public/$fastcgi_path_info;
          fastcgi_param SCRIPT_FILENAME /data/www/zPhal/public/$fastcgi_script_name;
      }
    
      location ~ /\.ht {
          deny all;
      }
    
      location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
          expires       max;
          log_not_found off;
          access_log    off;
      }
    }
    

5. 事件管理器,fire写法不管用

被手册误导,理解错误了。

下面是 错误 的写法,在dispatcher中去定义了监听事件:

$di->set('dispatcher', function () {
    // 创建一个事件管理器
    $eventsManager = new EventsManager();

    $media = new Media();

    $media->setEventsManager($eventsManager);

    // 监听分发器中使用插件产生的事件
    $eventsManager->attach(
        "media",
        new AliYunOss()
    );

    $dispatcher = new Dispatcher();
    $dispatcher->setDefaultNamespace('ZPhal\Modules\Admin\Controllers\\');
    $dispatcher->setEventsManager($eventsManager); // 分配事件管理器到分发器

    return $dispatcher;
});

然而我想封装的是文件上传功能,跟 dispatcher分发器 没有任何关系,所以起不了作用还报错;应该注册一个返回DI容器的文件上传服务:

$di->set('mediaUpload',function (){
    // 创建一个事件管理器
    $eventsManager = new EventsManager();

    $media = new Media();

    $eventsManager->attach(
        "media",
        new AliYunOss()
    );

    $media->setEventsManager($eventsManager);

    return $media;
});

6.使用模型关联不起作用

扔进去的对象报错;需要给关联的对象定义alias,通过alias来获取。
如果是这样:

$terms = new Terms();
$terms->name = $name;
$terms->slug = $slug;

$termTaxonomy = new TermTaxonomy();
$termTaxonomy->Terms  = $terms; // 这里
$termTaxonomy->taxonomy = $type;

$termTaxonomy->save();

在$termTaxonomy->Terms = $terms;这里,Terms是TermTaxonomy Model中定义的关系的别名(alias);
定义方式如下,在model中:

$this->belongsTo(
    "term_id",
    "ZPhal\\Models\\Terms",
    "term_id",
    [
        "alias" => "Terms",
    ]
);

不起alias别名会报错。

7. 插入数据时返回主键id

通过$model -> getWriteConnection() -> lastInsertId();来获取:

$model = new model();

if($model -> create($data)) {
    $insertId = $model -> getWriteConnection() -> lastInsertId($model -> getSource());
}

或者直接在执行之后拿id属性:

<?php
$model = new model();

if($model -> create($data)) {
    $insertId = $model -> id;
}

8. model事件 beforeCreate 和字段检查

在 beforeCreate 事件中定义数据表字段的数据检查和数据赋值,不生效。

beforeCreate 在执行之前就会检查字段是否符合要求(validation),所以在beforecreate时再插入不行,会报错,需要在执行create前就传值,或者设置默认值。

可以在 beforeValidation 时进行赋值检查的操作。

9. 操作model保存时,save或update无效

表现为save或者update失败,且不报错的问题。
情况:主键设置为两个字段,更新时更新了其中一个字段。
解决:不应该修改主键。
参考:https://stackoverflow.com/questions/3838414/can-we-update-primary-key-values-of-a-table

10. find()与findFirst()

  • find()与findFirst()返回值数据格式是不同的。
  • 加了 column 参数时的返回值object里时不完整的,所以无法使用save等方法,无法使用model关系。
  • 没有数据时,find()返回空数组,findfrist()返回false

11. Phalcon\Cache\Backend\Redis 的 queryKeys()出现以下错误:

Cached keys need to be enabled to use this function (options['statsKey'] == '_PHCR')!

Redis的默认配置有一个参数为‘_PHCR’前缀,所以queryKeys()时需要带上查询前缀。

12 dispatcher->forward() 分发后原脚本仍然继续执行

可以加上return阻断:

$this->dispatcher->forward([
    "controller" => "error",
    "action"    => "route404"
]);
return;

在分发后后面的代码将不再执行。

13. 错误:Encryption key cannot be empty

使用cookie时,默认会使用Crypt加密,而使用Crypt加密需要定义一个全局加密key。
可以禁用cookie加密:

<?php
use Phalcon\Http\Response\Cookies;

$di->set(
    "cookies",
    function () {
        $cookies = new Cookies();

        $cookies->useEncryption(false);

        return $cookies;
    }
);

或者设置一个key:

<?php
use Phalcon\Crypt;

$di->set(
    "crypt",
    function () {
        $crypt = new Crypt();

        $crypt->setKey('#1dj8$=dp?.ak//j1V$'); // 使用你自己的key!

        return $crypt;
    }
);

14. cache删除失败:queryKeys()之后foreach遍历循环delete()删除失败

正常删除时:

$this->cache->delete($key)

如果设置了前缀,会在$key自动加上前缀。

queryKeys列出来的已经带上了前缀,所以这样删除:

$keys = $this->cache->queryKeys();
    foreach($keys as $key) {
        $this->cache->delete($key)
    }

传进去的key还会自动再加一遍前缀,就找不到缓存了,导致删除失败。

解决方法:

  1. $this->cache->flush()清除所有缓存,但是会清除所有缓存,所以如果是memcache或者redis缓存可以设置一下statsKey,避免清除了所有缓存。
  2. 或者不使用前缀,就可以正常使用queryKeys()和delete()这条流程。