月度归档:2014年10月

Zend Framework 1.X Application对象构建

//index.php
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);

//Zend_Application
$_autoloader 	装载器
$_bootstrap	引导程序
$_environment	环境(开发 or 生产)
$_optionKeys	配置字段
$_options	选项

实际这个对象重点在$_bootstrap。

Zend_Application的构造函数:

    public function __construct($environment, $options = null)
    {
        $this->_environment = (string) $environment;

        require_once 'Zend/Loader/Autoloader.php';
        $this->_autoloader = Zend_Loader_Autoloader::getInstance();

        if (null !== $options) {
            if (is_string($options)) {
                $options = $this->_loadConfig($options);
            } elseif ($options instanceof Zend_Config) {
                $options = $options->toArray();
            } elseif (!is_array($options)) {
                throw new Zend_Application_Exception('Invalid options provided; must be location of config file, a config object, or an array');
            }

            $this->setOptions($options);
/***
 *关键代码
    public function setOptions(array $options)
    {
        /*
        application.ini文件添加,对应加载文件并合并
        config.exta = APPLICATION_PATH "/configs/a.ini"
        config.extb = APPLICATION_PATH "/configs/b.ini"
        */
        if (!empty($options['config'])) {
            if (is_array($options['config'])) {
                $_options = array();
                foreach ($options['config'] as $tmp) {
                    $_options = $this->mergeOptions($_options, $this->_loadConfig($tmp));
                }
                $options = $this->mergeOptions($_options, $options);
            } else {
                $options = $this->mergeOptions($this->_loadConfig($options['config']), $options);
            }
        }

        $this->_options = $options;
	//全部转换成小写
        $options = array_change_key_case($options, CASE_LOWER);

	//设置_optionKeys
        $this->_optionKeys = array_keys($options);

	//配置PHP配置值 实际调用init_set方法
        if (!empty($options['phpsettings'])) {
            $this->setPhpSettings($options['phpsettings']);
        }
	
	//配置includepaths,可以把自定义搜索路径放入
        if (!empty($options['includepaths'])) {
            $this->setIncludePaths($options['includepaths']);
        }
	
	//通过autoloadernamespaces设置名空间
	//这里只是把名空间注册到autoloader中
        if (!empty($options['autoloadernamespaces'])) {
            $this->setAutoloaderNamespaces($options['autoloadernamespaces']);
        }

	//框架路径
        if (!empty($options['autoloaderzfpath'])) {
            $autoloader = $this->getAutoloader();
            if (method_exists($autoloader, 'setZfPath')) {
                $zfPath    = $options['autoloaderzfpath'];
                $zfVersion = !empty($options['autoloaderzfversion'])
                           ? $options['autoloaderzfversion']
                           : 'latest';
                $autoloader->setZfPath($zfPath, $zfVersion);
            }
        }

	//设置Bootstrap
        if (!empty($options['bootstrap'])) {
            $bootstrap = $options['bootstrap'];

            if (is_string($bootstrap)) {
                $this->setBootstrap($bootstrap);
            } elseif (is_array($bootstrap)) {
                if (empty($bootstrap['path'])) {
                    throw new Zend_Application_Exception('No bootstrap path provided');
                }

                $path  = $bootstrap['path'];
                $class = null;

                if (!empty($bootstrap['class'])) {
                    $class = $bootstrap['class'];
                }

                $this->setBootstrap($path, $class);
            } else {
                throw new Zend_Application_Exception('Invalid bootstrap information provided');
            }
        }

        return $this;
    }
*/
        }
    }

设置$_autoloader,$_environment,$_options。可以看到,没有明确设置$_bootstrap,实际上$_bootstrap是可以自定义的,所以它会根据配置生成,具体就是在调用setOptions()方法时生成的,$_optionKeys也在这个方法中被设置。如果不知道配置文件怎么配置,看看这个方法。

这个对象提供了一些有用的方法,比如getAutoloader()获取实例化的装载器;getBootstrap()和setBootstrap($path, $class = null)是获取和设置Bootstrap对象(如果没有则使用一个默认的);getEnvironment()获取应用的环境;getOption($key) 和 getOptions()和hasOption($key)和mergeOptions(array $array1, $array2 = null)和setOptions(array $options)是对配置值的操作;setAutoloaderNamespaces()是对装载器的封装;setIncludePaths(array $paths)添加路径到include_path;setPhpSettings(array $settings, $prefix = ”)修改php.ini的配置值。

以上的Application对象的构建过程虽然很简单,但是需要认真理解清楚,特别是setOptions()方法,所以以下分几个部分:

//setOptions()方法
        if (!empty($options['config'])) {
            if (is_array($options['config'])) {
                $_options = array();
                foreach ($options['config'] as $tmp) {
                    $_options = $this->mergeOptions($_options, $this->_loadConfig($tmp));
                }
                $options = $this->mergeOptions($_options, $options);
            } else {
                $options = $this->mergeOptions($this->_loadConfig($options['config']), $options);
            }
        }
        $this->_options = $options;

        $options = array_change_key_case($options, CASE_LOWER);

        $this->_optionKeys = array_keys($options);

这段代码高速我们,可以在配置文件中通过config指令,把在其它地方的配置加载进来。也就是说可以把配置打散到多个配置文件中,然后通过主配置文件的config指令把它们汇集起来。不过这里有个细节需要知道,Application的构造函数第一个参数是设置运行环境(开发 or 生产,或其它),比如是development,这个值和获取配置中的哪个段的配置相关联,配置文件必须存在这个段,否则将产生解析文件错误。

        if (!empty($options['phpsettings'])) {
            $this->setPhpSettings($options['phpsettings']);
        }

这个可以动态修改php.ini中的配置值(当然是可以修改的部分),内部调用ini_set函数,比如:

phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0

//等同
ini_set('display_startup_errors',0);
ini_set('display_errors',0);
        if (!empty($options['includepaths'])) {
            $this->setIncludePaths($options['includepaths']);
        }

可以通过includepaths指定要添加到include_path中的路径。

        if (!empty($options['autoloadernamespaces'])) {
            $this->setAutoloaderNamespaces($options['autoloadernamespaces']);
        }

通过使用autoloadernamespaces来注册名空间到自动装载器。

        if (!empty($options['bootstrap'])) {
            $bootstrap = $options['bootstrap'];

            if (is_string($bootstrap)) {
                $this->setBootstrap($bootstrap);
            } elseif (is_array($bootstrap)) {
                if (empty($bootstrap['path'])) {
                    throw new Zend_Application_Exception('No bootstrap path provided');
                }

                $path  = $bootstrap['path'];
                $class = null;

                if (!empty($bootstrap['class'])) {
                    $class = $bootstrap['class'];
                }

                $this->setBootstrap($path, $class);
            } else {
                throw new Zend_Application_Exception('Invalid bootstrap information provided');
            }
        }

设置Bootstrap。

另外,再去看下Bootstrap对象的setOptions()方法,就能大体把握配置文件能进行哪些配置了。

Zend Framework 1.X 的资源装载实现

在Bootstrap的构造函数中:

//Zend_Application_Bootstrap
    public function __construct($application)
    {
	//...
        if ($application->hasOption('resourceloader')) {
            $this->setOptions(array(
                'resourceloader' => $application->getOption('resourceloader')
            ));
        }
        $this->getResourceLoader();
	//...
    }

//Zend_Application_Bootstrap
    public function getResourceLoader()
    {
        if ((null === $this->_resourceLoader)
            && (false !== ($namespace = $this->getAppNamespace()))
        ) {
            $r    = new ReflectionClass($this);
            $path = $r->getFileName();
/*
    public function setResourceLoader(Zend_Loader_Autoloader_Resource $loader)
    {
        $this->_resourceLoader = $loader;
        return $this;
    }
*/
            $this->setResourceLoader(new Zend_Application_Module_Autoloader(array(
                'namespace' => $namespace,
                'basePath'  => dirname($path),
            )));
        }
        return $this->_resourceLoader;
    }

到这里,实例化一个new Zend_Application_Module_Autoloader对象,把namespace和basePath传递进去,所以先看看这个对象的构造函数:

//Zend_Application_Module_Autoloader继承Zend_Loader_Autoloader_Resource
    public function __construct($options)
    {
        parent::__construct($options);
        $this->initDefaultResourceTypes();
/*
    public function initDefaultResourceTypes()
    {
        $basePath = $this->getBasePath();
        $this->addResourceTypes(array(
            'dbtable' => array(
                'namespace' => 'Model_DbTable',
                'path'      => 'models/DbTable',
            ),
            'mappers' => array(
                'namespace' => 'Model_Mapper',
                'path'      => 'models/mappers',
            ),
            'form'    => array(
                'namespace' => 'Form',
                'path'      => 'forms',
            ),
            'model'   => array(
                'namespace' => 'Model',
                'path'      => 'models',
            ),
            'plugin'  => array(
                'namespace' => 'Plugin',
                'path'      => 'plugins',
            ),
            'service' => array(
                'namespace' => 'Service',
                'path'      => 'services',
            ),
            'viewhelper' => array(
                'namespace' => 'View_Helper',
                'path'      => 'views/helpers',
            ),
            'viewfilter' => array(
                'namespace' => 'View_Filter',
                'path'      => 'views/filters',
            ),
        ));
        $this->setDefaultResourceType('model');
    }
*/
    }

这里实际就是对application里面的文件夹作对应,比如:

            'model'   => array(
                'namespace' => 'Model',
                'path'      => 'models',
            )

实际对应命名空间Application_Model_,它对应的路径是**app_root**/application/models。可以在构造函数最后输出看看:

