CakePHP-2.x笔记

2016-05-06

因工作需要接触了cakephp,英文文档很全,代码维护也很积极,是一个不错的MVC框架。

cakephp2加载Composer的vendor

如果使用 Composer 安装其他任何类库,则需要设置自动加载(autoloader)并绕过 (work around)Composer 自动加载的一个问题。在 Config/bootstrap.php 文件中添 加如下代码:

// 加载 Composer 的自动加载。
require APP . '../vendor/autoload.php';

// 删除并重新优先添加 CakePHP 的自动加载,因为 Composer 认为这是最重要的。
// 参看:http://goo.gl/kKVJO7
spl_autoload_unregister(array('App', 'load'));
spl_autoload_register(array('App', 'load'), true, true);

目录结构

app:包含了应用程序的文件,所有的controllers、models、views、layouts,以及JavaScript、css、图片、flash。

lib:CakePHP的核心库文件,以及一些支持CakePHP的类文件和脚本。

plugins:存放插件目录。

vendors:用来存放第三方的文件。

配置文件

在开始编写程序前,我们还需要进行以下工作。

1:修改Security.salt的值,在app/config/core.php文件中。(用来产生hash码,增强安全性。)

2:输入MySQL数据库的连接配置。在app/config/database.php.default中,重命名为database.php,然后根据自己的数据库配置此文件。

数据库配置文件:

app/config/database.php

命名规则

1:controller

类名称:必须是复数,并且要附加上Controller。

PHP文件:必须是复数,且附加上_controller,PHP扩展名。

2:model

类名称:必须是单数

PHP文件:必须是单数,PHP扩展名,如果多个单词,用“_”连接。

3:view

view文件的名称是从controller中action名称而来。例如:ProductsController::add(),view的路径那么就是:app/view/products/add.ctp。

cakephp里怎么输出原生的mysql查询语句?

打开项目文件:\lib\Cake\Model\Datasource\DboSource.php

在_execute函数中输出$sql变量即可:

protected function _execute($sql, $params = array(), $prepareOptions = array()) {
	$sql = trim($sql);
	var_dump($sql);
	if (preg_match('/^(?:CREATE|ALTER|DROP)\s+(?:TABLE|INDEX)/i', $sql)) {
		$statements = array_filter(explode(';', $sql));
		if (count($statements) > 1) {
			$result = array_map(array($this, '_execute'), $statements);
			return array_search(false, $result) === false;
		}
	}

全局配置文件:

/app/config/core.php

例如:

Configure::write('debug', 2);

这里可以配置debug参数,

这个选项定义了应用程序的Debug级别。

设置为一个非零的数值Cake会输出所有的pr()和debug()函数调用,并且在Forward之前会有提示信息。设置为2或者是更高的数值,则会在页面底部输出sql语句。

在debug模式下(DEBUG被设为1或者更高),Cake会输出错误自定义错误页面,

例如 “Missing Controller”,”Missing Action”等等。在生产环境下(DEBUG被设为0),Cake输出 “Not Found”页面,你可以自行修改该页面(app/views/errors/error400.ctp)。

还有Session、异常、php错误之类的配置都在这里。

错误提示(关闭notice通知):

Configure::write('Error', array(
	'handler' => 'ErrorHandler::handleError',
	'level' => E_ALL & ~E_DEPRECATED ^E_NOTICE,
	'trace' => true
));

路由配置

app/config/routes.php

例如:

Router::connect('URL',array('controller'=>'controllername','action'=>'actionname', 'firstparam') );

1.URL是一个正则表达式,表示你希望采用的URL格式

2.controllername是希望调用的controller的名字

3.actionname是希望调用的controller的action名字

4.firstparam是action的第一个参数名

全局方法

自动加载类:

App::uses('AppModel', 'Model');

模型

不包含查询:

$this->User->find('all', array(
    'conditions' => array(
        'NOT' => array(
            'User.id' => array(1, 2, 3)
        )
    )
));

组件(component)

助件(Helper)

访问控制名词解释:

访问请求对象(Access Request Object,ARO)

访问控制对象(access-control object,ACO)

登陆后权限控制: \app\Controller\Component\AuthComponent.php startup() 在此函数调试

cakephp recursive作用

在cakephp中我们会定义一些model,然后定义模型与模型之间的关系。

在查询时,cakephp会自主的把所有有关系的模型记录也查询出来,如果需要哪些附属数据倒是很好,但是如果不需要的要,就显得累赘、臃肿了。

cakephp是一个成熟的框架,在查询中定义了一个属性:recursive。定义需要查询的关系级数。

值为int型 array(-1 ,0, 1, 2)

-1 代表model本身
0  代表model本身 + belongTo + hasOne
1  代表model本身 + belongTo + hasOne + hasMany
2  代表model本身 + belongTo + hasOne + hasMany + hasAndBelongsToMany

具体用法为在find 或者 pagenate查询时使用

查询 GROUP BY

比如需要查询的SQL如下:

SELECT COUNT(active) as count,active FROM `users` WHERE group_id=1 GROUP BY active;

对应的代码:

<?php
public function testgroup()
{
    $this->loadModel('user');
    $this->user->recursive = -1;
    $result = $this->user->find('all',
		array(
			'conditions' => array('group_id' => '1'),
			'fields' => array('active', 'COUNT(active) as count'),
			'group'=>'active'
		)
	);
    var_dump($result);
    die;
}

官方文档:http://book.cakephp.org/2.0/en/index.html

中文文档(英文文档更详细些):http://book.cakephp.org/2.0/zh/index.html

国内文档:http://www.phpfans.net/manu/CakePHP/

Cakephp自动生成test代码bat脚本

生成Model:

e:
cd E:\SVN\2016\Projects\app\Model
for /f %%i in ('dir /b *.php') do (
    cd E:\SVN\2016\Projects\app
    cake bake test Model %%~ni
    )
pause

生成Controller:

e:
cd E:\SVN\2016\Projects\app\Controller
for /f %%i in ('dir /b *.php') do (
    cd E:\SVN\2016\Projects\app
    cake bake test Controller %%~ni
    )
pause

生成Component:

e:
cd E:\SVN\2016\Projects\app\Controller\Component
for /f %%i in ('dir /b *.php') do (
    cd E:\SVN\2016\Projects\app
    cake bake test Component %%~ni
    )
pause

缓存

开启redis缓存

\app\Config\bootstrap.php 添加如下内容

Cache::config('redis', array(
	'engine' => 'redis',
	'duration' => '+1 minutes',
	'prefix' => 'cake_short_'
));

检查路径 \app\Config\core.php 把关闭缓存的配置注释:

//	Configure::write('Cache.disable', true);

使用方法:

Cache::read($key, $config = 'default')
Cache::write($key, $value, $config = 'default')

参考文档:http://book.cakephp.org/2.0/zh/core-libraries/caching.html

cakephp调用多个数据库

首先在app/config/database.php添加一个配置

然后在model里用调用test这个数据库:

<?php
class Example extends AppModel {
    public $useDbConfig = test;
}

cakephp 2.9开启ACL

更新action权限列表

.\Console\cake AclExtras.AclExtras aco_update

简单的 Acl 控制的应用教程:https://book.cakephp.org/2.0/en/tutorials-and-examples/simple-acl-controlled-application/simple-acl-controlled-application.html

Acl Extras 插件https://github.com/markstory/acl_extras

cakephp创建ACL,更新aco数据库

1.安装AclExtras插件:

下载地址:https://github.com/markstory/acl_extras/

2.利用AclExtras生成acos表数据,cakephp\app 目录下执行:

cake AclExtras.AclExtras aco_sync

恢复aro数据库

cake AclExtras.AclExtras recover aro

初始化Acl表(注意:这步操作会清空aros acos aros_acos表)

./Console/cake schema create DbAcl
  • aros:在添加组及用户时会自动生成数据。
  • acos:记录controller方法对应权限的所有记录
  • aros_acos:记录aros表与acos表关系映射

ERROR

AclNode::node() - Couldn’t find Aro node identified by “Array ( [Aro0.model] => Group [Aro0.foreign_key] => 1 ) “

这个报错一般是由于aros表被清空导致,此时需要手动填入aros内容:

+----+-----------+-------+-------------+-------+-----+------+
| id | parent_id | model | foreign_key | alias | lft | rght |
+----+-----------+-------+-------------+-------+-----+------+
|  1 | NULL      | Group |           1 | NULL  |   1 |    2 |
|  2 | NULL      | Group |           2 | NULL  |   3 |    4 |
|  3 | NULL      | Group |           3 | NULL  |   5 |    6 |
|  4 | NULL      | Group |           4 | NULL  |   7 |    8 |
|  5 | NULL      | Group |           5 | NULL  |   9 |   10 |
|  6 | NULL      | Group |           6 | NULL  |  11 |   12 |
|  7 | NULL      | Group |           7 | NULL  |  13 |   14 |
|  8 | NULL      | Group |           8 | NULL  |  15 |   16 |
|  9 | NULL      | Group |           9 | NULL  |  17 |   18 |
| 10 | NULL      | Group |          10 | NULL  |  19 |   20 |
| 11 | NULL      | Group |          11 | NULL  |  21 |   22 |
| 12 | NULL      | Group |          12 | NULL  |  23 |   24 |
| 13 | NULL      | Group |          13 | NULL  |  25 |   26 |
| 14 | NULL      | Group |          14 | NULL  |  27 |   28 |
| 15 | NULL      | Group |          15 | NULL  |  29 |   30 |
| 16 | NULL      | Group |          16 | NULL  |  31 |   32 |
| 17 | NULL      | Group |          17 | NULL  |  33 |   34 |
| 18 | NULL      | Group |          18 | NULL  |  35 |   36 |
+----+-----------+-------+-------------+-------+-----+------+
18 rows in set

Fatal error: Class declarations may not be nested in ExceptionRenderer.php on line 57 cakephp

遇到报错:

Fatal error: Class declarations may not be nested in path\to\project\lib\Cake\Error\ExceptionRenderer.php on line 57 cakephp

但是,不幸的是,它没有告诉确切的文件和或变量信息。所以,对此毫无头绪,因为你必须一个接一个地通过所有的控制器去查找重复的声明。

但是,解决,事实上非常简单,准确的文件可以在日志文件中找到,对于CakePHP 2.x位于

app/tmp/logs/error.log

日志里会出现:

2017-03-02 13:59:13 Error: Fatal Error (64): Can't use method return value in write context in [/mnt/data/web/app/Controller/AppController.php, line 85]
2017-03-02 13:59:13 Error: [FatalErrorException] Can't use method return value in write context
Request URL: /

接下来去解决它吧。

An internal error has occurred

这种情况一般可以开启调试模式:

Configure::write('debug',2)

但是记得经常去清理缓存目录

app\tmp\cache

cake 3.x

1.去github下载源码

https://github.com/cakephp/cakephp/releases

2.把mysql连上,编辑 config\app.php

return [
    // More configuration above.
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'cake_blog',
            'password' => 'AngelF00dC4k3~',
            'database' => 'cake_blog',
            'encoding' => 'utf8',
            'timezone' => 'UTC'
        ],
    ],
    // More configuration below.
];