Zend_Application_Module_Autoloader Object
(
    [_basePath:protected] => /usr/local/httpd-2.2.27/htdocs/zf/application
    [_components:protected] => Array
        (
            [Application_Model_DbTable] => /usr/local/httpd-2.2.27/htdocs/zf/application/models/DbTable
            [Application_Model_Mapper] => /usr/local/httpd-2.2.27/htdocs/zf/application/models/mappers
            [Application_Form] => /usr/local/httpd-2.2.27/htdocs/zf/application/forms
            [Application_Model] => /usr/local/httpd-2.2.27/htdocs/zf/application/models
            [Application_Plugin] => /usr/local/httpd-2.2.27/htdocs/zf/application/plugins
            [Application_Service] => /usr/local/httpd-2.2.27/htdocs/zf/application/services
            [Application_View_Helper] => /usr/local/httpd-2.2.27/htdocs/zf/application/views/helpers
            [Application_View_Filter] => /usr/local/httpd-2.2.27/htdocs/zf/application/views/filters
        )

    [_defaultResourceType:protected] => model
    [_namespace:protected] => Application
    [_resourceTypes:protected] => Array
        (
            [dbtable] => Array
                (
                    [namespace] => Application_Model_DbTable
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/models/DbTable
                )

            [mappers] => Array
                (
                    [namespace] => Application_Model_Mapper
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/models/mappers
                )

            [form] => Array
                (
                    [namespace] => Application_Form
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/forms
                )

            [model] => Array
                (
                    [namespace] => Application_Model
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/models
                )

            [plugin] => Array
                (
                    [namespace] => Application_Plugin
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/plugins
                )

            [service] => Array
                (
                    [namespace] => Application_Service
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/services
                )

            [viewhelper] => Array
                (
                    [namespace] => Application_View_Helper
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/views/helpers
                )

            [viewfilter] => Array
                (
                    [namespace] => Application_View_Filter
                    [path] => /usr/local/httpd-2.2.27/htdocs/zf/application/views/filters
                )

        )

)

参考源代码可以看到,把所有配置置入_resourceTypes中,根据它最终生成_components,从结果已经可以猜测,举例,对Application_Model这样的类前缀,它到/usr/local/httpd-2.2.27/htdocs/zf/application/models中加载类,至于如何实现的,就要看这个类的父类的构造函数了:

//构造函数最后执行
Zend_Loader_Autoloader::getInstance()->unshiftAutoloader($this, $namespace);

//Zend_Loader_Autoloader
    public function unshiftAutoloader($callback, $namespace = '')
    {
        $autoloaders = $this->getAutoloaders();
        array_unshift($autoloaders, $callback);
        $this->setAutoloaders($autoloaders);

        $namespace = (array) $namespace;
        foreach ($namespace as $ns) {
            $autoloaders = $this->getNamespaceAutoloaders($ns);
            array_unshift($autoloaders, $callback);
            $this->_setNamespaceAutoloaders($autoloaders, $ns);
        }

        return $this;
    }

通过这个unshiftAutoloader方法,命空间Application开头的类使用Zend_Application_Module_Autoloader实例负责,换句话来说就是要加载Application的类时,
首先获取Zend_Application_Module_Autoloader实例,然后调用它的autoload方法负责加载类,而它autoload方法会根据已经设置好的映射关系找到类文件。(这里的疑问是,获取了自动装载器,怎么就知道调用autoload方法呢,实际上Zend_Loader_Autoloader的autoload方法是首先注册用来自动装载类的,它内部实现上如果命名空间有注册自动装载器,就会调用自动转载器的autoload方法)。

PHP框架Phalcon 之 ACL

Phalcon ACL组件
使用如下图解释这个组件:
Phalcon ACL

实际最终真正要使用的是access_list(ACL),但是这个ACL的填充的场景一般是在后台,先添加具体的role,然后添加resource以及resource_list(一般resource以及resource_list是固定的,因为这两个东西一般对应控制器和控制器的动作),再然后针对role设置access_list,每条access_list组成有roles_name、resources_name、access_name、allowed,检查某个用户(属于某个role)是否可以执行某控制器和动作,就检查对应的控制器和动作在列表中它的allowed的值。如果access_list已经设置好了,在检查用户权限这个步骤,完全可以只使用access_list。Phalcon\Acl\Adapter抽象类要求所有的适配器都要提供添加角色,添加资源,添加访问列表,判断权限等功能。

对于角色继承,比如有角色A和B,B继承A,如果要获取A的ACL,只要去access_list查询roles_name为A的记录即可,如果是B,则要查询roles_name为A和B的记录。

从逻辑上看,role,roles_inherits, resource, resource_access应不是ACL对象的组成部分,但是由于要管理这些资源,所有提供了对应方法。

        //acl object
        $acl = new \Phalcon\Acl\Adapter\Memory();
        $acl->setDefaultAction(\Phalcon\Acl::DENY);

        //role                  name    description
        $roleAdmins = new \Phalcon\Acl\Role("Admin","Super-User role");
$roleGuests = new \Phalcon\Acl\Role("Guests");
        $acl->addRole($roleGuests);
		
		//roles_inhelit			  roles_name  roles_inherit
		$acl->addRole($roleAdmins, $roleGuests);

        //resource              name    description
        //resource_accesses     resources_name  access_name
        $customersResource = new \Phalcon\Acl\Resource("Customers");
        $acl->addResource($customersResource,array("search","edit"));

        //access_list           roles_name  resources_name  access_name
        $acl->allow("Admin","Customers","search");

        //check acl
        //echo $acl->isAllowed("Admin","Customers","edit");
        echo $acl->isAllowed("Admin","Customers","update");

一般至少要在执行路由前要判断用户是否具有权限(一般在beforeDispatch中),所以ACL应该在它之前获得填充。以下代码可参考:

	public function beforeDispatch(Event $event, Dispatcher $dispatcher)
	{

		$auth = $this->session->get('auth');
		if (!$auth){
			$role = 'Guests';
		} else {
			$role = 'Users';
		}

		$controller = $dispatcher->getControllerName();
		$action = $dispatcher->getActionName();

		$acl = $this->getAcl();

		$allowed = $acl->isAllowed($role, $controller, $action);
		if ($allowed != Acl::ALLOW) {
			$this->flash->error("You don't have access to this module");
			$dispatcher->forward(
				array(
					'controller' => 'index',
					'action' => 'index'
				)
			);
			return false;
		}

	}

这里的getAcl()方法就是重点。参考:

	public function getAcl()
	{
		if (!isset($this->persistent->acl)) {

			$acl = new Phalcon\Acl\Adapter\Memory();

			$acl->setDefaultAction(Phalcon\Acl::DENY);

			//Register roles
			$roles = array(
				'users'  => new Phalcon\Acl\Role('Users'),
				'guests' => new Phalcon\Acl\Role('Guests')
			);
			foreach ($roles as $role) {
				$acl->addRole($role);
			}

			//Private area resources
			$privateResources = array(
				'companies'    => array('index', 'search', 'new', 'edit', 'save', 'create', 'delete'),
				'products'     => array('index', 'search', 'new', 'edit', 'save', 'create', 'delete'),
				'producttypes' => array('index', 'search', 'new', 'edit', 'save', 'create', 'delete'),
				'invoices'     => array('index', 'profile')
			);
			foreach ($privateResources as $resource => $actions) {
				$acl->addResource(new Phalcon\Acl\Resource($resource), $actions);
			}

			//Public area resources
			$publicResources = array(
				'index'   => array('index'),
				'about'   => array('index'),
				'session' => array('index', 'register', 'start', 'end'),
				'contact' => array('index', 'send')
			);
			foreach ($publicResources as $resource => $actions) {
				$acl->addResource(new Phalcon\Acl\Resource($resource), $actions);
			}

			//Grant access to public areas to both users and guests
			foreach ($roles as $role) {
				foreach ($publicResources as $resource => $actions) {
					$acl->allow($role->getName(), $resource, '*');
				}
			}

			//Grant acess to private area to role Users
			foreach ($privateResources as $resource => $actions) {
				foreach ($actions as $action){
					$acl->allow('Users', $resource, $action);
				}
			}

			//The acl is stored in session, APC would be useful here too
			$this->persistent->acl = $acl;
		}

		return $this->persistent->acl;
	}

这里把acl对象保存在persistent中。

设置和使用ACL的流程:
ACL
所有角色 资源 访问列表这些应该是要可配置并且是要保存起来的。但是Phalcon当前只提供Phalcon\Acl\Adapter\Memory适配器,在它实例化后你需要手动填充它,资源访问列表可以来自数据库等,然后可以把这个对象缓存起来,https://github.com/phalcon/incubator/tree/master/Library/Phalcon/Acl/Adapter中提供了一个保存到数据库的适配器,它可以根据数据表自动填充,可以调用相关方法添加资源、角色、访问列表,而这些如果使用Phalcon\Acl\Adapter\Memory,那么就要自己去实现。

PHP框架Phalcon 之 分页(Pagination)

Phalcon Paginator
Phalcon\Paginator\AdapterInterface有两个方法,setCurrentPage()和getPaginate()。
Phalcon\Paginator\Adapter\NativeArray 和 Phalcon\Paginator\Adapter\Model感觉很鸡肋,特别是Phalcon\Paginator\Adapter\Model,它把所有符合条件的记录全部返回填充,换句话说就是,如果有100条记录,我当前只显示20条,那么它把100条全部返回,如果翻到下一页,也是100条全部返回,然后定位到需要的记录,所以这里不讨论这两个东西。

//config/services.php中注册如下服务
	//缓存元数据
    $di->set('modelsMetadata',function(){
        $metaData = new \Phalcon\Mvc\Model\Metadata\Files(array(
                'metaDataDir' => __DIR__.'/../apps/cache/metadata/'
        ));
        return $metaData;
    });

	//设置数据库链接 记录查询
    $di->set('db', function() use($di) {
        $eventsManager = $di->get("eventsManager");
    

        $logger = new \Phalcon\Logger\Adapter\File(__DIR__."/../apps/logs/debugs.log");
        $eventsManager->attach('db', function($event, $connection) use ($logger) {
                if ($event->getType() == 'beforeQuery') {
                        $logger->log($connection->getSQLStatement(), \Phalcon\Logger::INFO);
                }
                if ($event->getType() == 'beforeSave') {
                        $logger->log($connection->getSQLStatement(), \Phalcon\Logger::INFO);
                }
        });

        $connection =  new \Phalcon\Db\Adapter\Pdo\Mysql(array(
            "host" => "127.0.0.1",
            "username" => "root",
            "password" => "root",
            "dbname" => "ai_manage",
            "charset" => "utf8"
        ));

        $connection->setEventsManager($eventsManager);
        return $connection;
    },false);

然后写一个分写查询:

//控制器动作
public function builderAction(){
        $currentPage = $this->request->getQuery("page","int",1);
        
        $builder = $this->modelsManager->createBuilder()
                ->columns('id, order_id')
                ->from('Vf\Frontend\Models\AiOrder')
                ->orderBy('id');

        $paginator = new \Phalcon\Paginator\Adapter\QueryBuilder(array(
                "builder" => $builder,
                "limit"=> 15,
                "page" => $currentPage
        ));     
        $this->view->setVar("page",$paginator->getPaginate());

}

//视图文件
<table width="50%" border="1">
<?php
foreach($page->items as $item){
?>
	<tr><td><?php echo $item->id;?></td><td><?php echo $item->order_id;?></td></tr>
<?php
}
?>
</table>

<span>
<?php
$cp = $this->request->getQuery("page","int",1);

if($cp == 1){
}else{
?>
<a href="<?php echo $this->url->get("index/builder",array("page"=>1));?>">First</a>
<?php
}

if($cp == 1){
?>
<a href="<?php echo $this->url->get("index/builder",array("page"=>$page->last));?>">Previous</a>
<?php
}else{
?>
<a href="<?php echo $this->url->get("index/builder",array("page"=>$page->before));?>">Previous</a>
<?php
}

if($cp == $page->last){
?>
<a href="<?php echo $this->url->get("index/builder",array("page"=>$page->first));?>">Next</a>
<?php
}else{
?>
<a href="<?php echo $this->url->get("index/builder",array("page"=>$page->next));?>">Next</a>
<?php
}
if($cp == $page->last){
}else{
?>
<a href="<?php echo $this->url->get("index/builder",array("page"=>$page->last));?>">Last</a>
<?php
}
?>
<?php echo "You are in page ", $page->current, " of ", $page->total_pages; ?>
</span>

输出:
Phalcon分页器

一共有762页,每页15条记录,总记录超过1万。发现翻页奇慢。查看SQL输出:

##默认第一页
[Wed, 10 Sep 14 13:57:48 +0800][INFO] SELECT `ai_order`.`id` AS `id`, `ai_order`.`order_id` AS `order_id` FROM `ai_order` ORDER BY `ai_order`.`id` LIMIT 15
[Wed, 10 Sep 14 13:57:56 +0800][INFO] SELECT COUNT(*) "rowcount" FROM (SELECT ai_order.* FROM `ai_order`) AS T
##翻到第二页
[Wed, 10 Sep 14 13:58:07 +0800][INFO] SELECT `ai_order`.`id` AS `id`, `ai_order`.`order_id` AS `order_id` FROM `ai_order` ORDER BY `ai_order`.`id` LIMIT 15 OFFSET 15
[Wed, 10 Sep 14 13:58:08 +0800][INFO] SELECT `ai_order`.`id` AS `id`, `ai_order`.`order_id` AS `order_id` FROM `ai_order` ORDER BY `ai_order`.`id` LIMIT 15 OFFSET 15
[Wed, 10 Sep 14 13:58:26 +0800][INFO] SELECT COUNT(*) "rowcount" FROM (SELECT ai_order.* FROM `ai_order`) AS T
[Wed, 10 Sep 14 13:58:26 +0800][INFO] SELECT COUNT(*) "rowcount" FROM (SELECT ai_order.* FROM `ai_order`) AS T 

缓慢的原因:

mysql> SELECT COUNT(*) "rowcount" FROM ai_order;
+----------+
| rowcount |
+----------+
|    11425 |
+----------+
1 row in set (0.03 sec)

mysql> SELECT COUNT(*) "rowcount" FROM (SELECT ai_order.* FROM `ai_order`) AS T;
+----------+
| rowcount |
+----------+
|    11425 |
+----------+
1 row in set (7.40 sec)

翻页时如下语句:

SELECT COUNT(*) "rowcount" FROM (SELECT ai_order.* FROM `ai_order`) AS T

还执行两次。每次执行花了7.4秒,执行两次公14.8秒。就是翻个页要15秒,简直蛋疼。至于为何要执行子查询而不是直接使用统计函数,就不得而知了,所以为了改善这个组件,看来只能覆盖getPaginate()方法了,我试着进行了改写,可参考:https://github.com/vfeelit/Phalcon_Paginator。

在https://github.com/phalcon/incubator/tree/master/Library/Phalcon中有提供一个叫Pager的分页器,类似如下:
Phalcon Pager分页器

<?php
namespace Vf\Frontend\Controllers;

use Vf\Frontend\Controllers\ControllerBase;
use Phalcon\Paginator\Pager;
use Phalcon\Paginator\Adapter\NativeArray as Paginator;

class IndexController extends ControllerBase
{
    public function indexAction(){
        $currentPage = $this->request->getQuery("page","int",1);

        $builder = $this->modelsManager->createBuilder()
                ->columns('id, order_id')
                ->from('Vf\Frontend\Models\AiOrder')
                ->orderBy('id');

        $pager = new Pager(
            new \Vfeelit\Paginator\Adapter\QueryBuilder(array(
                "builder" => $builder,
                "limit"=> 10,
                "page" => $currentPage
            )),
            array(
                // We will use Bootstrap framework styles
                'layoutClass' => '\Phalcon\Paginator\Pager\Layout\Bootstrap',
                // Range window will be 5 pages
                'rangeLength' => 10,
                // Just a string with URL mask
                'urlMask'     => $this->url->get("index/index").'?page={%page_number}',
                // Or something like this
                // 'urlMask'     => sprintf(
                //     '%s?page={%%page_number}',
                //     $this->url->get(array(
                //         'for'        => 'index:posts',
                //         'controller' => 'index',
                //         'action'     => 'index'
                //     ))
                // ),
            )
        );
        $this->view->setVar('pager', $pager);
    }
}

//对应视图
<table style="border:1px solid #ccc; border-collapse:collapse;">
<?php
foreach($pager->getIterator() as $p){
?>
<tr><td><?php echo $p->id;?></td><td><?php echo $p->order_id;?></td></tr>
<?php
}
?>
</table>
<?php
if($pager->haveToPaginate()){
        echo $pager->getLayout();
}

Phalcon提供的分页器相比其它框架,可以说是很不错的了。但是在针对大数据集时还是有点问题。关于这个内容就讨论这些了。

PHP框架Phalcon 之 使用缓存

Phalcon Cache
Phalcon提供的后端缓存看起来已经很多了,不过你需要的那个可能还是没有在这个列表里面。从https://github.com/phalcon/incubator中,你可以找到:

Phalcon\Cache\Backend\Database
Phalcon\Cache\Backend\Redis
Phalcon\Cache\Backend\Wincache

Phalcon\Cache\Multiple提供了设置多个后端的功能,写时同时写入。

每个后端都需要提供一个前端,这个前端主要负责数据如何输入,以什么格式保存,数据读出后以什么方法处理等。Phalcon\Cache\Frontend\Output和Phalcon\Cache\Frontend\Data的主要区别是数据的读入,前者把PHP的标准输出当做输入(通过使用PHP的输出缓冲实现),后者可以应用任何数据,保存和取出时分别进行序列化和反序列化处理,如果不希望采用这种处理,可以使用更加具体的前端,比如Phalcon\Cache\Frontend\Base64,会进行base64之后存储,如果希望不做任何处理可以使用Phalcon\Cache\Frontend\None。

使用如下程序测试:

public function memAction(){
	// Cache data for 2 days
	/*
	$frontCache = new \Phalcon\Cache\Frontend\Data(array(
    		"lifetime" => 172800
 	));
	*/

 	$frontCache = new \Phalcon\Cache\Frontend\Json(array(
    		"lifetime" => 172800
 	));

 	//Create the Cache setting memcached connection options
 	$cache = new \Phalcon\Cache\Backend\Libmemcached($frontCache, array(
     		'servers' => array(
         		array('host' => '127.0.0.1',
               		'port' => 11211,
               		'weight' => 1),
     		),
     		'client' => array(
        		\Memcached::OPT_HASH => \Memcached::HASH_MD5,
        		\Memcached::OPT_PREFIX_KEY => 'prefix.',
     		)
 	));

 	//Cache arbitrary data
 	$cache->save('my-cache-data', array(1, 2, 3, 4,array("111","222","333")));

 	//Get data
 	$data = $cache->get('my-cache-data');
	print_r($data);

	$this->view->disable();
}

首先使用\Phalcon\Cache\Frontend\Data()作为前端保存数据,查看保存的数据为:
Phalcon Cache
这个就是数据序列化存储的结果。

改为\Phalcon\Cache\Frontend\Json()作为前端,查看保存的数据为:
Phalcon Cache
这是JSON数据。

所以,当你要以一种自定义的格式把数据保存到后端时,你就需要自己实现一个前端。

另外,注意到一个稍微特殊的前端\Phalcon\Cache\Frontend\Output,它把PHP的标准输出作为它的输入,我们知道,后端的save()方法在没有指定内容时,它就像前端索要内容,这个用在需要输出内容同时又要把这些输出内容进行缓存的情况,它可以同时完成这个操作:

<?php

 //Create an Output frontend. Cache the files for 2 days
 $frontCache = new Phalcon\Cache\Frontend\Output(array(
   "lifetime" => 172800
 ));

 // Create the component that will cache from the "Output" to a "File" backend
 // Set the cache file directory - it's important to keep the "/" at the end of
 // the value for the folder
 $cache = new Phalcon\Cache\Backend\File($frontCache, array(
     "cacheDir" => "../app/cache/"
 ));

 // Get/Set the cache file to ../app/cache/my-cache.html
 $content = $cache->start("my-cache.html");

 // If $content is null then the content will be generated for the cache
 if ($content === null) {

     //Print date and time
     echo date("r");

     //Generate a link to the sign-up action
     echo Phalcon\Tag::linkTo(
         array(
             "user/signup",
             "Sign Up",
             "class" => "signup-button"
         )
     );

     // Store the output into the cache file
     $cache->save();

 } else {

     // Echo the cached output
     echo $content;
 }

接下来分析一段源代码加深对Phalcon缓存实现的理解(https://github.com/phalcon/incubator/blob/master/Library/Phalcon/Cache/Backend/Database.php),对一个后端而言,最主要的是get()和save()方法:

    public function get($keyName, $lifetime = null)
    {
        $prefixedKey = $this->getPrefixedIdentifier($keyName);
        $options     = $this->getOptions();
        $sql         = "SELECT data, lifetime FROM " . $options['table'] . " WHERE key_name = ?";
        $cache       = $options['db']->fetchOne($sql, Db::FETCH_ASSOC, array($prefixedKey));
		//不存在返回null
        if (!$cache) {
            return null;
        }
		//获取前端
        $frontend = $this->getFrontend();
		//没有指定$lifetime则默认使用前端的设置
        if ($lifetime === null) {
            $lifetime = $frontend->getLifetime();
        }

        //缓存过期删除
        if ($cache['lifetime'] < (time() - $lifetime)) {
            $options['db']->execute("DELETE FROM " . $options['table'] . " WHERE key_name = ?", array($prefixedKey));

            return null;
        }

        $this->setLastKey($keyName);
		//调用前端的afterRetrieve方法,通过它实现了不同前端的不同处理方法
        return $frontend->afterRetrieve($cache['data']);
}
	
	//保存缓存
public function save($keyName = null, $content = null, $lifetime = null, $stopBuffer = true)
{
	//确定缓存的key
        if ($keyName === null) {
            $lastKey = $this->_lastKey;
        } else {
            $lastKey = $keyName;
        }

        if (!$lastKey) {
            throw new Exception('The cache must be started first');
        }

        $options = $this->getOptions();
        $frontend = $this->getFrontend();

		//没有指定缓存则从前端获取
        if ($content === null) {
            $content = $frontend->getContent();
        }

        //检查缓存是否存在
        $prefixedKey = $this->getPrefixedIdentifier($keyName);
        $sql         = "SELECT data, lifetime FROM " . $options['table'] . " WHERE key_name = ?";
        $cache       = $options['db']->fetchOne($sql, Db::FETCH_ASSOC, array($prefixedKey));
		
		//缓存不存储则插入,存在则更新
        if (!$cache) {
            $options['db']->execute("INSERT INTO " . $options['table'] . " VALUES (?, ?, ?)", array(
                $prefixedKey,
				//保存内容前先调用前端的beforeStore方法处理数据
                $frontend->beforeStore($content),
                time()
            ));
        } else {
            $options['db']->execute(
                "UPDATE " . $options['table'] . " SET data = ?, lifetime = ? WHERE key_name = ?",
                array(
                    $frontend->beforeStore($content),
                    time(),
                    $prefixedKey
                )
            );
        }

        // Stop the buffer, this only applies for Phalcon\Cache\Frontend\Output
        if ($stopBuffer) {
            $frontend->stop();
        }

        // Print the buffer, this only applies for Phalcon\Cache\Frontend\Output
        if ($frontend->isBuffering()) {
            echo $content;
        }

        $this->_started = false;
    }

以上程序可知get()缓存时,并没有修改它的时间戳,实际传递进去的缓存时间只是判断缓存是否过期的参数(所有后端都应该是如此实现),在save()时缓存的时间戳被更新。

————————————————————————–
安装使用Memcached:

##安装libmemcached库
#wget https://launchpadlibrarian.net/165454254/libmemcached-1.0.18.tar.gz
#tar zxvf libmemcached-1.0.18.tar.gz
#cd libmemcached-1.0.18
#./configure --prefix=/usr/local/libmemcached --with-memcached
#make
#make install

##安装针对libmemcached库的PHP扩展memcached
#wget http://pecl.php.net/get/memcached-2.2.0.tgz
#tar zxvf memcached-2.2.0.tgz
#cd memcached-2.2.0
#/usr/local/php/bin/phpize
#./configure --with-php-config=/usr/local/php/bin/php-config --with-libmemcached-dir=/usr/local/libmemcached
#make
#make install

##往php.ini中添加
extension= memcached.so

##安装memcached服务器依赖的工具livevent
#wget https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
#tar zxvf libevent-2.0.21-stable.tar.gz
#cd libevent-2.0.21-stable
#./configure --prefix=/usr
#make
#make install

##安装memcached服务器
#wget http://memcached.org/latest
#tar -zxvf memcached-1.x.x.tar.gz
#cd memcached-1.x.x
#./configure --prefix=/usr/local/memcached --with-libevent=/usr
#make
#make install

##启动memcached服务器
memcached -d -p 11211 -u root -m 64 -c 10240

———————————————————
以下官方文档内容:

使用缓存提高性能(Improving Performance with Cache)

Phalcon provides the Phalcon\Cache class allowing faster access to frequently used or already processed data. Phalcon\Cache is written in C, achieving higher performance and reducing the overhead when getting items from the backends. This class uses an internal structure of frontend and backend components. Front-end components act as input sources or interfaces, while backend components offer storage options to the class.
Phalcon提供Phalcon\Cache类允许快速访问经常使用或已经处理的数据。Phalcon\Cache用C编写,实现从后端获取东西时的更高性能和降低开销。这类使用一个内部结构的前端和后端组件。前端组件作为输入源或接口,而后端组件提供存储选项。

什么情况下使用缓存?(When to implement cache?)
Although this component is very fast, implementing it in cases that are not needed could lead to a loss of performance rather than gain. We recommend you check this cases before using a cache:
You are making complex calculations that every time return the same result (changing infrequently)
You are using a lot of helpers and the output generated is almost always the same
You are accessing database data constantly and these data rarely change

NOTE Even after implementing the cache, you should check the hit ratio of your cache over a period of time. This can easily be done, especially in the case of Memcache or Apc, with the relevant tools that backends provide.

缓存行为(Caching Behavior)
The caching process is divided into 2 parts:
缓存过程分隔为两个部分:
Frontend: This part is responsible for checking if a key has expired and perform additional transformations to the data before storing and after retrieving them from the backend-
前端:这个部分代表检查一个key是否已经过期和执行在从后端存储之前和取出它们之后的其它的数据转换
Backend: This part is responsible for communicating, writing/reading the data required by the frontend.

缓存输出片段(Caching Output Fragments)
An output fragment is a piece of HTML or text that is cached as is and returned as is. The output is automatically captured from the ob_* functions or the PHP output so that it can be saved in the cache. The following example demonstrates such usage. It receives the output generated by PHP and stores it into a file. The contents of the file are refreshed every 172800 seconds (2 days).
一个输出片段是一个块HTML或文本。输出自动使用ob_*函数采集或PHP输出以致它可以在缓存中保存。

The implementation of this caching mechanism allows us to gain performance by not executing the helper Phalcon\Tag::linkTo call whenever this piece of code is called.

<?php

//Create an Output frontend. Cache the files for 2 days
$frontCache = new Phalcon\Cache\Frontend\Output(array(
    "lifetime" => 172800
));

// Create the component that will cache from the "Output" to a "File" backend
// Set the cache file directory - it's important to keep the "/" at the end of
// the value for the folder
$cache = new Phalcon\Cache\Backend\File($frontCache, array(
    "cacheDir" => "../app/cache/"
));

// Get/Set the cache file to ../app/cache/my-cache.html
$content = $cache->start("my-cache.html");

// If $content is null then the content will be generated for the cache
if ($content === null) {

    //Print date and time
    echo date("r");

    //Generate a link to the sign-up action
    echo Phalcon\Tag::linkTo(
        array(
            "user/signup",
            "Sign Up",
            "class" => "signup-button"
        )
    );

    // Store the output into the cache file
    $cache->save();

} else {

    // Echo the cached output
    echo $content;
}

NOTE In the example above, our code remains the same, echoing output to the user as it has been doing before. Our cache component transparently captures that output and stores it in the cache file (when the cache is generated) or it sends it back to the user pre-compiled from a previous call, thus avoiding expensive operations.

缓存任意数据(Caching Arbitrary Data)
Caching just data is equally important for your application. Caching can reduce database load by reusing commonly used (but not updated) data, thus speeding up your application.(说缓存可以提高性能)
文件后端存储器例子(File Backend Example)
One of the caching adapters is ‘File’. The only key area for this adapter is the location of where the cache files will be stored. This is controlled by the cacheDir option which must have a backslash at the end of it.(指定缓存的目录路基后面需要有斜杠)

<?php

// Cache the files for 2 days using a Data frontend
$frontCache = new Phalcon\Cache\Frontend\Data(array(
    "lifetime" => 172800
));

// Create the component that will cache "Data" to a "File" backend
// Set the cache file directory - important to keep the "/" at the end of
// of the value for the folder
$cache = new Phalcon\Cache\Backend\File($frontCache, array(
    "cacheDir" => "../app/cache/"
));

// Try to get cached records
$cacheKey = 'robots_order_id.cache';
$robots    = $cache->get($cacheKey);
if ($robots === null) {

    // $robots is null because of cache expiration or data does not exist
    // Make the database call and populate the variable
    $robots = Robots::find(array("order" => "id"));

    // Store it in the cache
    $cache->save($cacheKey, $robots);
}

// Use $robots 🙂
foreach ($robots as $robot) {
   echo $robot->name, "\n";
}

Memcached 后端存储器例子(Memcached Backend Example)
The above example changes slightly (especially in terms of configuration) when we are using a Memcached backend.

<?php

//Cache data for one hour
$frontCache = new Phalcon\Cache\Frontend\Data(array(
    "lifetime" => 3600
));

// Create the component that will cache "Data" to a "Memcached" backend
// Memcached connection settings
$cache = new Phalcon\Cache\Backend\Libmemcached($frontCache, array(
    "servers" => array(
            array(
                    "host" => "127.0.0.1",
                    "port" => "11211",
                    "weight" => "1"
            )
    )
));

// Try to get cached records
$cacheKey = 'robots_order_id.cache';
$robots    = $cache->get($cacheKey);
if ($robots === null) {

    // $robots is null because of cache expiration or data does not exist
    // Make the database call and populate the variable
    $robots = Robots::find(array("order" => "id"));

    // Store it in the cache
    $cache->save($cacheKey, $robots);
}

// Use $robots 🙂
foreach ($robots as $robot) {
   echo $robot->name, "\n";
}

查询缓存(Querying the cache)
The elements added to the cache are uniquely identified by a key. In the case of the File backend, the key is the actual filename. To retrieve data from the cache, we just have to call it using the unique key. If the key does not exist, the get method will return null.
添加到缓存中的元素有一个唯一的key。对文件后端它就是文件名。要从缓存获取数据,我们只要使用这个唯一key调用get方法。如果key不存在,get方法返回null。

<?php

// Retrieve products by key "myProducts"
$products = $cache->get("myProducts");

If you want to know which keys are stored in the cache you could call the queryKeys method:

<?php

// Query all keys used in the cache
$keys = $cache->queryKeys();
foreach ($keys as $key) {
    $data = $cache->get($key);
    echo "Key=", $key, " Data=", $data;
}

//Query keys in the cache that begins with "my-prefix"
$keys = $cache->queryKeys("my-prefix");

删除缓存数据(Deleting data from the cache)
There are times where you will need to forcibly invalidate a cache entry (due to an update in the cached data). The only requirement is to know the key that the data have been stored with.

<?php

// Delete an item with a specific key
$cache->delete("someKey");

// Delete all items from the cache
$keys = $cache->queryKeys();
foreach ($keys as $key) {
    $cache->delete($key);
}

检查缓存是否存在(Checking cache existence)It is possible to check if a cache already exists with a given key:

<?php

if ($cache->exists("someKey")) {
    echo $cache->get("someKey");
} else {
    echo "Cache does not exists!";
}

有效期(Lifetime)
A “lifetime” is a time in seconds that a cache could live without expire. By default, all the created caches use the lifetime set in the frontend creation. You can set a specific lifetime in the creation or retrieving of the data from the cache:

Setting the lifetime when retrieving:

<?php

$cacheKey = 'my.cache';

//Setting the cache when getting a result
$robots = $cache->get($cacheKey, 3600);
if ($robots === null) {

    $robots = "some robots";

    // Store it in the cache
    $cache->save($cacheKey, $robots);
}

Setting the lifetime when saving:

<?php

$cacheKey = 'my.cache';

$robots = $cache->get($cacheKey);
if ($robots === null) {

    $robots = "some robots";

    //Setting the cache when saving data
    $cache->save($cacheKey, $robots, 3600);
}

多级缓存(Multi-Level Cache)
This feature ​of the cache component, ​allows ​the developer to implement a multi-level cache​. This new feature is very ​useful because you can save the same data in several cache​ locations​ with different lifetimes, reading ​first from the one with the faster adapter and ending with the slowest one until the data expire​s​:

<?php

use Phalcon\Cache\Frontend\Data as DataFrontend,
    Phalcon\Cache\Multiple,
    Phalcon\Cache\Backend\Apc as ApcCache,
    Phalcon\Cache\Backend\Memcache as MemcacheCache,
    Phalcon\Cache\Backend\File as FileCache;

$ultraFastFrontend = new DataFrontend(array(
    "lifetime" => 3600
));

$fastFrontend = new DataFrontend(array(
    "lifetime" => 86400
));

$slowFrontend = new DataFrontend(array(
    "lifetime" => 604800
));

//Backends are registered from the fastest to the slower
$cache = new Multiple(array(
    new ApcCache($ultraFastFrontend, array(
        "prefix" => 'cache',
    )),
    new MemcacheCache($fastFrontend, array(
        "prefix" => 'cache',
        "host" => "localhost",
        "port" => "11211"
    )),
    new FileCache($slowFrontend, array(
        "prefix" => 'cache',
        "cacheDir" => "../app/cache/"
    ))
));

//Save, saves in every backend
$cache->save('my-key', $data);

前端适配器(Frontend Adapters)
The available frontend adapters that are used as interfaces or input sources to the cache are:

Adapter Description Example
Output Read input data from standard PHP output Phalcon\Cache\Frontend\Output
Data It’s used to cache any kind of PHP data (big arrays, objects, text, etc). Data is serialized before stored in the backend. Phalcon\Cache\Frontend\Data
Base64 It’s used to cache binary data. The data is serialized using base64_encode before be stored in the backend. Phalcon\Cache\Frontend\Base64
Json Data is encoded in JSON before be stored in the backend. Decoded after be retrieved. This frontend is useful to share data with other languages or frameworks. Phalcon\Cache\Frontend\Json
IgBinary It’s used to cache any kind of PHP data (big arrays, objects, text, etc). Data is serialized using IgBinary before be stored in the backend. Phalcon\Cache\Frontend\Igbinary
None It’s used to cache any kind of PHP data without serializing them. Phalcon\Cache\Frontend\None

Adapter Description Example
Output Read input data from standard PHP output Phalcon\Cache\Frontend\Output
Data It’s used to cache any kind of PHP data (big arrays, objects, text, etc). Data is serialized before stored in the backend. Phalcon\Cache\Frontend\Data
Base64 It’s used to cache binary data. The data is serialized using base64_encode before be stored in the backend. Phalcon\Cache\Frontend\Base64
Json Data is encoded in JSON before be stored in the backend. Decoded after be retrieved. This frontend is useful to share data with other languages or frameworks. Phalcon\Cache\Frontend\Json
IgBinary It’s used to cache any kind of PHP data (big arrays, objects, text, etc). Data is serialized using IgBinary before be stored in the backend. Phalcon\Cache\Frontend\Igbinary
None It’s used to cache any kind of PHP data without serializing them. Phalcon\Cache\Frontend\None

自定义前端适配器(Implementing your own Frontend adapters)
The Phalcon\Cache\FrontendInterface interface must be implemented in order to create your own frontend adapters or extend the existing ones.

后端适配器(Backend Adapters)
The backend adapters available to store cache data are:

Adapter Description Info Required Extensions Example
File Stores data to local plain files     Phalcon\Cache\Backend\File
Memcached Stores data to a memcached server Memcached memcache Phalcon\Cache\Backend\Memcache
APC Stores data to the Alternative PHP Cache (APC) APC APC extension Phalcon\Cache\Backend\Apc
Mongo Stores data to Mongo Database MongoDb Mongo Phalcon\Cache\Backend\Mongo
XCache Stores data in XCache XCache xcache extension Phalcon\Cache\Backend\Xcache

PHP框架Phalcon 之 安全 加密解密

安全(Security)

This component aids the developer in common security tasks such as password hashing and Cross-Site Request Forgery protection (CSRF).
通用的安全任务比如密码哈希和跨站请求伪造保护(CSRF)

密码散列(Password Hashing)
Storing passwords in plain text is a bad security practice. Anyone with access to the database will immediately have access to all user accounts thus being able to engage in unauthorized activities. To combat that, many applications use the familiar one way hashing methods “md5” and “sha1”. However, hardware evolves each day, and becomes faster, these algorithms are becoming vulnerable to brute force attacks. These attacks are also known as rainbow tables.
硬件提升,密码容器破解。这些攻击工具有著名的彩虹表。

To solve this problem we can use hash algorithms as bcrypt. Why bcrypt? Thanks to its “Eksblowfish” key setup algorithm we can make the password encryption as “slow” as we want. Slow algorithms make the process to calculate the real password behind a hash extremely difficult if not impossible. This will protect your for a long time from a possible attack using rainbow tables.

This component gives you the ability to use this algorithm in a simple way:

<?php

use Phalcon\Mvc\Controller;

class UsersController extends Controller
{

    public function registerAction()
    {

        $user = new Users();

        $login = $this->request->getPost('login');
        $password = $this->request->getPost('password');

        $user->login = $login;

        //Store the password hashed
        $user->password = $this->security->hash($password);

        $user->save();
    }

}

We saved the password hashed with a default work factor. A higher work factor will make the password less vulnerable as its encryption will be slow. We can check if the password is correct as follows:

<?php

use Phalcon\Mvc\Controller;

class SessionController extends Controller
{

    public function loginAction()
    {

        $login = $this->request->getPost('login');
        $password = $this->request->getPost('password');

        $user = Users::findFirstByLogin($login);
        if ($user) {
            if ($this->security->checkHash($password, $user->password)) {
                //The password is valid
            }
        }

        //The validation has failed
    }

}

The salt is generated using pseudo-random bytes with the PHP’s function openssl_random_pseudo_bytes so is required to have the openssl extension loaded.
需要使用openssl扩展。

防止跨站点请求伪造攻击(Cross-Site Request Forgery (CSRF) protection)
This is another common attack against web sites and applications. Forms designed to perform tasks such as user registration or adding comments are vulnerable to this attack.

The idea is to prevent the form values from being sent outside our application. To fix this, we generate a random nonce (token) in each form, add the token in the session and then validate the token once the form posts data back to our application by comparing the stored token in the session to the one submitted by the form:
对每个表单产生一个随机的token,添加这个token到session中,如果有表单提交过来则验证这个token:

<?php echo Tag::form('session/login') ?>

    <!-- login and password inputs ... -->

    <input type="hidden" name="<?php echo $this->security->getTokenKey() ?>"
        value="<?php echo $this->security->getToken() ?>"/>

</form>

Then in the controller’s action you can check if the CSRF token is valid:

<?php

use Phalcon\Mvc\Controller;

class SessionController extends Controller
{

    public function loginAction()
    {
        if ($this->request->isPost()) {
            if ($this->security->checkToken()) {
                //The token is ok
            }
        }
    }

}

(三个方法,getTokenKey()和getToken()和checkToken())

Remember to add a session adapter to your Dependency Injector, otherwise the token check won’t work:

$di->setShared('session', function() {
    $session = new Phalcon\Session\Adapter\Files();
    $session->start();
    return $session;
});

Adding a captcha to the form is also recommended to completely avoid the risks of this attack.

设置组件(Setting up the component)
This component is automatically registered in the services container as ‘security’, you can re-register it to setup it’s options:
这个组件自动注册为‘security’:

<?php

$di->set('security', function(){

    $security = new Phalcon\Security();

    //Set the password hashing factor to 12 rounds
    $security->setWorkFactor(12);

    return $security;
}, true);

———————————————–
关于加密服务,默认Phalcon会注册一个叫crypt的服务,但是一般需要自定义一下,因为通过自定义可以设置一个key,那么在调用加密解密函数时就不需要指定key。
———————————————–
加密与解密(Encryption/Decryption)
Phalcon provides encryption facilities via the Phalcon\Crypt component. This class offers simple object-oriented wrappers to the mcrypt php’s encryption library.
Phalcon通过Phalcon\Crypt组件提供加密工具。这个类提供了简单的面向对象的针对php的加密库mcrypt的包装器。

By default, this component provides secure encryption using AES-256 (rijndael-256-cbc).

Basic Usage
This component is designed to provide a very simple usage:

<?php

//Create an instance
$crypt = new Phalcon\Crypt();

$key = 'le password';
$text = 'This is a secret text';

$encrypted = $crypt->encrypt($text, $key);

echo $crypt->decrypt($encrypted, $key);

You can use the same instance to encrypt/decrypt several times:

<?php

//Create an instance
$crypt = new Phalcon\Crypt();

$texts = array(
    'my-key' => 'This is a secret text',
    'other-key' => 'This is a very secret'
);

foreach ($texts as $key => $text) {

    //Perform the encryption
    $encrypted = $crypt->encrypt($text, $key);

    //Now decrypt
    echo $crypt->decrypt($encrypted, $key);
}

加密选项(Encryption Options)
The following options are available to change the encryption behavior:

Name Description
Cipher The cipher is one of the encryption algorithms supported by libmcrypt. You can see a list here
Mode One of the encryption modes supported by libmcrypt (ecb, cbc, cfb, ofb)
<?php

//Create an instance
$crypt = new Phalcon\Crypt();

//Use blowfish
$crypt->setCipher('blowfish');

$key = 'le password';
$text = 'This is a secret text';

echo $crypt->encrypt($text, $key);

提供 Base64(Base64 Support)
In order that encryption is properly transmitted (emails) or displayed (browsers) base64 encoding is usually applied to encrypted texts:
为了让加密文本更容器传送,可以把加密文本转换为Base64编码:

<?php

//Create an instance
$crypt = new Phalcon\Crypt();

$key = 'le password';
$text = 'This is a secret text';

$encrypt = $crypt->encryptBase64($text, $key);

echo $crypt->decryptBase64($text, $key);

配置加密服务(Setting up an Encryption service)
You can set up the encryption component in the services container in order to use it from any part of the application:

<?php

$di->set('crypt', function() {

    $crypt = new Phalcon\Crypt();

    //Set a global encryption key
    $crypt->setKey('%31.1e$i86e$f!8jz');

    return $crypt;
}, true);

Then, for example, in a controller you can use it as follows:

<?php

use Phalcon\Mvc\Controller;

class SecretsController extends Controller
{

    public function saveAction()
    {
        $secret = new Secrets();

        $text = $this->request->getPost('text');

        $secret->content = $this->crypt->encrypt($text);

        if ($secret->save()) {
            $this->flash->success('Secret was successfully created!');
        }

    }

}

PHP框架Phalcon 之 读取配置

Phalcon\Config is a component used to read configuration files of various formats (using adapters) into PHP objects for use in an application.

文件适配器(File Adapters)
The adapters available are:

File Type Description
Ini Uses INI files to store settings. Internally the adapter uses the PHP function parse_ini_file.
Array Uses PHP multidimensional arrays to store settings. This adapter offers the best performance.

原生数组(Native Arrays)
The next example shows how to convert native arrays into Phalcon\Config objects. This option offers the best performance since no files are read during this request.
下一个例子展示如何转换本地数组到Phalcon\Config对象。这个选项提供了最好的性能因为在请求中没有不需要读取文件。

<?php

$settings = array(
    "database" => array(
        "adapter"  => "Mysql",
        "host"     => "localhost",
        "username" => "scott",
        "password" => "cheetah",
        "dbname"     => "test_db",
    ),
     "app" => array(
        "controllersDir" => "../app/controllers/",
        "modelsDir"      => "../app/models/",
        "viewsDir"       => "../app/views/",
    ),
    "mysetting" => "the-value"
);

$config = new \Phalcon\Config($settings);

echo $config->app->controllersDir, "\n";
echo $config->database->username, "\n";
echo $config->mysetting, "\n";

If you want to better organize your project you can save the array in another file and then read it.

<?php

require "config/config.php";
$config = new \Phalcon\Config($settings);

读取 INI 文件(Reading INI Files)
Ini files are a common way to store settings. Phalcon\Config uses the optimized PHP function parse_ini_file to read these files. Files sections are parsed into sub-settings for easy access.
Phalcon\Config使用PHP函数parse_ini_file来读取这些文件。

[database]
adapter  = Mysql
host     = localhost
username = scott
password = cheetah
dbname     = test_db

[phalcon]
controllersDir = "../app/controllers/"
modelsDir      = "../app/models/"
viewsDir       = "../app/views/"

[models]
metadata.adapter  = "Memory"

//You can read the file as follows:
<?php

$config = new \Phalcon\Config\Adapter\Ini("path/config.ini");

echo $config->phalcon->controllersDir, "\n";
echo $config->database->username, "\n";
echo $config->models->metadata->adapter, "\n";

(一般通过ini可以设置继承,Phalcon中的ini就是一般的ini文件而已)

合并配置(Merging Configurations)
Phalcon\Config allows to merge a configuration object into another one recursively:

<?php

$config = new \Phalcon\Config(array(
    'database' => array(
        'host' => 'localhost',
        'dbname' => 'test_db'
    ),
    'debug' => 1
));

$config2 = new \Phalcon\Config(array(
    'database' => array(
        'username' => 'scott',
        'password' => 'secret',
    )
));

$config->merge($config2);

print_r($config);

///The above code produces the following:
Phalcon\Config Object
(
    [database] => Phalcon\Config Object
        (
            [host] => localhost
            [dbname] => test_db
            [username] => scott
            [password] => secret
        )
    [debug] => 1
)

PHP框架Phalcon 之 过滤与清理 验证

过滤是指按照条件过滤出符合条件的数据或过滤掉符合条件的数据;清理是指按照条件清理数据中的非法内容;验证是指验证数据是否符合条件,可为同一个字段指定多个验证器,在验证之前数据可以首先被过滤或清理。

Phalcon中不区分过滤与清理,过滤就是清理,清理就是过滤。
—————————————
过滤与清理(Filtering and Sanitizing)
Sanitizing user input is a critical part of software development. Trusting or neglecting to sanitize user input could lead to unauthorized access to the content of your application, mainly user data, or even the server your application is hosted on.
清理用户的输入是软件开发中的关键部分…

The Phalcon\Filter component provides a set of commonly used filters and data sanitizing helpers. It provides object-oriented wrappers around the PHP filter extension.
Phalcon\Filter组件提供了一套通用的过滤器和数据清理助手。它提供了一个面向对象的围绕PHP filter扩展的包装器。(注意,它是PHP filter扩展的包装器)

清理数据(Sanitizing data)
Sanitizing is the process which removes specific characters from a value, that are not required or desired by the user or application. By sanitizing input we ensure that application integrity will be intact.
清理是从一个值中删除特定字符的过程,这些删除的字符时用户或应用不要的。

<?php

$filter = new \Phalcon\Filter();

// returns "someone@example.com"
$filter->sanitize("some(one)@exa\mple.com", "email");

// returns "hello"
$filter->sanitize("hello<<", "string");

// returns "100019"
$filter->sanitize("!100a019", "int");

// returns "100019.01"
$filter->sanitize("!100a019.01a", "float");

在控制器中使用清理(Sanitizing from Controllers)
You can access a Phalcon\Filter object from your controllers when accessing GET or POST input data (through the request object). The first parameter is the name of the variable to be obtained; the second is the filter to be applied on it.
第一参数是将要获取的变量名称,第二个是将要应用的过滤器。

<?php

class ProductsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function saveAction()
    {

        // Sanitizing price from input
        $price = $this->request->getPost("price", "double");

        // Sanitizing email from input
        $email = $this->request->getPost("customerEmail", "email");

    }

}