3.建立数据库表

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    password CHAR(60) NOT NULL,
    group_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE groups (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE posts (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    user_id INT(11) NOT NULL,
    title VARCHAR(255) NOT NULL,
    body TEXT,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE widgets (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    part_no VARCHAR(12),
    quantity INT(11)
);

4.设置国内composer源

composer config repo.packagist composer https://packagist.laravel-china.org

5.cakephp/acl插件安装

composer require cakephp/acl

6.自动生成模版

bin\cake bake all groups
bin\cake bake all users
bin\cake bake all posts
bin\cake bake all widgets

7.参考英文文档:

cakephp-3-acl-example

A very simple CakePHP 3 ACL plugin usage example. This example is based on Simple Acl controlled Application for CakePHP 2. The differences are described in this document. The files in this repository contain the changes and implementations of functions discuessed below.

Getting started

  • Assuming you are using composer, get a copy of the latest cakephp release by running composer create-project --prefer-dist cakephp/app acl-example. This will create an empty CakePHP project in the acl-example directory. Answer YES when asked if folder permissions should be set.
  • Navigate to the CakePHP project directory (acl-example in this case) cd acl-example
  • Install the CakePHP ACL plugin by running composer require cakephp/acl
  • Include the ACL plugin in app/config/bootstrap.php
Plugin::load('Acl', ['bootstrap' => true]);

###Example schema An example schema taken from the CakePHP 2 ACL tutorial:

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    password CHAR(60) NOT NULL,
    group_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE groups (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE posts (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    user_id INT(11) NOT NULL,
    title VARCHAR(255) NOT NULL,
    body TEXT,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE widgets (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    part_no VARCHAR(12),
    quantity INT(11)
);

After the schema is created, proceed to “bake” the application.

bin/cake bake all groups
bin/cake bake all users
bin/cake bake all posts
bin/cake bake all widgets

Preparing to Add Auth

Add UsersController::login function

public function login() {
	if ($this->request->is('post')) {
		$user = $this->Auth->identify();
		if ($user) {
			$this->Auth->setUser($user);
			return $this->redirect($this->Auth->redirectUrl());
		}
		$this->Flash->error(__('Your username or password was incorrect.'));
	}
}

Add UsersController::logout function

public function logout() {
	$this->Flash->success(__('Good-Bye'));
	$this->redirect($this->Auth->logout());
}

Add src/Templates/Users/login.ctp

<?= $this->Form->create() ?>
<fieldset>
	<legend><?= __('Login') ?></legend>
	<?= $this->Form->input('username') ?>
	<?= $this->Form->input('password') ?>
	<?= $this->Form->submit(__('Login')) ?>
</fieldset>
<?= $this->Form->end() ?>

Modify UsersTable::beforeSave to hash the password before saving

use Cake\Auth\DefaultPasswordHasher;
...
public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, 
	\ArrayObject $options)
{
	$hasher = new DefaultPasswordHasher;
	$entity->password = $hasher->hash($entity->password);
	return true;
}

Include and configure the AuthComponent and the AclComponent in the AppController

public $components = [
	'Acl' => [
		'className' => 'Acl.Acl'
	]
];
...
$this->loadComponent('Auth', [
	'authorize' => [
		'Acl.Actions' => ['actionPath' => 'controllers/']
	],
	'loginAction' => [
		'plugin' => false,
		'controller' => 'Users',
		'action' => 'login'
	],
	'loginRedirect' => [
		'plugin' => false,
		'controller' => 'Posts',
		'action' => 'index'
	],
	'logoutRedirect' => [
		'plugin' => false,
		'controller' => 'Users',
		'action' => 'login'
	],
	'unauthorizedRedirect' => [
		'controller' => 'Users',
		'action' => 'login',
		'prefix' => false
	],
	'authError' => 'You are not authorized to access that location.',
	'flash' => [
		'element' => 'error'
	]
]);

Add Temporary Auth Overrides

Temporarily allow access to UsersController and GroupsController so groups and users can be added. Add the following implementation of beforeFilter to src/Controllers/UsersController.php and src/Controllers/GroupsController.php:

public function initialize()
{
	parent::initialize();
	
	$this->Auth->allow();
}

Initialize the Db Acl tables

  • Create the ACL related tables by running bin/cake Migrations.migrations migrate -p Acl

Model Setup

Acting as a requester

  • Add the requester behavior to GroupsTable and UsersTable
  • Add $this->addBehavior('Acl.Acl', ['type' => 'requester']); to the initialize function in the files src/Model/Table/UsersTable.php and src/Model/Table/GroupsTable.php

Implement parentNode function in Group entity

Add the following implementation of parentNode to the file src/Model/Entity/Group.php:

public function parentNode()
{
	return null;
}

Implement parentNode function in User entity

Add the following implementation of parentNode to the file src/Model/Entity/User.php:

public function parentNode()
{
	if (!$this->id) {
		return null;
	}
	if (isset($this->group_id)) {
		$groupId = $this->group_id;
	} else {
		$Users = TableRegistry::get('Users');
		$user = $Users->find('all', ['fields' => ['group_id']])->where(['id' => $this->id])->first();
		$groupId = $user->group_id;
	}
	if (!$groupId) {
		return null;
	}
	return ['Groups' => ['id' => $groupId]];
}

Creating ACOs

The ACL Extras plugin referred to in the CakePHP 2 ACL tutorial is now integrated into the CakePHP ACL plugin for CakePHP 3.

自动生成acos表:bin/cake acl_extras aco_sync

  • Run bin/cake acl_extras aco_sync to automatically create ACOs.
  • ACOs and AROs can be managed manually using the ACL shell. Run bin/cake acl for more information.

Creating Users and Groups

Create Groups

  • Navigate to /groups/add and add the groups
    • For this example, we will create Administrator, Manager, and User

Create Users

  • Navigate to /users/add and add the users
    • For this example, we will create one user in each group
      • test-administrator is an Administrator
      • test-manager is a Manager
      • test-user is a User

Remove Temporary Auth Overrides

Remove the temporary auth overrides by removing the beforeFilter function or the call to $this->Auth->allow(); in src/Controllers/UsersController.php and src/Controllers/GroupsController.php.

Configuring Permissions

Configuring permissions using the ACL shell

建立ACL,生成aros_acos关联表

如果出现:Warning (512): DbAcl::allow() - Invalid node [CORE\Cake\Model\Permission.php, line 182]

可以尝试使用如下方法创建关联表,

$this->Acl->allow(array('model'=>'Groups','foreign_key'=> 4),'controllers/Posts/index');

因为以下这种方式创建关联,会去寻找aros表里的alias字段,而默认是null的

$this->Acl->allow('ReadOnly', 'controllers/Users/login');

First, find the IDs of each group you want to grant permissions on. There are several ways of doing this. Since we will be at the console anyway, the quickest way is probably to run bin/cake acl view aro to view the ARO tree. In this example, we will assume the Administrator, Manager, and User groups have IDs 1, 2, and 3 respectively.

  • Grant members of the Administrator group permission to everything
    • Run bin/cake acl grant Groups.1 controllers
  • Grant members of the Manager group permission to all actions in Posts and Widgets
    • Run bin/cake acl deny Groups.2 controllers
    • Run bin/cake acl grant Groups.2 controllers/Posts
    • Run bin/cake acl grant Groups.2 controllers/Widgets
  • Grant members of the User group permission to view Posts and Widgets
    • Run bin/cake acl deny Groups.3 controllers
    • Run bin/cake acl grant Groups.3 controllers/Posts/index
    • Run bin/cake acl grant Groups.3 controllers/Posts/view
    • Run bin/cake acl grant Groups.3 controllers/Widgets/index
    • Run bin/cake acl grant Groups.3 controllers/Widgets/view
  • Allow all groups to logout
    • Run bin/cake acl grant Groups.2 controllers/Users/logout
    • Run bin/cake acl grant Groups.3 controllers/Users/logout

如果想使用别名来创建权限

在Users模型里添加:

//src/Model/Table/UsersTable.php

use Cake\ORM\TableRegistry;

public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, 
	\ArrayObject $options)
{
	//update the user's aro record with an alias
	$alias = $entity->username;
	
	$Aros = TableRegistry::get('Aros');
	$aro = $Aros->find('all')->where([
		'model' => 'Users', 
		'foreign_key' => $entity->id
	])
	->first();
	
	$aro = $Aros->patchEntity(
		$aro, ['alias' => $alias]
	);
	return $Aros->save($aro) ? true : false;
}

在Groups模型里添加:

//src/Model/Table/GroupsTable.php

use Cake\ORM\TableRegistry;

public function afterSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, 
	\ArrayObject $options)
{
	//update the user's aro record with an alias
	$alias = $entity->name;
	
	$Aros = TableRegistry::get('Aros');
	$aro = $Aros->find('all')->where([
		'model' => 'Groups', 
		'foreign_key' => $entity->id
	])
	->first();
	
	$aro = $Aros->patchEntity(
		$aro, ['alias' => $alias]
	);
	return $Aros->save($aro) ? true : false;
}

这样在创建用户或组的时候,会自动生成aros里的alias,然后可以使用:

$this->Acl->allow('ReadOnly', 'controllers/Users/login');

来创建权限

来自:https://github.com/mattmemmesheimer/cakephp-3-acl-example

参考:https://github.com/sambaneko/cakephp-3-acl-example