过滤动作参数(Filtering Action Parameters)
The next example shows you how to sanitize the action parameters within a controller action:
清理动作参数:

<?php

class ProductsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function showAction($productId)
    {
        $productId = $this->filter->sanitize($productId, "int");
    }

}

过滤数据(Filtering data)
In addition to sanitizing, Phalcon\Filter also provides filtering by removing or modifying input data to the format we expect.

<?php

$filter = new \Phalcon\Filter();

// returns "Hello"
$filter->sanitize("<h1>Hello</h1>", "striptags");

// returns "Hello"
$filter->sanitize("  Hello   ", "trim");

内置过滤器类型(Types of Built-in Filters)
The following are the built-in filters provided by this component:

Name Description
string Strip tags
email Remove all characters except letters, digits and !#$%&*+-/=?^_`{|}~@.[].
int Remove all characters except digits, plus and minus sign.
float Remove all characters except digits, dot, plus and minus sign.
alphanum Remove all characters except [a-zA-Z0-9]
striptags Applies the strip_tags function
trim Applies the trim function
lower Applies the strtolower function
upper Applies the strtoupper function

创建过滤器(Creating your own Filters)
You can add your own filters to Phalcon\Filter. The filter function could be an anonymous function:

<?php

$filter = new \Phalcon\Filter();

//Using an anonymous function
$filter->add('md5', function($value) {
    return preg_replace('/[^0-9a-f]/', '', $value);
});

//Sanitize with the "md5" filter
$filtered = $filter->sanitize($possibleMd5, "md5");

Or, if you prefer, you can implement the filter in a class:

<?php

class IPv4Filter
{

    public function filter($value)
    {
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
    }

}

$filter = new \Phalcon\Filter();

//Using an object
$filter->add('ipv4', new IPv4Filter());

//Sanitize with the "ipv4" filter
$filteredIp = $filter->sanitize("127.0.0.1", "ipv4");

复杂的过滤与清理(Complex Sanitizing and Filtering)
PHP itself provides an excellent filter extension you can use. Check out its documentation: Data Filtering at PHP Documentation

自定义过滤器(Implementing your own Filter)
The Phalcon\FilterInterface interface must be implemented to create your own filtering service replacing the one provided by Phalcon.

验证(Validation)

Phalcon\Validation is an independent validation component that validates an arbitrary set of data. This component can be used to implement validation rules on data objects that do not belong to a model or collection.
Phalcon\Validation是一个独立的验证组件可以验证数据任意集合。这个组件可以用来在数据对象上实现验证规则。

The following example shows its basic usage:

<?php

use Phalcon\Validation\Validator\PresenceOf,
    Phalcon\Validation\Validator\Email;

$validation = new Phalcon\Validation();

$validation->add('name', new PresenceOf(array(
    'message' => 'The name is required'
)));

$validation->add('email', new PresenceOf(array(
    'message' => 'The e-mail is required'
)));

$validation->add('email', new Email(array(
    'message' => 'The e-mail is not valid'
)));

$messages = $validation->validate($_POST);
if (count($messages)) {
    foreach ($messages as $message) {
        echo $message, '<br>';
    }
}

The loosely-coupled design of this component allows you to create your own validators along with the ones provided by the framework.

初始化验证(Initializing Validation)
Validation chains can be initialized in a direct manner by just adding validators to the Phalcon\Validation object. You can put your validations in a separate file for better re-use code and organization:
在一个直接的管理器中通过只添加验证器到Phalcon\Validation对象中,验证链可以被初始化 。

<?php

use Phalcon\Validation,
    Phalcon\Validation\Validator\PresenceOf,
    Phalcon\Validation\Validator\Email;

class MyValidation extends Validation
{
    public function initialize()
    {
        $this->add('name', new PresenceOf(array(
            'message' => 'The name is required'
        )));

        $this->add('email', new PresenceOf(array(
            'message' => 'The e-mail is required'
        )));

        $this->add('email', new Email(array(
            'message' => 'The e-mail is not valid'
        )));
    }
}

<?php

$validation = new MyValidation();

$messages = $validation->validate($_POST);
if (count($messages)) {
    foreach ($messages as $message) {
        echo $message, '<br>';
    }
}

验证器(Validators)
Phalcon exposes a set of built-in validators for this component:

Name Explanation Example
PresenceOf Validates that a field’s value is not null or empty string. Example
Identical Validates that a field’s value is the same as a specified value Example
Email Validates that field contains a valid email format Example
ExclusionIn Validates that a value is not within a list of possible values Example
InclusionIn Validates that a value is within a list of possible values Example
Regex Validates that the value of a field matches a regular expression Example
StringLength Validates the length of a string Example
Between Validates that a value is between two values Example
Confirmation Validates that a value is the same as another present in the data Example

The following example explains how to create additional validators for this component:
演示如果创建其它验证器:

<?php

use Phalcon\Validation\Validator,
    Phalcon\Validation\ValidatorInterface,
    Phalcon\Validation\Message;

class IpValidator extends Validator implements ValidatorInterface
{

    /**
     * Executes the validation
     *
     * @param Phalcon\Validation $validator
     * @param string $attribute
     * @return boolean
     */
    public function validate($validator, $attribute)
    {
        $value = $validator->getValue($attribute);

        if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {

            $message = $this->getOption('message');
            if (!$message) {
                $message = 'The IP is not valid';
            }

            $validator->appendMessage(new Message($message, $attribute, 'Ip'));

            return false;
        }

        return true;
    }

}

It is important that validators return a valid boolean value indicating if the validation was successful or not.

验证信息(Validation Messages)
Phalcon\Validation has a messaging subsystem that provides a flexible way to output or store the validation messages generated during the validation processes.
提供了灵活的输出或存储验证消息。

Each message consists of an instance of the class Phalcon\Validation\Message. The set of messages generated can be retrieved with the getMessages() method. Each message provides extended information like the attribute that generated the message or the message type:
每条消息被认为是一个Phalcon\Validation\Message类的实例。消息集可以通过getMessages()方法获取。每条消息提供扩展的信息比如属性或消息类型:

<?php

$messages = $validation->validate();
if (count($messages)) {
    foreach ($validation->getMessages() as $message) {
        echo "Message: ", $message->getMessage(), "\n";
        echo "Field: ", $message->getField(), "\n";
        echo "Type: ", $message->getType(), "\n";
    }
}

The getMessages() method can be overridden in a validation class to replace/translate the default messages generated by the validators:
在验证类中getMessages()方法可以重新以替换或翻译有验证器产生的默认消息:

<?php

class MyValidation extends Phalcon\Validation
{

    public function initialize()
    {
        // ...
    }

    public function getMessages()
    {
        $messages = array();
        foreach (parent::getMessages() as $message) {
            switch ($message->getType()) {
                case 'PresenceOf':
                    $messages[] = 'The field ' . $message->getField() . ' is mandatory';
                    break;
            }
        }
        return $messages;
    }
}

Or you can pass a ‘message’ parameter to change the default message in each validator:
你可以传递一个‘message’参数去改变在每个验证器中的默认消息:

<?php

use Phalcon\Validation\Validator\Email;

$validation->add('email', new Email(array(
    'message' => 'The e-mail is not valid'
)));

By default, ‘getMessages’ returns all the messages generated during validation. You can filter messages for a specific field using the ‘filter’ method:
通过filter方法获取返回的消息中的自己要的:

<?php

$messages = $validation->validate();
if (count($messages)) {
    //Filter only the messages generated for the field 'name'
    foreach ($validation->getMessages()->filter('name') as $message) {
        echo $message;
    }
}

过滤数据(Filtering of Data)
Data can be filtered prior to the validation ensuring that malicious or incorrect data is not validated.
数据可以被过滤优先于验证以确保恶意的或不正确的数据不被验证(验证前先过滤数据)。

<?php

$validation = new Phalcon\Validation();

$validation
    ->add('name', new PresenceOf(array(
        'message' => 'The name is required'
    )))
    ->add('email', new PresenceOf(array(
        'message' => 'The email is required'
    )));

//Filter any extra space
$validation->setFilters('name', 'trim');
$validation->setFilters('email', 'trim');

Filtering and sanitizing is performed using the filter: component. You can add more filters to this component or use the built-in ones.

验证事件(Validation Events)
When validations are organized in classes, you can implement the ‘beforeValidation’ and ‘afterValidation’ methods to perform additional checks, filters, clean-up, etc. If ‘beforeValidation’ method returns false the validation is automatically cancelled:
如果beforeValidation’方法返回false,验证器自动取消:

<?php

use Phalcon\Validation;

class LoginValidation extends Validation
{

    public function initialize()
    {
        // ...
    }

    /**
     * Executed before validation
     *
     * @param array $data
     * @param object $entity
     * @param Phalcon\Validation\Message\Group $messages
     * @return bool
     */
    public function beforeValidation($data, $entity, $messages)
    {
        if ($this->request->getHttpHost() != 'admin.mydomain.com') {
            $messages->appendMessage(new Message('Only users can log on in the administration domain'));
            return false;
        }
        return true;
    }

    /**
     * Executed after validation
     *
     * @param array $data
     * @param object $entity
     * @param Phalcon\Validation\Message\Group $messages
     */
    public function afterValidation($data, $entity, $messages)
    {
        //... add additional messages or perform more validations
    }

}

取消验证(Cancelling Validations)
By default all validators assigned to a field are tested regardless if one of them have failed or not. You can change this behavior by telling the validation component which validator may stop the validation:
默认所有分配到字段的验证器总被测试,不管他们是否有验证失败。你可以改变这个行为通过告诉验证组件哪个验证器肯可以停止验证:

<?php

use Phalcon\Validation\Validator\PresenceOf,
    Phalcon\Validation\Validator\Regex;

$validation = new Phalcon\Validation();

$validation
    ->add('telephone', new PresenceOf(array(
        'message' => 'The telephone is required',
        'cancelOnFail' => true
    )))
    ->add('telephone', new Regex(array(
        'message' => 'The telephone is required',
        'pattern' => '/\+44 [0-9]+/'
    )))
    ->add('telephone', new StringLength(array(
        'minimumMessage' => 'The telephone is too short',
        'min' => 2
    )));

The first validator has the option ‘cancelOnFail’ with a value of true, therefore if that validator fails the remaining validators in the chain are not executed.
第一个验证器有一个叫‘cancelOnFail’选项为true,如果这个验证器失败,它之后的在链条中的验证器都不会执行。

If you are creating custom validators you can dynamically stop the validation chain by setting the ‘cancelOnFail’ option:

<?php

use Phalcon\Validation\Validator,
    Phalcon\Validation\ValidatorInterface,
    Phalcon\Validation\Message;

class MyValidator extends Validator implements ValidatorInterface
{

    /**
     * Executes the validation
     *
     * @param Phalcon\Validation $validator
     * @param string $attribute
     * @return boolean
     */
    public function validate($validator, $attribute)
    {
        // If the attribute value is name we must stop the chain
        if ($attribute == 'name') {
            $validator->setOption('cancelOnFail', true);
        }

        //...
    }

}

PHP框架Phalcon 之 闪存消息 Session

闪存消息(Flashing Messages)
Flash messages are used to notify the user about the state of actions he/she made or simply show information to the users. These kind of messages can be generated using this component.

适配器(Adapters)
This component makes use of adapters to define the behavior of the messages after being passed to the Flasher:

Adapter Description API
Direct Directly outputs the messages passed to the flasher Phalcon\Flash\Direct
Session Temporarily stores the messages in session, then messages can be printed in the next request Phalcon\Flash\Session

使用(Usage)
Usually the Flash Messaging service is requested from the services container, if you’re using Phalcon\DI\FactoryDefault then Phalcon\Flash\Direct is automatically registered as “flash” service:

<?php

//Set up the flash service
$di->set('flash', function() {
    return new \Phalcon\Flash\Direct();
});

This way, you can use it in controllers or views by injecting the service in the required scope:

<?php

class PostsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function saveAction()
    {
        $this->flash->success("The post was correctly saved!");
    }

There are four built-in message types supported:
有4个内置消息类型:

<?php

$this->flash->error("too bad! the form had errors");
$this->flash->success("yes!, everything went very smoothly");
$this->flash->notice("this a very important information");
$this->flash->warning("best check yo self, you're not looking too good.");

You can add messages with your own types:
注意,可以通过调用message()方法建立一种新消息类型:

<?php

$this->flash->message("debug", "this is debug message, you don't say");

输出信息(Printing Messages)
Messages sent to the flash service are automatically formatted with html:

<div class="errorMessage">too bad! the form had errors</div>
<div class="successMessage">yes!, everything went very smoothly</div>
<div class="noticeMessage">this a very important information</div>
<div class="warningMessage">best check yo self, you're not looking too good.</div>

As you can see, CSS classes are added automatically to the DIVs. These classes allow you to define the graphical presentation of the messages in the browser. The CSS classes can be overridden, for example, if you’re using Twitter bootstrap, classes can be configured as:

<?php

//Register the flash service with custom CSS classes
$di->set('flash', function(){
    $flash = new \Phalcon\Flash\Direct(array(
        'error' => 'alert alert-error',
        'success' => 'alert alert-success',
        'notice' => 'alert alert-info',
    ));
    return $flash;
});

(可以做有限定制)
Then the messages would be printed as follows:

<div class="alert alert-error">too bad! the form had errors</div>
<div class="alert alert-success">yes!, everything went very smoothly</div>
<div class="alert alert-info">this a very important information</div>

绝对刷送与会话(Implicit Flush vs. Session)
Depending on the adapter used to send the messages, it could be producing output directly, or be temporarily storing the messages in session to be shown later. When should you use each? That usually depends on the type of redirection you do after sending the messages. For example, if you make a “forward” is not necessary to store the messages in session, but if you do a HTTP redirect then, they need to be stored in session:
(这段说应用场景)

<?php

class ContactController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function saveAction()
    {

        //store the post

        //Using direct flash
        $this->flash->success("Your information was stored correctly!");

        //Forward to the index action
        return $this->dispatcher->forward(array("action" => "index"));
    }

}

Or using a HTTP redirection:

<?php

class ContactController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function saveAction()
    {

        //store the post

        //Using session flash
        $this->flashSession->success("Your information was stored correctly!");

        //Make a full HTTP redirection
        return $this->response->redirect("contact/index");
    }

}

In this case you need to manually print the messages in the corresponding view:
这样你需要手动在相关视图中打印消息:

<!-- app/views/contact/index.phtml -->

<p><?php $this->flashSession->output() ?></p>

The attribute ‘flashSession’ is how the flash was previously set into the dependency injection container. You need to start the session first to successfully use the flashSession messenger.
属性flashSession跟之前在DI中设置flash一样。在使用flashSession消息器之前你要首先开启会话。

一般会话在用到时自动开启:

//在DI中设置session,让其在使用到session时启动并返回实例
$di['session'] = function () {
    $session = new SessionAdapter();
    $session->start();

    return $session;
};

以下是会话存储内容:
————————————————
测试程序:

    public function indexAction()
    {
        //Session do not start, so $_SESSION do not exists
        //print_r($_SESSION);

        $this->persistent->name = "index controller.";

        $user       = new \Phalcon\Session\Bag('user');
        $user->setDI($this->getDI());
        $user->name = "Kimbra Johnson";
        $user->age  = 22;

        //Session have start.
        print_r($_SESSION);
        print_r($this->session);

        echo $this->session->getId();
        $this->view->disable();
    }
//输出
Array
(
    [user] => Array
        (
            [name] => Kimbra Johnson
            [age] => 22
        )

    [Vf\Frontend\Controllers\IndexController] => Array
        (
            [name] => index controller.
        )

    [ifeelineVf\Frontend\Controllers\IndexController] => Array
        (
            [name] => index controller.
        )

    [ifeelineuser] => Array
        (
            [name] => Kimbra Johnson
            [age] => 22
        )

)
Phalcon\Session\Adapter\Files Object
(
    [_uniqueId:protected] => ifeeline
    [_started:protected] => 1
    [_options:protected] => Array
        (
            [uniqueId] => ifeeline
        )

)
ducm4na6838kdfvmu1f5jdhls0

从输出来看,所谓的分组,不过是$_SESSION中设置一个变量名称执行一个数组,Bag就是这样的一个变量。组件中的persistent就是DI资源的persistent,它是一个Bag对象,如果在组件中设置了persistent,那么对应到$_SESSION时使用组件的完整名作为下标,所以这个组的数据就显得只有这个组件才能使用。另外如果设置了uniqueId,它就在原有基础上复制一遍然后添加前缀。

找不到修改会话ID对应名称修改的封装(这个名称实际无关痛痒,默认使用php.ini的默认设置),也没有修改传送会话ID的cookie的封装,基本上,Phalcon中提供的Session封装,相对单薄了一些。

PHP的Session可以参考:http://blog.ifeeline.com/300.html

Phalcon 1.3.x中提供了一个叫Phalcon\Session\Adapter\Libmemcached的适配器(类似Phalcon\Session\Adapter\Memcache),可以通过它把会话数据保存到Memcached中:

//官方代码给出的例子
	 * $session = new Phalcon\Session\Adapter\Libmemcached(array(
	 *     'servers' => array(
	 *         array('host' => 'localhost', 'port' => 11211, 'weight' => 1),
	 *     ),
	 *     'client' => array(
	 *         Memcached::OPT_HASH => Memcached::HASH_MD5,
	 *         Memcached::OPT_PREFIX_KEY => 'prefix.',
	 *     ),
	 *    'lifetime' => 3600,
	 *    'prefix' => 'my_'
	 * ));
	 *
	 * $session->start();
	 *
	 * $session->set('var', 'some-value');
	 *
	 * echo $session->get('var');

另外,我们知道如要把会话数据放到其它介质,可以通过在php.ini中配置session.save_handler = files|mm|sqlite|user指令(user表示使用自定义函数),还可以通过执行session_set_save_handler()函数来修改会话的保存和获取的逻辑。它要实现一系列函数,也是会话适配器的要实现的方法,可以查看https://github.com/phalcon/incubator/blob/master/Library/Phalcon/Session/Adapter/Database.php的实现方法:

    public function __construct($options = null)
    {
        //......

        parent::__construct($options);

        session_set_save_handler(
            array($this, 'open'),
            array($this, 'close'),
            array($this, 'read'),
            array($this, 'write'),
            array($this, 'destroy'),
            array($this, 'gc')
        );
    }

———————————————-

使用 Session 存储数据(Storing data in Session)
The Phalcon\Session provides object-oriented wrappers to access session data.

Reasons to use this component instead of raw-sessions:
使用这个组件替换原生的session的原因:
You can easily isolate session data across applications on the same domain
Intercept where session data is set/get in your application
Change the session adapter according to the application need 随时改变会话适配器

启动会话(Starting the Session)
Some applications are session-intensive, almost any action that performs requires access to session data. There are others who access session data casually. Thanks to the service container, we can ensure that the session is accessed only when it’s clearly needed:
用到时才启用:

<?php

//Start the session the first time when some component request the session service
$di->setShared('session', function() {
    $session = new Phalcon\Session\Adapter\Files();
    $session->start();
    return $session;
});

Session 的存储与读取(Storing/Retrieving data in Session)
From a controller, a view or any other component that extends Phalcon\DI\Injectable you can access the session service and store items and retrieve them in the following way:

<?php

class UserController extends Phalcon\Mvc\Controller
{

    public function indexAction()
    {
        //Set a session variable
        $this->session->set("user-name", "Michael");
    }

    public function welcomeAction()
    {

        //Check if the variable is defined
        if ($this->session->has("user-name")) {

            //Retrieve its value
            $name = $this->session->get("user-name");
        }
    }

}

Sessions 的删除和销毁(Removing/Destroying Sessions)
It’s also possible remove specific variables or destroy the whole session:

<?php

class UserController extends Phalcon\Mvc\Controller
{

    public function removeAction()
    {
        //Remove a session variable
        $this->session->remove("user-name");
    }

    public function logoutAction()
    {
        //Destroy the whole session
        $this->session->destroy();
    }

}

隔离不同应用的会话数据(Isolating Session Data between Applications)
Sometimes a user can use the same application twice, on the same server, in the same session. Surely, if we use variables in session, we want that every application have separate session data (even though the same code and same variable names). To solve this, you can add a prefix for every session variable created in a certain application:
为特定的应用对每个创建的会话变量添加一个前缀:

<?php

//Isolating the session data
$di->set('session', function(){

    //All variables created will prefixed with "my-app-1"
    $session = new Phalcon\Session\Adapter\Files(
        array(
            'uniqueId' => 'my-app-1'
        )
    );

    $session->start();

    return $session;
});

会话袋(Session Bags)
Phalcon\Session\Bag is a component that helps separating session data into “namespaces”. Working by this way you can easily create groups of session variables into the application. By only setting the variables in the “bag”, it’s automatically stored in session:
为会话变量分组,只要把变量放入袋子中,它自动在会话中存储:

<?php

$user       = new Phalcon\Session\Bag('user');
$user->setDI($di);
$user->name = "Kimbra Johnson";
$user->age  = 22;

组件的持久数据(Persistent Data in Components)
Controller, components and classes that extends Phalcon\DI\Injectable may inject a Phalcon\Session\Bag. This class isolates variables for every class. Thanks to this you can persist data between requests in every class in an independent way.
Phalcon\Session\Bag对应的DI资源名称是persistent。它用来为每个类隔离变量。

<?php

class UserController extends Phalcon\Mvc\Controller
{

    public function indexAction()
    {
        // Create a persistent variable "name"
        $this->persistent->name = "Laura";
    }

    public function welcomeAction()
    {
        if (isset($this->persistent->name))
        {
            echo "Welcome, ", $this->persistent->name;
        }
    }

}

In a component:

<?php

class Security extends Phalcon\Mvc\User\Component
{

    public function auth()
    {
        // Create a persistent variable "name"
        $this->persistent->name = "Laura";
    }

    public function getAuthName()
    {
        return $this->persistent->name;
    }

}

The data added to the session ($this->session) are available throughout the application, while persistent ($this->persistent) can only be accessed in the scope of the current class.
$this->session中设置的数据是在整个应用中都有可用的,而$this->persistent只能在当前类的作用域内被访问到。

自定义适配器(Implementing your own adapters)
The Phalcon\Session\AdapterInterface interface must be implemented in order to create your own session adapters or extend the existing ones.

There are more adapters available for this components in the Phalcon Incubator

(以上关于SESSION的内容是远远不够的)