多语言支持

最后更新于:2022-04-02 05:15:49

[TOC] # 多语言支持 `Phalcon\Translate` 组件有助于创建多语言应用程序。使用此组件的应用程序,根据应用程序支持的用户所选语言显示不同语言的内容。 ## 适配器 该组件使用适配器以统一的方式读取来自不同源的翻译消息。 | 适配器 | 描述 | | ------------------------------------------ | --------------------------------------------------------------------------------------- | | `Phalcon\Translate\Adapter\NativeArray` | 使用PHP数组来存储消息。这是性能方面的最佳选择。| ### 工厂 使用适配器选项加载Translate Adapter类 ```php 'de_DE.UTF-8', 'defaultDomain' => 'translations', 'directory' => '/path/to/application/locales', 'category' => LC_MESSAGES, 'adapter' => 'gettext', ]; $translate = Factory::load($options); ``` ## 使用组件 翻译字符串存储在文件中。这些文件的结构可能因使用的适配器而异。Phalcon让您可以自由地组织翻译字符串。一个简单的结构可能是: ```bash app/messages/en.php app/messages/es.php app/messages/fr.php app/messages/zh.php ``` 每个文件都以键/值的方式包含一系列翻译。对于每个翻译文件,密钥都是唯一的。在不同的文件中使用相同的数组,其中键保持不变,值包含取决于每种语言的翻译字符串。 ```php 'Hello', 'bye' => 'Good Bye', 'hi-name' => 'Hello %name%', 'song' => 'This song is %song%', ]; ``` ```php 'Bonjour', 'bye' => 'Au revoir', 'hi-name' => 'Bonjour %name%', 'song' => 'La chanson est %song%', ]; ``` 在您的应用程序中实现转换机制是微不足道的,但取决于您希望如何实现它。您可以使用用户浏览器中的语言自动检测,也可以提供用户可以选择语言的设置页面。 检测用户语言的一种简单方法是解析 `$_SERVER['HTTP_ACCEPT_LANGUAGE']` 内容,或者如果您愿意,可以通过从动作/控制器调用 `$this->request->getBestLanguage()` 来直接访问它: ```php request->getBestLanguage(); $messages = []; $translationFile = 'app/messages/' . $language . '.php'; // 检查我们是否有该lang的翻译文件 if (file_exists($translationFile)) { require $translationFile; } else { // 回退到某些默认值 require 'app/messages/en.php'; } // 返回翻译对象$messages来自上面的require语句 return new NativeArray( [ 'content' => $messages, ] ); } public function indexAction() { $this->view->name = 'Mike'; $this->view->t = $this->getTranslation(); } } ``` `_getTranslation()` 方法适用于需要翻译的所有操作。`$t` 变量传递给视图,有了它,我们可以翻译该层中的字符串: ```php

_('hi'), ' ', $name; ?>

``` `_()` 方法根据传递的索引返回已翻译的字符串。某些字符串需要包含计算数据的占位符,即`Hello %name%`。可以使用`_()`方法中的传递参数替换这些占位符。传递的参数采用 key/value 数组的形式,其中键与占位符名称匹配,值是要替换的实际数据: ```php

_('hi-name', ['name' => $name]); ?>

``` 一些应用程序在URL上实现多语言,例如 `http://www.mozilla.org/**es-ES**/firefox/`。Phalcon可以使用路由器实现这一点。 上面的实现很有帮助,但它需要一个基本控制器来实现 `_getTranslation()` 并返回 `Phalcon\Translate\Adapter\NativeArray` 组件。另外,需要在视图中设置组件,如上面 `$t` 变量中所示。 您始终可以将此功能包装在自己的类中,并在DI容器中注册该类: ```php request->getBestLanguage(); /** * 我们使用基于JSON的文件来存储翻译。 * 您需要检查文件是否存在! */ $translations = json_decode( file_get_contents('app/messages/' . $language . '.json'), true ); // 返回翻译对象$messages来自上面的require语句 return new NativeArray( [ 'content' => $translations, ] ); } } ``` 这样您就可以在控制器中使用该组件: ```php locale->_('hi-name', ['name' => $name]); $this->view->text = $text; } } ``` 或直接查看 ```php _('hi-name', ['name' => 'Mike']); ``` ## 实现自己的适配器 必须实现 `Phalcon\Translate\AdapterInterface` 接口才能创建自己的转换适配器或扩展现有转换适配器: ```php ';

国际化

最后更新于:2022-04-02 05:15:47

[TOC] # 国际化 Phalcon是用C语言编写的PHP的扩展。有一个[PECL](http://pecl.php.net/package/intl)扩展,称为`intl`的PHP应用程序提供国际化功能。从PHP 5.4 / 5.5开始,此扩展与PHP捆绑在一起。它的文档可以在官方PHP手册的页面中找到。 Phalcon不提供此功能,因为创建此类组件将复制现有代码。 在下面的示例中,我们将向您展示如何在Phalcon支持的应用程序中实现intl扩展的功能。 >[warning] 本指南不是 [intl](http://pecl.php.net/package/intl) 扩展的完整文档。 请访问该扩展程序的[文档](http://www.php.net/manual/en/book.intl.php) 以获取参考。 ## 找出最佳的Locale 有几种方法可以使用intl找出最佳的可用语言环境。 其中之一是检查HTTP `Accept-Language`标头: ```php format([4560]); // Prints USD$ 4,560.5 $formatter = new MessageFormatter('en_US', 'USD$ {0, number}'); echo $formatter->format([4560.50]); // Prints ARS$ 1.250,25 $formatter = new MessageFormatter('es_AR', 'ARS$ {0, number}'); echo $formatter->format([1250.25]); ``` 使用时间和日期模式格式化消息: ```php format($values); // 打印 'À 15:53:01 le 19 avr. 2015, il y avait une perturbation sur la planète 7.' $pattern = 'À {1, time} le {1, date}, il y avait une perturbation sur la planète {0, number}.'; $formatter = new MessageFormatter('fr_FR', $pattern); echo $formatter->format($values); ``` ## 区域敏感比较 Collator类提供字符串比较功能,支持适当的区域设置敏感排序顺序。请查看以下有关此类用法的示例: ```php setStrength(Collator::PRIMARY); var_dump($collator->compare('una canción', 'una cancion')); // 返回字符串不相等 $collator->setStrength(Collator::DEFAULT_VALUE); var_dump($collator->compare('una canción', 'una cancion')); ``` ## 音译 Transliterator提供字符串的音译: ```php transliterate($string); // garconetudiantoulecole ```
';

国际化

最后更新于:2022-04-02 05:15:45

- 国际化
';

安全

最后更新于:2022-04-02 05:15:42

[TOC] # 安全 该组件帮助开发人员执行常见的安全任务,例如密码散列和跨站请求伪造保护([CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery))。 ## Password Hashing 以纯文本格式存储密码是一种糟糕的安全措施。有权访问数据库的任何人都可以立即访问所有用户帐户,从而可以进行未经授权的活动。为了解决这个问题,许多应用程序使用熟悉的单向散列方法'[md5](http://php.net/manual/en/function.md5.php)'和'[sha1](http://php.net/manual/en/function.sha1.php)'。但是,硬件每天都会发展变得越来越快,这些算法越来越容易受到强力攻击。这些攻击也称为[彩虹表](http://en.wikipedia.org/wiki/Rainbow_table)。 为了解决这个问题,我们可以使用哈希算法作为[bcrypt](http://en.wikipedia.org/wiki/Bcrypt)。为什么要加密?由于其'[Eksblowfish](http://en.wikipedia.org/wiki/Bcrypt#Algorithm)'密钥设置算法,我们可以根据需要将密码加密设置为“慢”。慢速算法使得计算哈希背后的真实密码的过程非常困难,如果不是不可能的话。这将使用彩虹表保护您长时间免受可能的攻击。 该组件使您能够以一种简单的方式使用此算法: ```php request->getPost('login'); $password = $this->request->getPost('password'); $user->login = $login; // Store the password hashed $user->password = $this->security->hash($password); $user->save(); } } ``` 我们使用默认工作因子保存了密码哈希值。较高的工作因素会使密码不易受到攻击,因为加密速度会很慢。我们可以检查密码是否正确如下: ```php 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 } } else { // To protect against timing attacks. Regardless of whether a user // exists or not, the script will take roughly the same amount as // it will always be computing a hash. $this->security->hash(rand()); } // The validation has failed } } ``` 使用伪随机字节生成salt,其PHP函数为[openssl_random_pseudo_bytes](http://php.net/manual/en/function.openssl-random-pseudo-bytes.php),因此需要加载openssl扩展。 ## 跨站请求伪造(CSRF)保护 这是针对网站和应用程序的另一种常见攻击。用于执行用户注册或添加注释等任务的表单容易受到此攻击。 我们的想法是防止在我们的应用程序之外发送表单值。为了解决这个问题,我们在每个表单中生成一个随机的[随机数](http://en.wikipedia.org/wiki/Cryptographic_nonce)(令牌),在会话中添加令牌,然后在表单通过将会话中存储的令牌与会话中提交的令牌进行比较后将数据发布回应用程序时验证令牌。形成: ```php ``` 然后在控制器的操作中,您可以检查CSRF令牌是否有效: ```php request->isPost()) { if ($this->security->checkToken()) { // The token is OK } } } } ``` 请记住将会话适配器添加到Dependency Injector,否则令牌检查将不起作用: ```php setShared( 'session', function () { $session = new \Phalcon\Session\Adapter\Files(); $session->start(); return $session; } ); ``` 建议在表单中添加[验证码](http://www.google.com/recaptcha)以完全避免此攻击的风险。 ## 设置组件 此组件作为`security`自动注册在服务容器中,您可以重新注册它以设置其选项: ```php set( 'security', function () { $security = new Security(); // Set the password hashing factor to 12 rounds $security->setWorkFactor(12); return $security; }, true ); ``` ## 随机数 `Phalcon\Security\Random`类使得生成许多类型的随机数据变得非常容易。 ```php bytes(); // Generate a random hex string of length $len. $hex = $random->hex($len); // Generate a random base64 string of length $len. $base64 = $random->base64($len); // Generate a random URL-safe base64 string of length $len. $base64Safe = $random->base64Safe($len); // Generate a UUID (version 4). // See https://en.wikipedia.org/wiki/Universally_unique_identifier $uuid = $random->uuid(); // Generate a random integer between 0 and $n. $number = $random->number($n); ``` ## 外部资源 * [Vökuró](https://vokuro.phalconphp.com)是一个示例应用程序,它使用安全组件来避免CSRF和密码散列,[Github](https://github.com/phalcon/vokuro)
';

加密/解密

最后更新于:2022-04-02 05:15:40

[TOC] # 加密/解密 Phalcon通过`Phalcon\Crypt`组件提供加密功能。该类为[openssl](http://www.php.net/manual/en/book.openssl.php) PHP的加密库提供了简单的面向对象的包装器。 默认情况下,此组件使用AES-256-CFB提供安全加密。 密码AES-256用于Internet上的SSL/TLS中的其他位置。它被认为是顶级密码之一。理论上它是不可破解的,因为密钥的组合是巨大的。尽管NSA已将此分类在[Suite B](https://en.wikipedia.org/wiki/NSA_Suite_B_Cryptography)中,但他们还建议使用高于128位的密钥进行加密。 >[warning] 您必须使用与当前算法相对应的密钥长度。对于默认使用的算法,它是32个字节。 如果在对象构造期间未选择用于计算摘要(签名)的算法,则默认选择aes-256-cfb。 ## 基础使用 该组件设计非常简单易用: ```php setCipher('aes-256-ctr'); /** * Set the encryption key. * * The `$key' should have been previously generated in a cryptographically safe way. * * Bad key: * "le password" * * Better (but still unsafe): * "#1dj8$=dp?.ak//j1V$~%*0X" * * Good key: * "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3" * * Use your own key. Do not copy and paste this example key. */ $key = "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"; $text = 'This is the text that you want to encrypt.'; $encrypted = $crypt->encrypt($text, $key); echo $crypt->decrypt($encrypted, $key); ``` 您还可以设置算法以及在对象构造期间是否计算消息的摘要(签名)。这消除了调用`setCipher()`和`useSigning()`的需要: ```php encrypt($text, $key); echo $crypt->decrypt($encrypted, $key); ``` 您可以使用相同的实例多次加密/解密: ```php setCipher('aes-256-ctr'); // Create an instance $crypt = new Crypt(); // Use your own keys! $texts = [ "T4\xb1\x8d\xa9\x98\x054t7w!z%C*F-Jk\x98\x05\\\x5c" => 'This is a secret text', "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3" => '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); } ``` 为了更好的安全性,您可以指示组件根据 `getAvailableHashAlgos` 返回的受支持算法之一计算消息摘要。如上所示,该算法可以在对象实例化期间设置,但也可以在之后设置。 **注意** 默认情况下,Phalcon 4.0.0或更高版本将启用计算消息摘要(签名)。 ```php setCipher('aes-256-ctr'); $crypt->setHashAlgo('aes-256-cfb'); // Force calculation of a digest of the message based on the Hash algorithm $crypt->useSigning(true); $key = "T4\xb1\x8d\xa9\x98\x054t7w!z%C*F-Jk\x98\x05\\\x5c"; $text = 'This is a secret text'; // Perform the encryption $encrypted = $crypt->encrypt($text, $key); // Now decrypt echo $crypt->decrypt($encrypted, $key); ``` ## 加密选项 以下选项可用于更改加密行为: | 名称 | 描述 | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Cipher | 密码是openssl支持的加密算法之一。你可以在[这里](http://www.php.net/manual/en/function.openssl-get-cipher-methods.php)看到一个列表| 案例: ```php setCipher('bf-cbc'); // Use your own key! $key = "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"; $text = 'This is a secret text'; echo $crypt->encrypt($text, $key); ``` 如果要检查系统支持的可用算法,可以调用 `getAvailableHashAlgos()` 方法。 ```php getAvailableHashAlgos(); var_dump($algorithms); ``` ## Base64支持 为了正确传输(电子邮件)或显示(浏览器)加密,[base64](http://www.php.net/manual/en/function.base64-encode.php)编码通常应用于加密文本: ```php encryptBase64($text, $key); echo $crypt->decryptBase64($encrypt, $key); ``` ## 设置加密服务 您可以在服务容器中设置加密组件,以便从应用程序的任何部分使用它: ```php set( 'crypt', function () { $crypt = new Crypt(); // Set a global encryption key $crypt->setKey( "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3" ); return $crypt; }, true ); ``` 然后,例如,在控制器中,您可以按如下方式使用它: ```php request->getPost('text'); $secret->content = $this->crypt->encrypt($text); if ($secret->save()) { $this->flash->success( 'Secret was successfully created!' ); } } } ``` ## 链接 * [Advanced Encryption Standard (AES)](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) * [What is block cipher](https://en.wikipedia.org/wiki/Block_cipher) * [Introduction to Blowfish](http://www.splashdata.com/splashid/blowfish.htm) * [CTR-Mode Encryption](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.79.1353&rep=rep1&type=pdf) * [Recommendation for Block Cipher Modes of Operation: Methods and Techniques](https://csrc.nist.gov/publications/detail/sp/800-38a/final) * [Counter (CTR) mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29)
';

安全

最后更新于:2022-04-02 05:15:38

[加密/解密](crypt.md) [安全](security.md)
';

返回响应

最后更新于:2022-04-02 05:15:36

[TOC] # 返回响应 HTTP周期的一部分是返回对客户端的响应。`Phalcon\Http\Response`是为实现此任务而设计的Phalcon组件。HTTP响应通常由标题和正文组成。以下是基本用法的示例: ```php setStatusCode(404, 'Not Found'); // Set the content of the response $response->setContent("Sorry, the page doesn't exist"); // Send response to the client $response->send(); ``` 如果您使用完整的MVC堆栈,则无需手动创建响应。但是,如果您需要直接从控制器的操作返回响应,请遵循以下示例: ```php setContent( $feed->asString() ); // Return the response return $response; } } ``` ## 使用Headers Headers是HTTP响应的重要部分。它包含有关响应状态的有用信息,如HTTP状态,响应类型等等。 您可以通过以下方式设置headers : ```php setHeader('Content-Type', 'application/pdf'); $response->setHeader('Content-Disposition', "attachment; filename='downloaded.pdf'"); // Setting a raw header $response->setRawHeader('HTTP/1.1 200 OK'); ``` `Phalcon\Http\Response\Headers` 包内部管理标头。 此类在将标头发送到客户端之前检索标头: ```php getHeaders(); // Get a header by its name $contentType = $headers->get('Content-Type'); ``` ## 执行重定向 使用`Phalcon\Http\Response`,您还可以执行HTTP重定向: ```php redirect(); // Redirect to the local base URI $response->redirect('posts/index'); // Redirect to an external URL $response->redirect('http://en.wikipedia.org', true); // Redirect specifying the HTTP status code $response->redirect('http://www.example.com/new-location', true, 301); ``` 所有内部URI都是使用`url`服务生成的(默认为`Phalcon\Mvc\Url`)。此示例演示了如何使用您在应用程序中定义的路由重定向: ```php redirect( [ 'for' => 'index-lang', 'lang' => 'jp', 'controller' => 'index', ] ); ``` 请注意,重定向不会禁用视图组件,因此如果存在与当前操作关联的视图,则无论如何都将执行该视图。您可以通过执行`$this->view->disable()`来禁用控制器中的视图。 ## HTTP缓存 提高应用程序性能和减少流量的最简单方法之一是使用HTTP缓存。大多数现代浏览器都支持HTTP缓存,这也是许多网站目前速度很快的原因之一。 在第一次提供页面时,应用程序发送的以下header值可以更改HTTP缓存: * **`Expires:`** 使用此标头,应用程序可以设置将来或过去的日期,告诉浏览器何时页面必须到期。 * **`Cache-Control:`** 此标头允许指定页面在浏览器中应被视为新鲜的时间。 * **`Last-Modified:`** 此标头告诉浏览器最后一次更新网站,避免重新加载页面。 * **`ETag:`** etag是必须创建的唯一标识符,包括当前页面的修改时间戳。 ### 设置过期时间 过期时间是在客户端(浏览器)中缓存页面的最简单且最有效的方法之一。从当前日期开始,我们添加页面将存储在浏览器缓存中的时间。在此日期到期之前,不会从服务器请求新内容: ```php modify('+2 months'); $response->setExpires($expiryDate); ``` 响应组件在Expires标头中按预期自动显示GMT时区中的日期。 如果我们将此值设置为过去的日期,则浏览器将始终刷新请求的页面: ```php modify('-10 minutes'); $response->setExpires($expiryDate); ``` 浏览器依靠客户端的时钟来评估此日期是否已过。可以修改客户端时钟以使页面过期,这可能代表对此高速缓存机制的限制。 ### Cache-Control 此标头提供了一种更安全的方式来缓存所服务的页面。我们必须指定一个时间(以秒为单位)告诉浏览器它必须将页面保留在其缓存中多长时间: ```php setHeader('Cache-Control', 'max-age=86400'); ``` 以这种方式实现相反的效果(避免页面缓存): ```php setHeader('Cache-Control', 'private, max-age=0, must-revalidate'); ``` ### E-Tag `entity-tag`或`E-tag`是一种唯一标识符,可帮助浏览器实现页面在两个请求之间是否发生了变化。必须计算标识符,并考虑到如果先前提供的内容已更改,则必须更改标识符: ```php 'created_at' ] ); $eTag = md5($mostRecentDate); // Send an E-Tag header $response->setHeader('E-Tag', $eTag); ```
';

请求环境

最后更新于:2022-04-02 05:15:33

[TOC] # 请求环境 每个HTTP请求(通常由浏览器发起)包含有关请求的附加信息,例如标题数据,文件,变量等。基于Web的应用程序需要解析该信息,以便向请求者提供正确的响应。`Phalcon\Http\Request`封装了请求的信息,允许您以面向对象的方式访问它。 ```php isPost()) { // Check whether the request was made with Ajax if ($request->isAjax()) { echo 'Request was made using POST and AJAX'; } } ``` ## 获取值 PHP根据请求的类型自动填充超全局数组`$_GET`和`$_POST`。这些数组包含提交的表单中存在的值或通过URL发送的参数。数组中的变量永远不会被清理,并且可能包含非法字符甚至恶意代码,这可能导致[SQL注入](http://en.wikipedia.org/wiki/SQL_injection)或[跨站点脚本(XSS)](http://en.wikipedia.org/wiki/Cross-site_scripting)攻击。 `Phalcon\Http\Request` 允许您访问存储在`$_REQUEST`,`$_GET`和`$_POST`数组中的值,并使用过滤器服务(默认为Phalcon \ Filter)清理或过滤它们。以下示例提供相同的行为: ```php sanitize($_POST['user_email'], 'email'); // Manually applying the filter to the value $email = $filter->sanitize($request->getPost('user_email'), 'email'); // Automatically applying the filter $email = $request->getPost('user_email', 'email'); // Setting a default value if the param is null $email = $request->getPost('user_email', 'email', 'some@example.com'); // Setting a default value if the param is null without filtering $email = $request->getPost('user_email', null, 'some@example.com'); ``` ## 访问控制器的请求 访问请求环境的最常见位置是控制器的操作。要从控制器访问`Phalcon\Http\Request`对象,您需要使用控制器的 `$this->request`公共属性: ```php request->isPost()) { // Access POST data $customerName = $this->request->getPost('name'); $customerBorn = $this->request->getPost('born'); } } } ``` ## 上传文件 另一个常见任务是文件上传。`Phalcon\Http\Request`提供了一种面向对象的方式来完成这项任务: ```php request->hasFiles()) { $files = $this->request->getUploadedFiles(); // Print the real file names and sizes foreach ($files as $file) { // Print file details echo $file->getName(), ' ', $file->getSize(), '\n'; // Move the file into the application $file->moveTo( 'files/' . $file->getName() ); } } } } ``` `Phalcon\Http\Request::getUploadedFiles()`返回的每个对象都是`Phalcon\Http\Request\File`类的实例。使用`$_FILES` 超全局数组提供相同的行为。`Phalcon\Http\Request\File`仅封装与请求一起上载的每个文件相关的信息。 ## 使用Headers 如上所述,Headers包含有用的信息,允许我们将适当的响应发送回用户。以下示例显示了该信息的用法: ```php getHeader('HTTP_X_REQUESTED_WITH'); if ($requestedWith === 'XMLHttpRequest') { echo 'The request was made with Ajax'; } // Same as above if ($request->isAjax()) { echo 'The request was made with Ajax'; } // Check the request layer if ($request->isSecure()) { echo 'The request was made using a secure layer'; } // Get the servers's IP address. ie. 192.168.0.100 $ipAddress = $request->getServerAddress(); // Get the client's IP address ie. 201.245.53.51 $ipAddress = $request->getClientAddress(); // Get the User Agent (HTTP_USER_AGENT) $userAgent = $request->getUserAgent(); // Get the best acceptable content by the browser. ie text/xml $contentType = $request->getAcceptableContent(); // Get the best charset accepted by the browser. ie. utf-8 $charset = $request->getBestCharset(); // Get the best language accepted configured in the browser. ie. en-us $language = $request->getBestLanguage(); // Check if a header exists if ($request->hasHeader('my-header')) { echo "Mary had a little lamb"; } ``` ## 事件 使用HTTP授权时,`Authorization`标头具有以下格式: ```text Authorization: ``` 其中 `` 是一种身份验证类型。常见的类型是 `Basic`。其他身份验证类型在身份验证方案的[IANA注册表](http://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml)和[AWS服务器的身份验证(AWS4-HMAC-SHA256)](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html)中进行了描述。在99.99%的用例中,身份验证类型为: * `AWS4-HMAC-SHA256` * `Basic` * `Bearer` * `Digest` * `HOBA` * `Mutual` * `Negotiate` * `OAuth` * `SCRAM-SHA-1` * `SCRAM-SHA-256` * `vapid` 您可以使用`request:beforeAuthorizationResolve`和`request:afterAuthorizationResolve` 事件在授权解析之前或之后执行其他操作。需要自定义授权解析程序。 不使用自定义授权解析程序的示例: ```php getHeaders()); ``` 结果: ```bash Array ( [Authorization] => Enigma Secret ) Type: Enigma Credentials: Secret ``` 使用自定义授权解析程序的示例: ```php $data['server']['CUSTOM_KERBEROS_AUTH'], ]; } } $_SERVER['CUSTOM_KERBEROS_AUTH'] = 'Negotiate a87421000492aa874209af8bc028'; $di = new Di(); $di->set('eventsManager', function () { $manager = new Manager(); $manager->attach('request', new NegotiateAuthorizationListener()); return $manager; }); $request = new Request(); $request->setDI($di); print_r($request->getHeaders()); ``` 结果: ```bash Array ( [Authorization] => Negotiate a87421000492aa874209af8bc028 ) Type: Negotiate Credentials: a87421000492aa874209af8bc028 ```
';

Cookies管理

最后更新于:2022-04-02 05:15:31

[TOC] # Cookies管理 [Cookies](http://en.wikipedia.org/wiki/HTTP_cookie) 是在客户端计算机上存储小块数据的非常有用的方法,即使用户关闭他/她的浏览器也可以检索这些数据。`Phalcon\Http\Response\Cookies` a充当cookie的全局包。Cookie在请求执行期间存储在此包中,并在请求结束时自动发送。 ## 基本用法 您可以通过访问可以访问服务的应用程序的任何部分中的`cookie`服务来设置/获取cookie: ```php cookies->has('remember-me')) { // Get the cookie $rememberMeCookie = $this->cookies->get('remember-me'); // Get the cookie's value $value = $rememberMeCookie->getValue(); } } public function startAction() { $this->cookies->set( 'remember-me', 'some value', time() + 15 * 86400 ); $this->cookies->send(); } public function logoutAction() { $rememberMeCookie = $this->cookies->get('remember-me'); // Delete the cookie $rememberMeCookie->delete(); } } ``` ## Cookie的加密/解密 默认情况下,cookie在发送到客户端之前会自动加密,并在从用户检索时解密。此保护可防止未经授权的用户在客户端(浏览器)中查看cookie的内容。尽管有这种保护,敏感数据不应存储在cookie中。 您可以按如下方式禁用加密: ```php set( 'cookies', function () { $cookies = new Cookies(); $cookies->useEncryption(false); return $cookies; } ); ``` 如果要使用加密,则必须在加密服务中设置全局密钥: ```php set( 'crypt', function () { $crypt = new Crypt(); /** * Set the cipher algorithm. * * The `aes-256-gcm' is the preferable cipher, but it is not usable until the * openssl library is upgraded, which is available in PHP 7.1. * * The `aes-256-ctr' is arguably the best choice for cipher * algorithm in these days. */ $crypt->setCipher('aes-256-ctr'); /** * Setting the encryption key. * * The key should have been previously generated in a cryptographically safe way. * * Bad key: * "le password" * * Better (but still unsafe): * "#1dj8$=dp?.ak//j1V$~%*0X" * * Good key: * "T4\xb1\x8d\xa9\x98\x054t7w!z%C*F-Jk\x98\x05\\\x5c" * * Use your own key. Do not copy and paste this example key. */ $key = "T4\xb1\x8d\xa9\x98\x054t7w!z%C*F-Jk\x98\x05\\\x5c"; $crypt->setKey($key); return $crypt; } ); ``` >[danger] 向客户端发送不加密的cookie数据(包括复杂对象结构,结果集,服务信息等)可能会暴露攻击者可能用来攻击应用程序的内部应用程序详细信息。如果您不想使用加密,我们强烈建议您只发送非常基本的cookie数据,如数字或小字符串文字。
';

HTTP

最后更新于:2022-04-02 05:15:29

[Cookies管理](cookies.md) [请求环境](request.md) [返回响应](response.md)
';

验证

最后更新于:2022-04-02 05:15:27

[TOC] # 验证 `Phalcon\Validation` 是一个独立的验证组件,用于验证任意数据集。此组件可用于对不属于模型或集合的数据对象实现验证规则。 以下示例显示了其基本用法: ```php add( 'name', new PresenceOf( [ 'message' => 'The name is required', ] ) ); $validation->add( 'email', new PresenceOf( [ 'message' => 'The e-mail is required', ] ) ); $validation->add( 'email', new Email( [ 'message' => 'The e-mail is not valid', ] ) ); $messages = $validation->validate($_POST); if (count($messages)) { foreach ($messages as $message) { echo $message, '
'; } } ``` 此组件的松耦合设计允许您以框架提供的验证器创建自己的验证器。 ## 初始化验证 只需在`Phalcon\Validation`对象中添加验证器,即可直接初始化验证链。您可以将验证放在单独的文件中,以便更好地重用代码和组织: ```php add( 'name', new PresenceOf( [ 'message' => 'The name is required', ] ) ); $this->add( 'email', new PresenceOf( [ 'message' => 'The e-mail is required', ] ) ); $this->add( 'email', new Email( [ 'message' => 'The e-mail is not valid', ] ) ); } } ``` 然后初始化并使用您自己的验证器: ```php validate($_POST); if (count($messages)) { foreach ($messages as $message) { echo $message, '
'; } } ``` ## 验证器 Phalcon为此组件公开了一组内置验证器: | 类 | 说明 | | ---------------------------------------------- | ------------------------------------------------------------------ | | `Phalcon\Validation\Validator\Alnum` | 验证字段的值仅为字母数字字符。 | | `Phalcon\Validation\Validator\Alpha` | 验证字段的值是否仅为字母字符。 | | `Phalcon\Validation\Validator\Date` | 验证字段的值是否为有效日期。 | | `Phalcon\Validation\Validator\Digit` | 验证字段的值是否只是数字字符。 | | `Phalcon\Validation\Validator\File` | 验证字段的值是否是正确的文件。 | | `Phalcon\Validation\Validator\Uniqueness` | 验证字段的值在相关模型中是唯一的。 | | `Phalcon\Validation\Validator\Numericality` | 验证字段的值是否为有效数值。 | | `Phalcon\Validation\Validator\PresenceOf` | 验证字段的值不为null或空字符串。 | | `Phalcon\Validation\Validator\Identical` | 验证字段的值是否与指定值相同。 | | `Phalcon\Validation\Validator\Email` | 验证该字段是否包含有效的电子邮件格式。 | | `Phalcon\Validation\Validator\ExclusionIn` | 验证值是否不在可能值列表中。 | | `Phalcon\Validation\Validator\InclusionIn` | 验证值是否在可能值列表中。 | | `Phalcon\Validation\Validator\Regex` | 验证字段的值是否与正则表达式匹配。 | | `Phalcon\Validation\Validator\StringLength` | 验证字符串的长度。 | | `Phalcon\Validation\Validator\Between` | 验证值是否在两个值之间。 | | `Phalcon\Validation\Validator\Confirmation` | 验证值与数据中存在的另一个值相同。 | | `Phalcon\Validation\Validator\Url` | 验证该字段是否包含有效的URL。 | | `Phalcon\Validation\Validator\CreditCard` | 验证信用卡号。 | | `Phalcon\Validation\Validator\Callback` | 使用回调函数进行验证。 | 以下示例说明如何为此组件创建其他验证器: ```php 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; } } ``` 验证器返回有效的布尔值非常重要,该值指示验证是否成功。 ## 回调验证器 通过使用`Phalcon\Validation\Validator\Callback`,您可以执行自定义函数,该函数必须返回将用于验证同一字段的布尔值或新验证器类。通过返回真正的验证将成功,返回`false`将意味着验证失败。执行此验证程序时,Phalcon将根据数据传递数据 - 如果它是实体,则将传递实体,否则传递数据。有例子: ```php add( 'amount', new Callback( [ 'callback' => function($data) { return $data['amount'] % 2 == 0; }, 'message' => 'Only even number of products are accepted' ] ) ); $validation->add( 'amount', new Callback( [ 'callback' => function($data) { if($data['amount'] % 2 == 0) { return $data['amount'] != 2; } return true; }, 'message' => "You can't buy 2 products" ] ) ); $validation->add( 'description', new Callback( [ 'callback' => function($data) { if($data['amount'] >= 10) { return new PresenceOf( [ 'message' => 'You must write why you need so big amount.' ] ); } return true; } ] ) ); $messages = $validation->validate(['amount' => 1]); // will return message from first validator $messages = $validation->validate(['amount' => 2]); // will return message from second validator $messages = $validation->validate(['amount' => 10]); // will return message from validator returned by third validator ``` ## 验证消息 `Phalcon\Validation` 有一个消息传递子系统,它提供了一种灵活的方法来输出或存储验证过程中生成的验证消息。 每条消息都包含`Phalcon\Validation\Message`类的实例。可以使用`getMessages()`方法获取生成的消息集。每条消息都提供扩展信息,例如生成消息的属性或消息类型: ```php validate(); if (count($messages)) { foreach ($messages as $message) { echo 'Message: ', $message->getMessage(), "\n"; echo 'Field: ', $message->getField(), "\n"; echo 'Type: ', $message->getType(), "\n"; } } ``` 您可以传递`message`参数来更改/转换每个验证器中的默认消息,即使可以使用消息中的通配符`:field`替换为字段的标签: ```php add( 'email', new Email( [ 'message' => 'The e-mail is not valid', ] ) ); ``` 默认情况下, `getMessages()` 方法返回验证期间生成的所有消息。您可以使用`filter()`方法过滤特定字段的消息: ```php validate(); if (count($messages)) { // Filter only the messages generated for the field 'name' $filteredMessages = $messages->filter('name'); foreach ($filteredMessages as $message) { echo $message; } } ``` ## 过滤数据 可以在验证之前过滤数据,确保未验证恶意或不正确的数据。 ```php add( 'name', new PresenceOf( [ 'message' => 'The name is required', ] ) ); $validation->add( 'email', new PresenceOf( [ 'message' => 'The email is required', ] ) ); // Filter any extra space $validation->setFilters('name', 'trim'); $validation->setFilters('email', 'trim'); ``` 使用过滤器组件进行过滤和消毒。您可以向此组件添加更多过滤器或使用内置过滤器。 ## 验证事件 在类中组织验证时,可以实现`beforeValidation()`和`afterValidation()`方法以执行其他检查,过滤,清理等。如果`beforeValidation()`方法返回`false`,则验证将自动取消: ```php 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 } } ``` ## 取消验证 默认情况下,将测试分配给某个字段的所有验证程序,无论其中一个验证程序是否已失败。您可以通过告知验证组件哪个验证程序可以停止验证来更改此行为: ```php add( 'telephone', new PresenceOf( [ 'message' => 'The telephone is required', 'cancelOnFail' => true, ] ) ); $validation->add( 'telephone', new Regex( [ 'message' => 'The telephone is required', 'pattern' => '/\+44 [0-9]+/', ] ) ); $validation->add( 'telephone', new StringLength( [ 'messageMinimum' => 'The telephone is too short', 'min' => 2, ] ) ); ``` 第一个验证器具有选项`cancelOnFail`,其值为`true`,因此如果该验证器失败,则不会执行链中的其余验证器。 如果要创建自定义验证器,可以通过设置`cancelOnFail`选项动态停止验证链: ```php setOption('cancelOnFail', true); } // ... } } ``` ## 避免验证空值 您可以将选项`allowEmpty`传递给所有内置验证器,以避免在传递空值时执行验证: ```php add( 'telephone', new Regex( [ 'message' => 'The telephone is required', 'pattern' => '/\+44 [0-9]+/', 'allowEmpty' => true, ] ) ); ``` ## 递归验证 您还可以通过`afterValidation()` 方法在另一个中运行`Validation`实例。在此示例中,验证`CompanyValidation`实例还将检查`PhoneValidation`实例: ```php phoneValidation = new PhoneValidation(); } public function afterValidation($data, $entity, $messages) { $phoneValidationMessages = $this->phoneValidation->validate( $data['phone'] ); $messages->appendMessages( $phoneValidationMessages ); } } ```
';

生成URL和路径

最后更新于:2022-04-02 05:15:24

[TOC] # 生成URL和路径 `Phalcon\Mvc\Url` 是负责在Phalcon应用程序中生成URL的组件。它能够根据路由生成独立的URL。 ## 设置基本URI 根据您的应用程序安装的文档根目录,它可能具有基本URI。 例如,如果您的文档根目录是`/var/www/htdocs` ,并且您的应用程序安装在`/var/www/htdocs/invo`中,那么您的baseUri将是`/invo/`。如果您使用的是VirtualHost,或者您的应用程序安装在文档根目录上,那么您的baseUri是`/`。执行以下代码以了解Phalcon检测到的基URI: ```php getBaseUri(); ``` 默认情况下,Phalcon会自动检测您的baseUri,但如果您想提高应用程序的性能,建议您手动设置它: ```php setBaseUri('/invo/'); // Setting a full domain as base URI $url->setBaseUri('//my.domain.com/'); // Setting a full domain as base URI $url->setBaseUri('http://my.domain.com/my-app/'); ``` 通常,此组件必须在Dependency Injector容器中注册,因此您可以在那里进行设置: ```php set( 'url', function () { $url = new Url(); $url->setBaseUri('/invo/'); return $url; } ); ``` ## 生成URI 如果您使用路由器的默认行为,您的应用程序可以根据以下模式匹配路由: >[info] /:controller/:action/:params 因此,很容易创建满足该模式(或路由器中定义的任何其他模式)的路由,将字符串传递给方法`get`: ```php get('products/save'); ?> ``` 请注意,不必预先添加基URI。如果您已命名路由,则可以轻松地更改它以动态创建它。例如,如果您有以下路由: ```php add( '/blog/{year}/{month}/{title}', [ 'controller' => 'posts', 'action' => 'show', ] ) ->setName('show-post'); ``` 可以通过以下方式生成URL: ```php get( [ 'for' => 'show-post', 'year' => '2015', 'month' => '01', 'title' => 'some-blog-post', ] ); ``` ## 生成没有mod_rewrite的URL 您也可以使用此组件创建没有mod_rewrite的URL: ```php setBaseUri('/invo/index.php?_url=/'); // This produce: /invo/index.php?_url=/products/save echo $url->get('products/save'); ``` 你也可以使用 `$_SERVER['REQUEST_URI']`: ```php setBaseUri('/invo/index.php?_url=/'); // Pass the URI using $_SERVER['REQUEST_URI'] $url->setBaseUri('/invo/index.php/'); ``` 在这种情况下,需要在路由器中手动处理所需的URI: ```php handle($uri); ``` 生成的路由看起来像: ```php get('products/save'); ``` ## 在Volt生成URL volt中的`url`函数使用此组件生成URL: ```twig Edit ``` 生成静态路由: ```twig ``` ## 静态与动态URI 此组件允许您为应用程序中的静态资源设置不同的基URI: ```php setBaseUri('/'); // Static resources go through a CDN $url->setStaticBaseUri('http://static.mywebsite.com/'); ``` `Phalcon\Tag` 将使用此组件请求动态和静态URI。 ## 实现自己的URL生成器 必须实现`Phalcon\Mvc\UrlInterface`接口才能创建自己的URL生成器,替换Phalcon提供的URL生成器。
';

在session中存储数据

最后更新于:2022-04-02 05:15:22

[TOC] # 在session中存储数据 session组件提供面向对象的包装器来访问session数据。 使用此组件而不是原生session的原因: * 您可以轻松地在同一域上的应用程序之间隔离session数据 * 拦截在您的应用程序中设置/获取session数据的位置 * 根据应用程序需要更改session适配器 ## 启动Session 某些应用程序是session密集型的,几乎所有执行的操作都需要访问session数据。还有其他人随便访问session数据。感谢服务容器,我们可以确保只在明确需要时访问session: ```php setShared( 'session', function () { $session = new Session(); $session->start(); return $session; } ); ``` ## 工厂 使用`adapter`选项加载会话适配器类 ```php 'my-private-app', 'host' => '127.0.0.1', 'port' => 11211, 'persistent' => true, 'lifetime' => 3600, 'prefix' => 'my_', 'adapter' => 'memcache', ]; $session = Factory::load($options); ``` ## 在Session中存储/获取数据 从控制器,视图或任何其他继承`Phalcon\Di\Injectable`的组件,您可以访问session服务并存储项目并按以下方式获取它们: ```php 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'); } } } ``` ## 删除/销毁Session 它也可以删除特定变量或销毁整个session: ```php session->remove('user-name'); } public function logoutAction() { // Destroy the whole session $this->session->destroy(); } } ``` ## 隔离应用程序之间的session数据 有时,用户可以在同一session中在同一服务器上使用同一应用程序两次。当然,如果我们在session中使用变量,我们希望每个应用程序都有单独的session数据(即使相同的代码和相同的变量名称)。要解决此问题,您可以为在特定应用程序中创建的每个session变量添加前缀: ```php set( 'session', function () { // All variables created will prefixed with 'my-app-1' $session = new Session( [ 'uniqueId' => 'my-app-1', ] ); $session->start(); return $session; } ); ``` 不需要添加唯一ID。 ## Session Bags `Phalcon\Session\Bag` 是一个帮助将会话数据分离到`命名空间`的组件。通过这种方式工作,您可以轻松地在应用程序中创建会话变量组。只需在包中设置变量,它就会自动存储在会话中: ```php setDI($di); $user->name = 'Kimbra Johnson'; $user->age = 22; ``` ## 组件中的持久数据 继承 `Phalcon\Di\Injectable` 的控制器,组件和类可以注入`Phalcon\Session\Bag`。此类隔离每个类的变量。多亏了这一点,您可以以独立的方式在每个类中的请求之间保留数据。 ```php persistent->name = 'Laura'; } public function welcomeAction() { if (isset($this->persistent->name)) { echo 'Welcome, ', $this->persistent->name; } } } ``` 在一个组件中: ```php persistent->name = 'Laura'; } public function getAuthName() { return $this->persistent->name; } } ``` 添加到会话的数据(`$this->session`)在整个应用程序中都可用,而持久性(`$this->persistent`)只能在当前类的范围内访问。 ## 实现自己的适配器 必须实现`Phalcon\Session\AdapterInterface` 接口才能创建自己的会话适配器或扩展现有会话适配器。 [Phalcon Incubator](https://github.com/phalcon/incubator/tree/master/Library/Phalcon/Session/Adapter) 中有更多适用于此组件的适配器
';

路由

最后更新于:2022-04-02 05:15:20

[TOC] # 路由 路由器组件允许您定义映射到应接收请求的控制器或处理程序的路由。路由器只是解析URI以确定此信息。路由器有两种模式:MVC模式和仅匹配模式。第一种模式非常适合使用MVC应用程序。 ## 默认路由 `Phalcon\Mvc\Router` 提供高级路由功能。 在MVC模式下,您可以定义路由并将它们映射到您需要的控制器/操作。路由定义如下: ```php add( '/admin/users/my-profile', [ 'controller' => 'users', 'action' => 'profile', ] ); // Another route $router->add( '/admin/users/change-password', [ 'controller' => 'users', 'action' => 'changePassword', ] ); $router->handle(); ``` `add()` 方法的第一个参数是您要匹配的模式,可选地,第二个参数是一组路径。在这种情况下,如果URI是`/admin/users/my-profile`,则将执行`users`控制器的 `profile` 操作。重要的是要记住路由器不执行控制器和操作,它只收集此信息以通知正确的组件(即`Phalcon\Mvc\Dispatcher`)这是它应该执行的控制器/操作。 应用程序可以有许多路径,逐个定义路由可能是一项繁琐的任务。在这些情况下,我们可以创建更灵活的路由: ```php add( '/admin/:controller/a/:action/:params', [ 'controller' => 1, 'action' => 2, 'params' => 3, ] ); ``` 在上面的示例中,我们使用通配符来使路由对许多URI有效。例如,通过访问以下URL(`/admin/users/a/delete/dave/301`)将产生: | Controller | Action | Parameter | Parameter | |:----------:|:------:|:---------:|:---------:| | users | delete | dave | 301 | `add()`方法接收一个模式,该模式可以选择具有预定义的占位符和正则表达式修饰符。所有路由模式必须以正斜杠字符(`/`)开头。使用的正则表达式语法与[PCRE正则表达式](http://www.php.net/manual/en/book.pcre.php)相同。请注意,没有必要添加正则表达式分隔符。所有路由模式都不区分大小写。 第二个参数定义匹配的部分应如何绑定到控制器/动作/参数。匹配部分是由括号(圆括号)分隔的占位符或子图案。在上面给出的示例中,第一个子模式匹配(`:controller`)是路径的控制器部分,第二个是动作,依此类推。 这些占位符有助于编写对开发人员更具可读性且更易于理解的正则表达式。支持以下占位符: | 占位符 | 正则表达式 | 用法 | | -------------- | ------------------------ | ------------------------------------------------------------------------------------------------------ | | `/:module` | `/([a-zA-Z0-9\_\-]+)` | 仅使用字母数字字符匹配有效的模块名称 | | `/:controller` | `/([a-zA-Z0-9\_\-]+)` | 仅使用字母数字字符匹配有效的控制器名称 | | `/:action` | `/([a-zA-Z0-9_-]+)` | 仅使用字母数字字符匹配有效的操作名称 | | `/:params` | `(/.*)*` | 匹配由斜杠分隔的可选单词列表。仅在路由末尾使用此占位符 | | `/:namespace` | `/([a-zA-Z0-9\_\-]+)` | 匹配单级命名空间名称 | | `/:int` | `/([0-9]+)` | 匹配整数参数 | 控制器名称是驼峰,这意味着删除了字符( `-` )和(`_`),下一个字符是大写的。例如,some_controller被转换为SomeController。 Since you can add many routes as you need using the `add()` method, the order in which routes are added indicate their relevance, latest routes added have more relevance than first added. Internally, all defined routes are traversed in reverse order until `Phalcon\Mvc\Router` finds the one that matches the given URI and processes it, while ignoring the rest. 由于您可以使用 `add()` 方法根据需要添加许多路由,因此添加路由的顺序表明它们的相关性,添加的最新路由具有比首次添加的更多相关性。在内部,所有定义的路由都以相反的顺序遍历,直到`Phalcon\Mvc\Router` 找到与给定URI匹配并处理它的那个,同时忽略其余的路由。 ### 带名称的参数 下面的示例演示了如何定义路由参数的名称: ```php add( '/news/([0-9]{4})/([0-9]{2})/([0-9]{2})/:params', [ 'controller' => 'posts', 'action' => 'show', 'year' => 1, // ([0-9]{4}) 'month' => 2, // ([0-9]{2}) 'day' => 3, // ([0-9]{2}) 'params' => 4, // :params ] ); ``` 在上面的示例中,路径未定义`controller`或`action`部件。这些部件将替换为固定值(`posts`和`show`)。用户将不知道请求真正分发的控制器。在控制器内部,可以按如下方式访问这些命名参数: ```php dispatcher->getParam('year'); // Get 'month' parameter $month = $this->dispatcher->getParam('month'); // Get 'day' parameter $day = $this->dispatcher->getParam('day'); // ... } } ``` 请注意,参数的值是从调度程序获得的。发生这种情况是因为它是最终与应用程序驱动程序交互的组件。此外,还有另一种方法可以创建命名参数作为模式的一部分: ```php add( '/documentation/{chapter}/{name}.{type:[a-z]+}', [ 'controller' => 'documentation', 'action' => 'show', ] ); ``` 您可以像以前一样访问其值: ```php dispatcher->getParam('name'); // Get 'type' parameter $type = $this->dispatcher->getParam('type'); // ... } } ``` ### 短语法 如果您不喜欢使用数组来定义路径路径,则还可以使用替代语法。以下示例产生相同的结果: ```php add( '/posts/{year:[0-9]+}/{title:[a-z\-]+}', 'Posts::show' ); // Array form $router->add( '/posts/([0-9]+)/([a-z\-]+)', [ 'controller' => 'posts', 'action' => 'show', 'year' => 1, 'title' => 2, ] ); ``` ### 混合数组和短语法 可以混合使用数组和短语法来定义路由,在这种情况下请注意,命名参数会根据定义它们的位置自动添加到路径路径中: ```php add( '/news/{country:[a-z]{2}}/([a-z+])/([a-z\-+])', [ 'section' => 2, // Positions start with 2 'article' => 3, ] ); ``` ### 模块路由 您可以定义路径包含模块的路由。这特别适用于多模块应用。可以定义包含模块通配符的默认路由: ```php add( '/:module/:controller/:action/:params', [ 'module' => 1, 'controller' => 2, 'action' => 3, 'params' => 4, ] ); ``` 在这种情况下,路由始终必须将模块名称作为URL的一部分。例如,以下URL:`/admin/users/edit/sonny` 将被处理为: | Module | Controller | Action | Parameter | |:------:|:----------:|:------:|:---------:| | admin | users | edit | sonny | 或者您可以将特定路由绑定到特定模块: ```php add( '/login', [ 'module' => 'backend', 'controller' => 'login', 'action' => 'index', ] ); $router->add( '/products/:action', [ 'module' => 'frontend', 'controller' => 'products', 'action' => 1, ] ); ``` 或者将它们绑定到特定的名称空间: ```php add( '/:namespace/login', [ 'namespace' => 1, 'controller' => 'login', 'action' => 'index', ] ); ``` 命名空间/类名必须分开传递: ```php add( '/login', [ 'namespace' => 'Backend\Controllers', 'controller' => 'login', 'action' => 'index', ] ); ``` ### HTTP方法限制 使用简单的`add()`添加路由时,将为任何HTTP方法启用路由。有时我们可以将路由限制为特定方法,这在创建RESTful应用程序时特别有用: ```php addGet( '/products/edit/{id}', 'Products::edit' ); // This route only will be matched if the HTTP method is POST $router->addPost( '/products/save', 'Products::save' ); // This route will be matched if the HTTP method is POST or PUT $router->add( '/products/update', 'Products::update' )->via( [ 'POST', 'PUT', ] ); ``` ### 使用转换器 转换器允许您在将路由参数传递给调度程序之前自由转换路径的参数。以下示例显示了如何使用它们: ```php add( '/products/{slug:[a-z\-]+}', [ 'controller' => 'products', 'action' => 'show', ] ); $route->convert( 'slug', function ($slug) { // Transform the slug removing the dashes return str_replace('-', '', $slug); } ); ``` 转换器的另一个用例是将模型绑定到路由中。这允许模型直接传递到定义的操作: ```php add( '/products/{id}', [ 'controller' => 'products', 'action' => 'show', ] ); $route->convert( 'id', function ($id) { // Fetch the model return Product::findFirstById($id); } ); ``` ### 路由分组 如果一组路由具有公共路径,则可以对它们进行分组以轻松维护它们: ```php 'blog', 'controller' => 'index', ] ); // All the routes start with /blog $blog->setPrefix('/blog'); // Add a route to the group $blog->add( '/save', [ 'action' => 'save', ] ); // Add another route to the group $blog->add( '/edit/{id}', [ 'action' => 'edit', ] ); // This route maps to a controller different than the default $blog->add( '/blog', [ 'controller' => 'blog', 'action' => 'index', ] ); // Add the group to the router $router->mount($blog); ``` 您可以将路径组移动到单独的文件,以改善应用程序中的组织和代码重用: ```php setPaths( [ 'module' => 'blog', 'namespace' => 'Blog\Controllers', ] ); // All the routes start with /blog $this->setPrefix('/blog'); // Add a route to the group $this->add( '/save', [ 'action' => 'save', ] ); // Add another route to the group $this->add( '/edit/{id}', [ 'action' => 'edit', ] ); // This route maps to a controller different than the default $this->add( '/blog', [ 'controller' => 'blog', 'action' => 'index', ] ); } } ``` 然后将该组安装在路由器中: ```php mount( new BlogRoutes() ); ``` ## 匹配路由 必须将有效的URI传递给路由器,以便它可以处理它并找到匹配的路由。默认情况下,路由URI取自重写引擎模块创建的`$_GET['_url']`变量。与Phalcon一起使用的几个重写规则是: ```apacheconfig RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L] ``` 在此配置中,对不存在的文件或文件夹的任何请求都将发送到`index.php`。以下示例显示如何在独立模式下使用此组件: ```php handle(); // Or Setting the URI value directly $router->handle('/employees/edit/17'); // Getting the processed controller echo $router->getControllerName(); // Getting the processed action echo $router->getActionName(); // Get the matched route $route = $router->getMatchedRoute(); ``` ## 路由命名 添加到路由器的每条路由都在内部存储为`Phalcon\Mvc\Router\Route` 对象。该类封装了每个路由的所有细节。例如,我们可以为路径命名,以便在我们的应用程序中唯一地标识它。如果要从中创建URL,这尤其有用。 ```php add( '/posts/{year}/{title}', 'Posts::show' ); $route->setName('show-posts'); ``` 然后,使用例如组件`Phalcon\Mvc\Url`,我们可以从其名称构建路由: ```php get( [ 'for' => 'show-posts', 'year' => '2012', 'title' => 'phalcon-1-0-released', ] ); ``` ## 用例 以下是自定义路由的示例: ```php add( '/system/:controller/a/:action/:params', [ 'controller' => 1, 'action' => 2, 'params' => 3, ] ); // Matches '/es/news' $router->add( '/([a-z]{2})/:controller', [ 'controller' => 2, 'action' => 'index', 'language' => 1, ] ); // Matches '/es/news' $router->add( '/{language:[a-z]{2}}/:controller', [ 'controller' => 2, 'action' => 'index', ] ); // Matches '/admin/posts/edit/100' $router->add( '/admin/:controller/:action/:int', [ 'controller' => 1, 'action' => 2, 'id' => 3, ] ); // Matches '/posts/2015/02/some-cool-content' $router->add( '/posts/([0-9]{4})/([0-9]{2})/([a-z\-]+)', [ 'controller' => 'posts', 'action' => 'show', 'year' => 1, 'month' => 2, 'title' => 3, ] ); // Matches '/manual/en/translate.adapter.html' $router->add( '/manual/([a-z]{2})/([a-z\.]+)\.html', [ 'controller' => 'manual', 'action' => 'show', 'language' => 1, 'file' => 2, ] ); // Matches /feed/fr/le-robots-hot-news.atom $router->add( '/feed/{lang:[a-z]+}/{blog:[a-z\-]+}\.{type:[a-z\-]+}', 'Feed::get' ); // Matches /api/v1/users/peter.json $router->add( '/api/(v1|v2)/{method:[a-z]+}/{param:[a-z]+}\.(json|xml)', [ 'controller' => 'api', 'version' => 1, 'format' => 4, ] ); ``` >[warning] 注意控制器和命名空间的正则表达式中允许的字符。由于它们成为类名,反过来它们通过文件系统可被攻击者用来读取未经授权的文件。一个安全的正则表达式是:`/([a-zA-Z0-9\_\-]+)` ## 默认行为 `Phalcon\Mvc\Router`有一个默认行为,它提供了一个非常简单的路由,总是需要一个匹配以下模式的URI:`/:controller/:action/:params` 例如,对于像`http://phalconphp.com/documentation/show/about.html`这样的URL,此路由器将按如下方式对其进行翻译: | Controller | Action | Parameter | |:-------------:|:------:|:----------:| | documentation | show | about.html | 如果您不希望路由器出现此行为,则必须创建路由器,并将`false`作为第一个参数: ```php add( '/', [ 'controller' => 'index', 'action' => 'index', ] ); ``` ## Not Found路径 如果路由器中指定的路由均未匹配,则可以定义要在此方案中使用的一组路径: ```php notFound( [ 'controller' => 'index', 'action' => 'route404', ] ); ``` 这通常用于错误404页面。 > 只有在没有默认路由的情况下创建路由器时,这才有效: `$router = Phalcon\Mvc\Router(FALSE);` ## 设置默认路径 可以为模块,控制器或操作定义默认值。当路由缺少任何这些路径时,它们可以由路由器自动填充: ```php setDefaultModule('backend'); $router->setDefaultNamespace('Backend\Controllers'); $router->setDefaultController('index'); $router->setDefaultAction('index'); // Using an array $router->setDefaults( [ 'controller' => 'index', 'action' => 'index', ] ); ``` ## 处理额外/尾随斜杠 有时可以使用额外/尾部斜杠访问路径。这些额外的斜杠将导致在调度器中产生未找到的状态。您可以设置路由器以自动从处理路径的末尾删除斜杠: ```php removeExtraSlashes(true); ``` 或者,您可以修改特定路由以选择性地接受尾部斜杠: ```php add( '/{language:[a-z]{2}}/:controller[/]{0,1}', [ 'controller' => 2, 'action' => 'index', ] ); ``` ## 匹配回调 有时,路由只有在满足特定条件时才能匹配。您可以使用`beforeMatch()` 回调向路由添加任意条件。如果此函数返回`false`,则路由将被视为不匹配: ```php add('/login', [ 'module' => 'admin', 'controller' => 'session', ] ); $route->beforeMatch( function ($uri, $route) { // Check if the request was made with Ajax if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { return false; } return true; } ); ``` 您可以在类中重复使用这些额外条件: ```php add( '/get/info/{id}', [ 'controller' => 'products', 'action' => 'info', ] ); $route->beforeMatch( [ new AjaxFilter(), 'check' ] ); ``` 从Phalcon 3开始,还有另一种方法可以检查: ```php add( '/login', [ 'module' => 'admin', 'controller' => 'session', ] ); $route->beforeMatch( function ($uri, $route) { /** * @var string $uri * @var \Phalcon\Mvc\Router\Route $route * @var \Phalcon\DiInterface $this * @var \Phalcon\Http\Request $request */ $request = $this->getShared('request'); // Check if the request was made with Ajax return $request->isAjax(); } ); ``` ## 主机名约束 路由器允许您设置主机名约束,这意味着可以限制特定路由或一组路由仅在路由也满足主机名约束时匹配: ```php add( '/login', [ 'module' => 'admin', 'controller' => 'session', 'action' => 'login', ] ); $route->setHostName('admin.company.com'); ``` 主机名也可以作为正则表达式传递: ```php add( '/login', [ 'module' => 'admin', 'controller' => 'session', 'action' => 'login', ] ); $route->setHostName('([a-z]+).company.com'); ``` 在路由组中,您可以设置适用于组中每个路由的主机名约束: ```php 'blog', 'controller' => 'posts', ] ); // Hostname restriction $blog->setHostName('blog.mycompany.com'); // All the routes start with /blog $blog->setPrefix('/blog'); // Default route $blog->add( '/', [ 'action' => 'index', ] ); // Add a route to the group $blog->add( '/save', [ 'action' => 'save', ] ); // Add another route to the group $blog->add( '/edit/{id}', [ 'action' => 'edit', ] ); // Add the group to the router $router->mount($blog); ``` ## URI来源 默认情况下,URI信息是从`$_GET['_url']`变量获得的,这是由Rewrite-Engine传递给Phalcon的,如果需要,你也可以使用`$_SERVER['REQUEST_URI']`: ```php setUriSource( Router::URI_SOURCE_GET_URL ); // Use $_SERVER['REQUEST_URI'] $router->setUriSource( Router::URI_SOURCE_SERVER_REQUEST_URI ); ``` 或者您可以手动将URI传递给`handle()`方法: ```php handle('/some/route/to/handle'); ``` >[danger] 请注意,使用`Router::URI_SOURCE_GET_URL`会自动解码Uri,因为它基于`$_REQUEST`超全局。但是,目前使用`Router::URI_SOURCE_SERVER_REQUEST_URI`不会自动为您解码Uri。这将在下一个主版本中发生变化。 ## 测试你的路由 由于此组件没有依赖关系,您可以创建如下所示的文件来测试您的路由: ```php handle($testRoute); echo 'Testing ', $testRoute, '
'; // Check if some route was matched if ($router->wasMatched()) { echo 'Controller: ', $router->getControllerName(), '
'; echo 'Action: ', $router->getActionName(), '
'; } else { echo "The route wasn't matched by any route
"; } echo '
'; } ``` ## 事件 与许多其他组件一样,路由器也有事件。没有任何事件可以停止操作。以下是可用事件列表 | 事件 | 描述 | | -------------------------- | ------------------------------------ | | `router:beforeCheckRoutes` | 在检查所有加载的路由之前被触发 | | `router:beforeCheckRoute` | 在检查路由之前被触发 | | `router:matchedRoute` | 匹配路由时触发 | | `router:notMatchedRoute` | 任一路由匹配时触发 | | `router:afterCheckRoutes` | 检查完所有路由后触发 | | `router:beforeMount` | 在安装新路由之前触发 | ## 注解路由 此组件提供与注解服务集成的变体。使用此策略,您可以直接在控制器中编写路由,而不是在服务注册中添加它们: ```php addResource('Products', '/api/products'); return $router; }; ``` 注解可以通过以下方式定义: ```php addModuleResource('backend', 'Products', '/api/products'); return $router; }; ``` ## 注册路由器实例 您可以在服务注册期间使用Phalcon依赖注入器注册路由器,以使其在控制器内可用。 您需要在引导程序文件中添加以下代码(例如 `index.php`,如果使用[Phalcon Developer Tools](http://phalconphp.com/en/download/tools),则为`app/config/services.php`。 ```php set( 'router', function () { require __DIR__ . '/../app/config/routes.php'; return $router; } ); ``` 您需要创建 `app/config/routes.php`并添加路由器初始化代码,例如: ```php add( '/login', [ 'controller' => 'login', 'action' => 'index', ] ); $router->add( '/products/:action', [ 'controller' => 'products', 'action' => 1, ] ); return $router; ``` ## 实现自己的路由器 必须实现 `Phalcon\Mvc\RouterInterface` 接口才能创建自己的路由器,取代Phalcon提供的路由器。
';

过滤与清理

最后更新于:2022-04-02 05:15:18

[TOC] # 过滤与清理 清理用户输入是软件开发的关键部分。信任或忽略对用户输入进行清理可能导致未经授权访问应用程序的内容,主要是用户数据,甚至是托管应用程序的服务器。 ![](https://docs.phalconphp.com/images/content/filter-sql.png) [完整大图](http://xkcd.com/327) `Phalcon\Filter` 组件提供了一组常用的过滤器和数据清理助手。它提供围绕PHP过滤器扩展的面向对象的包装器。 ## 内置过滤器的类型 以下是此组件提供的内置过滤器: | 名称 | 描述 | | ------------- | ------------------------------------------------------------------------------------------------------------- | | string |剥离标签并对HTML实体进行编码,包括单引号和双引号 | | email | 删除除字母,数字和 ``!#$%&*+-/=?^_`{\|}~@.[]`` 之外的所有字符 | | int |删除除数字,加号和减号以外的所有字符 | | int! | 使用[intval](http://php.net/manual/en/function.intval.php)函数将值转换为整数值 | | absint | 获取已转换为整数的值的绝对值 | | float | 删除除数字,点,加号和减号以外的所有字符 | | float! | 使用[floatval](http://php.net/manual/en/function.floatval.php)函数将值转换为浮点值 | | alphanum | 删除[a-zA-Z0-9]以外的所有字符 | | striptags | 应用[strip_tags](http://www.php.net/manual/en/function.strip-tags.php)函数 | | special_chars | 转义'“<>&和ASCII值小于32的字符 | | trim | 应用 [trim](http://www.php.net/manual/en/function.trim.php) 函数 | | lower | 应用 [strtolower](http://www.php.net/manual/en/function.strtolower.php) 函数 | | url | 删除除字母,数字和 ``|$`-_.+!*'(),{}[]<>#%";/?:@&=.^\\~`` 之外的所有字符 | | upper | 应用 [strtoupper](http://www.php.net/manual/en/function.strtoupper.php) 函数 | ## 清理数据 清理是从值中删除特定字符的过程,用户或应用程序不需要或不需要这些字符。通过清理输入,我们确保应用程序的完整性将保持不变。 ```php 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'); ``` ## 从控制器清理 访问`GET`或`POST`输入数据时(通过请求对象),您可以从控制器访问`Phalcon\Filter`对象。第一个参数是要获取的变量的名称;第二个是要应用的过滤器。 ```php request->getPost('price', 'double'); // Sanitizing email from input $email = $this->request->getPost('customerEmail', 'email'); } } ``` ## 过滤操作参数 下一个示例显示如何清理控制器操作中的操作参数: ```php filter->sanitize($productId, 'int'); } } ``` ## 过滤数据 除了清理之外, `Phalcon\Filter`还通过删除或修改输入数据到我们期望的格式来提供过滤。 ```php sanitize('

Hello

', 'striptags'); // Returns 'Hello' $filter->sanitize(' Hello ', 'trim'); ``` ## 结合过滤器 您还可以通过将过滤器标识符数组作为第二个参数传递,同时对字符串运行多个过滤器: ```php sanitize( '

Hello

', [ 'striptags', 'trim', ] ); ``` ## 添加过滤器 您可以将自己的过滤器添加到`Phalcon\Filter`。过滤函数可以是匿名函数: ```php add( 'md5', function ($value) { return preg_replace('/[^0-9a-f]/', '', $value); } ); // Sanitize with the 'md5' filter $filtered = $filter->sanitize($possibleMd5, 'md5'); ``` 或者,如果您愿意,可以在类中实现过滤器: ```php add( 'ipv4', new IPv4Filter() ); // Sanitize with the 'ipv4' filter $filteredIp = $filter->sanitize('127.0.0.1', 'ipv4'); ``` ## 复杂的清理和过滤 PHP本身提供了一个很好的过滤扩展,您可以使用。查看其文档:[PHP文档中的数据过滤](http://www.php.net/manual/en/book.filter.php) ## 实现自己的过滤器 必须实现`Phalcon\FilterInterface`接口才能创建自己的过滤服务,取代Phalcon提供的过滤服务。
';

事件管理器

最后更新于:2022-04-02 05:15:15

[TOC] # 事件管理器 该组件的目的是通过创建“钩子”来拦截框架的大多数其他组件的执行。这些挂钩点允许开发人员在组件处理期间获取状态信息,操纵数据或更改执行流程。 ## 命名惯例 Phalcon事件使用命名空间来避免命名冲突。Phalcon中的每个组件都占用不同的事件名称空间,您可以根据需要自由创建自己的名称空间。事件名称格式为`component:event`。例如,当`Phalcon\Db` 占用`db`命名空间时,其`afterQuery`事件的全名是`db:afterQuery`。 将事件侦听器附加到事件管理器时,可以使用`组件`来捕获该组件中的所有事件(例如,`db`以捕获所有`Phalcon\Db`事件)或`component:event`来定位特定事件(例如`db:afterQuery`)。 ## 用例 在下面的示例中,我们将使用EventsManager侦听由`Phalcon\Db`管理的MySQL连接中生成的`afterQuery`事件: ```php attach( 'db:afterQuery', function (Event $event, $connection) { echo $connection->getSQLStatement(); } ); $connection = new DbAdapter( [ 'host' => 'localhost', 'username' => 'root', 'password' => 'secret', 'dbname' => 'invo', ] ); // Assign the eventsManager to the db adapter instance $connection->setEventsManager($eventsManager); // Send a SQL command to the database server $connection->query( 'SELECT * FROM products p WHERE p.status = 1' ); ``` 现在每次执行查询时,都会回显出SQL语句。传递给lambda函数的第一个参数包含有关正在运行的事件的上下文信息,第二个参数是事件的来源(在本例中是连接本身)。还可以指定第三个参数,该参数将包含特定于该事件的任意数据。 >[warning] 必须使用`setEventsManager()`方法将事件管理器显式设置为组件,以便该组件触发事件。您可以为每个组件创建新的事件管理器实例,也可以将相同的事件管理器设置为多个组件,因为命名约定可以避免冲突。 您可以使用事件侦听器类,而不是使用lambda函数。事件侦听器还允许您侦听多个事件。在这个例子中,我们将实现`Phalcon\Db\Profiler`来检测执行时间比预期更长的SQL语句: ```php profiler = new Profiler(); $this->logger = new Logger('../apps/logs/db.log'); } /** * This is executed if the event triggered is 'beforeQuery' */ public function beforeQuery(Event $event, $connection) { $this->profiler->startProfile( $connection->getSQLStatement() ); } /** * This is executed if the event triggered is 'afterQuery' */ public function afterQuery(Event $event, $connection) { $this->logger->log( $connection->getSQLStatement(), Logger::INFO ); $this->profiler->stopProfile(); } public function getProfiler() { return $this->profiler; } } ``` 将事件侦听器附加到事件管理器非常简单: ```php attach( 'db', $dbListener ); ``` 可以从侦听器获取生成的配置文件数据: ```php execute( 'SELECT * FROM products p WHERE p.status = 1' ); foreach ($dbListener->getProfiler()->getProfiles() as $profile) { echo 'SQL Statement: ', $profile->getSQLStatement(), '\n'; echo 'Start Time: ', $profile->getInitialTime(), '\n'; echo 'Final Time: ', $profile->getFinalTime(), '\n'; echo 'Total Elapsed Time: ', $profile->getTotalElapsedSeconds(), '\n'; } ``` ## 创建触发事件的组件 您可以在应用程序中创建触发EventManager事件的组件。因此,可能存在在生成时对这些事件作出反应的侦听器。在下面的示例中,我们将创建一个名为`MyComponent`的组件。该组件是EventsManager感知的(它实现了`Phalcon\Events\EventsAwareInterface`);当执行`someTask()`方法时,它会向EventsManager中的任何侦听器触发两个事件: ```php eventsManager = $eventsManager; } public function getEventsManager() { return $this->eventsManager; } public function someTask() { $this->eventsManager->fire('my-component:beforeSomeTask', $this); // Do some task echo 'Here, someTask\n'; $this->eventsManager->fire('my-component:afterSomeTask', $this); } } ``` 请注意,在此示例中,我们使用的是`my-component`事件命名空间。现在我们需要为这个组件创建一个事件监听器: ```php setEventsManager($eventsManager); // Attach the listener to the EventsManager $eventsManager->attach( 'my-component', new SomeListener() ); // Execute methods in the component $myComponent->someTask(); ``` 当执行 `someTask()` 时,将执行侦听器中的两个方法,从而产生以下输出: ```bash Here, beforeSomeTask Here, someTask Here, afterSomeTask ``` 使用 `fire()`的第三个参数触发事件时,也可能传递其他数据: ```php fire('my-component:afterSomeTask', $this, $extraData); ``` 在侦听器中,第三个参数也接收此数据: ```php attach( 'my-component', function (Event $event, $component, $data) { print_r($data); } ); // Receiving the data from the event context $eventsManager->attach( 'my-component', function (Event $event, $component) { print_r($event->getData()); } ); ``` ## 使用来自DI的服务 通过继承`Phalcon\Mvc\User\Plugin`,您可以从DI访问服务,就像在控制器中一样: ```php logger->debug( 'beforeSomeTask has been triggered' ); } public function afterSomeTask(Event $event, $myComponent) { echo 'Here, afterSomeTask\n'; $this->logger->debug( 'afterSomeTask has been triggered' ); } } ``` ## 事件通知/取消 许多听众可能会被添加到同一个事件管理器中。这意味着对于相同类型的事件,可以通知许多侦听器。将按照它们在EventsManager中注册的顺序通知侦听器。某些事件是可取消的,表示可能会停止这些事件,以防止其他侦听器收到有关该事件的通知: ```php attach( 'db', function (Event $event, $connection) { // We stop the event if it is cancelable if ($event->isCancelable()) { // Stop the event, so other listeners will not be notified about this $event->stop(); } // ... } ); ``` 默认情况下,事件是可取消的 - 即使框架生成的大多数事件都是可取消的。您可以通过在`fire()`的第四个参数中传递`false`来触发不可取消的事件: ```php fire('my-component:afterSomeTask', $this, $extraData, false); ``` ## 侦听优先级 附加侦听器时,您可以设置特定的优先级。使用此功能,您可以附加指示必须调用它们的顺序的侦听器: ```php enablePriorities(true); $eventsManager->attach('db', new DbListener(), 150); // More priority $eventsManager->attach('db', new DbListener(), 100); // Normal priority $eventsManager->attach('db', new DbListener(), 50); // Less priority ``` ## 收集响应 事件管理器可以收集每个通知的侦听器返回的每个响应。此示例说明了它的工作原理: ```php collectResponses(true); // Attach a listener $eventsManager->attach( 'custom:custom', function () { return 'first response'; } ); // Attach a listener $eventsManager->attach( 'custom:custom', function () { return 'second response'; } ); // Fire the event $eventsManager->fire('custom:custom', null); // Get all the collected responses print_r($eventsManager->getResponses()); ``` 上面的例子生成: ```php Array ( [0] => first response [1] => second response ) ``` ## 实现自己的EventsManager 必须实现`Phalcon\Events\ManagerInterface`接口才能创建自己的EventsManager,取代Phalcon提供的EventManager。 ## 事件列表 Phalcon提供的事件包括: | 组件 | 事件 | | ------------------ | ----------------------------------- | | ACL | `acl:afterCheckAccess` | | ACL | `acl:beforeCheckAccess` | | Application | `application:afterHandleRequest` | | Application | `application:afterStartModule` | | Application | `application:beforeHandleRequest` | | Application | `application:beforeSendResponse` | | Application | `application:beforeStartModule` | | Application | `application:boot` | | Application | `application:viewRender` | | CLI | `dispatch:beforeException` | | Collection | `afterCreate` | | Collection | `afterSave` | | Collection | `afterUpdate` | | Collection | `afterValidation` | | Collection | `afterValidationOnCreate` | | Collection | `afterValidationOnUpdate` | | Collection | `beforeCreate` | | Collection | `beforeSave` | | Collection | `beforeUpdate` | | Collection | `beforeValidation` | | Collection | `beforeValidationOnCreate` | | Collection | `beforeValidationOnUpdate` | | Collection | `notDeleted` | | Collection | `notSave` | | Collection | `notSaved` | | Collection | `onValidationFails` | | Collection | `validation` | | Collection Manager | `collectionManager:afterInitialize` | | Console | `console:afterHandleTask` | | Console | `console:afterStartModule` | | Console | `console:beforeHandleTask` | | Console | `console:beforeStartModule` | | Db | `db:afterQuery` | | Db | `db:beforeQuery` | | Db | `db:beginTransaction` | | Db | `db:createSavepoint` | | Db | `db:commitTransaction` | | Db | `db:releaseSavepoint` | | Db | `db:rollbackTransaction` | | Db | `db:rollbackSavepoint` | | Dispatcher | `dispatch:afterExecuteRoute` | | Dispatcher | `dispatch:afterDispatch` | | Dispatcher | `dispatch:afterDispatchLoop` | | Dispatcher | `dispatch:afterInitialize` | | Dispatcher | `dispatch:beforeException` | | Dispatcher | `dispatch:beforeExecuteRoute` | | Dispatcher | `dispatch:beforeDispatch` | | Dispatcher | `dispatch:beforeDispatchLoop` | | Dispatcher | `dispatch:beforeForward` | | Dispatcher | `dispatch:beforeNotFoundAction` | | Loader | `loader:afterCheckClass` | | Loader | `loader:beforeCheckClass` | | Loader | `loader:beforeCheckPath` | | Loader | `loader:pathFound` | | Micro | `micro:afterHandleRoute` | | Micro | `micro:afterExecuteRoute` | | Micro | `micro:beforeExecuteRoute` | | Micro | `micro:beforeHandleRoute` | | Micro | `micro:beforeNotFound` | | Middleware | `afterBinding` | | Middleware | `afterExecuteRoute` | | Middleware | `afterHandleRoute` | | Middleware | `beforeExecuteRoute` | | Middleware | `beforeHandleRoute` | | Middleware | `beforeNotFound` | | Model | `afterCreate` | | Model | `afterDelete` | | Model | `afterSave` | | Model | `afterUpdate` | | Model | `afterValidation` | | Model | `afterValidationOnCreate` | | Model | `afterValidationOnUpdate` | | Model | `beforeDelete` | | Model | `notDeleted` | | Model | `beforeCreate` | | Model | `beforeDelete` | | Model | `beforeSave` | | Model | `beforeUpdate` | | Model | `beforeValidation` | | Model | `beforeValidationOnCreate` | | Model | `beforeValidationOnUpdate` | | Model | `notSave` | | Model | `notSaved` | | Model | `onValidationFails` | | Model | `prepareSave` | | Models Manager | `modelsManager:afterInitialize` | | Request | `request:afterAuthorizationResolve` | | Request | `request:beforeAuthorizationResolve` | | Router | `router:beforeCheckRoutes` | | Router | `router:beforeCheckRoute` | | Router | `router:matchedRoute` | | Router | `router:notMatchedRoute` | | Router | `router:afterCheckRoutes` | | Router | `router:beforeMount` | | View | `view:afterRender` | | View | `view:afterRenderView` | | View | `view:beforeRender` | | View | `view:beforeRenderView` | | View | `view:notFoundView` | | Volt | `compileFilter` | | Volt | `compileFunction` | | Volt | `compileStatement` | | Volt | `resolveExpression` |
';

调度控制器

最后更新于:2022-04-02 05:15:13

[TOC] # 调度控制器 `Phalcon\Mvc\Dispatcher` 是负责实例化控制器并在MVC应用程序中对它们执行所需操作的组件。了解其操作和功能有助于我们从框架提供的服务中获得更多。 ## 调度循环 这是一个重要的过程,与MVC流本身有很大关系,特别是与控制器部分有关。工作发生在控制器调度程序中。读取,加载和实例化控制器文件。然后执行所需的操作。如果操作将流转发到另一个控制器/操作,则控制器调度程序再次启动。为了更好地说明这一点,以下示例显示了`Phalcon\Mvc\Dispatcher`中执行的大致过程: ```php set( 'dispatcher', function () { // Create an event manager $eventsManager = new EventsManager(); // Attach a listener for type 'dispatch' $eventsManager->attach( 'dispatch', function (Event $event, $dispatcher) { // ... } ); $dispatcher = new MvcDispatcher(); // Bind the eventsManager to the view component $dispatcher->setEventsManager($eventsManager); return $dispatcher; }, true ); ``` 实例化的控制器自动充当调度事件的侦听器,因此您可以将方法实现为回调: ```php [warning] 事件侦听器上的方法接受`Phalcon\Events\Event`对象作为它们的第一个参数 - 控制器中的方法不接受。 ## 转发到其他动作 调度循环允许我们将执行流转发到另一个控制器/动作。这对于检查用户是否可以访问某些选项,将用户重定向到其他屏幕或仅重用代码非常有用。 ```php dispatcher->forward( [ 'controller' => 'posts', 'action' => 'index', ] ); } } ``` 请记住,进行`forward`与进行HTTP重定向不同。虽然他们显然得到了相同的结果。`forward`不重新加载当前页面,所有重定向都发生在单个请求中,而HTTP重定向需要两个请求才能完成该过程。 更多转发示例: ```php dispatcher->forward( [ 'action' => 'search' ] ); // Forward flow to another action in the current controller // passing parameters $this->dispatcher->forward( [ 'action' => 'search', 'params' => [1, 2, 3] ] ); ``` `forward` 操作接受以下参数: | 参数 | 描述 | | ------------ | ------------------------------------------------------- | | `controller` | 要转发的有效控制器名称。 | | `action` | 要转发到的有效操作名称。 | | `params` | 操作的参数数组。 | | `namespace` | 控制器所属的有效命名空间名称。 | ### 使用事件管理器 您可以使用`dispatcher::beforeForward`事件来更改模块比重定向更容易和“更清洁”: ```php [ 'className' => 'App\Backend\Bootstrap', 'path' => '/app/Modules/Backend/Bootstrap.php', 'metadata' => [ 'controllersNamespace' => 'App\Backend\Controllers', ], ], ]; $manager = new Manager(); $manager->attach( 'dispatch:beforeForward', function (Event $event, Dispatcher $dispatcher, array $forward) use ($modules) { $metadata = $modules[$forward['module']]['metadata']; $dispatcher->setModuleName($forward['module']); $dispatcher->setNamespaceName($metadata['controllersNamespace']); } ); $dispatcher = new Dispatcher(); $dispatcher->setDI($di); $dispatcher->setEventsManager($manager); $di->set('dispatcher', $dispatcher); $dispatcher->forward( [ 'module' => 'backend', 'controller' => 'posts', 'action' => 'index', ] ); echo $dispatcher->getModuleName(); // will display properly 'backend' ``` ## 准备参数 感谢`Phalcon\Mvc\Dispatcher`提供的挂钩点,您可以轻松地将您的应用程序调整为任何URL架构;即您可能希望您的网址如下所示:`http://example.com/controller/key1/value1/key2/value`。由于参数是按照在操作的URL中定义的顺序传递的,因此您可以将它们转换为采用所需的模式: ```php set( 'dispatcher', function () { // Create an EventsManager $eventsManager = new EventsManager(); // Attach a listener $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { $params = $dispatcher->getParams(); $keyParams = []; // Use odd parameters as keys and even as values foreach ($params as $i => $value) { if ($i & 1) { // Previous param $key = $params[$i - 1]; $keyParams[$key] = $value; } } // Override parameters $dispatcher->setParams($keyParams); } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } ); ``` 如果所需的架构是:`http://example.com/controller/key1:value1/key2:value`,则需要以下代码: ```php set( 'dispatcher', function () { // Create an EventsManager $eventsManager = new EventsManager(); // Attach a listener $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { $params = $dispatcher->getParams(); $keyParams = []; // Explode each parameter as key,value pairs foreach ($params as $number => $value) { $parts = explode(':', $value); $keyParams[$parts[0]] = $parts[1]; } // Override parameters $dispatcher->setParams($keyParams); } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } ); ``` ## 获取参数 当路由提供命名参数时,您可以在控制器,视图或任何其他继承`Phalcon\Di\Injectable`的组件中接收它们。 ```php dispatcher->getParam('title'); // Get the post's year passed in the URL as parameter // or prepared in an event also filtering it $year = $this->dispatcher->getParam('year', 'int'); // ... } } ``` ## 准备动作 您还可以在调度循环之`前`为操作定义任意模式。 ### 操作名称驼峰化 如果原始网址为:`http://example.com/admin/products/show-latest-products`,例如,您希望将`show-latest-products`传递给`ShowLatestProducts`,则需要以下代码: ```php set( 'dispatcher', function () { // Create an EventsManager $eventsManager = new EventsManager(); // Camelize actions $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { $dispatcher->setActionName( Text::camelize($dispatcher->getActionName()) ); } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } ); ``` ### 删除遗留的扩展名 如果原始URL始终包含`.php`扩展名: ```php http://example.com/admin/products/show-latest-products.php http://example.com/admin/products/index.php ``` 您可以在调度控制器/操作组合之前将其删除: ```php set( 'dispatcher', function () { // Create an EventsManager $eventsManager = new EventsManager(); // Remove extension before dispatch $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { $action = $dispatcher->getActionName(); // Remove extension $action = preg_replace('/\.php$/', '', $action); // Override action $dispatcher->setActionName($action); } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } ); ``` ### 注入模型实例 在此示例中,开发人员希望检查操作将接收的参数,以便动态注入模型实例。 控制器看起来像: ```php view->post = $post; } } ``` 方法`showAction`接收模型`\Posts`的一个实例,开发人员可以在调度准备参数的动作之前检查这个: ```php set( 'dispatcher', function () { // Create an EventsManager $eventsManager = new EventsManager(); $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { // Possible controller class name $controllerName = $dispatcher->getControllerClass(); // Possible method name $actionName = $dispatcher->getActiveMethod(); try { // Get the reflection for the method to be executed $reflection = new ReflectionMethod($controllerName, $actionName); $parameters = $reflection->getParameters(); // Check parameters foreach ($parameters as $parameter) { // Get the expected model name $className = $parameter->getClass()->name; // Check if the parameter expects a model instance if (is_subclass_of($className, Model::class)) { $model = $className::findFirstById($dispatcher->getParams()[0]); // Override the parameters by the model instance $dispatcher->setParams([$model]); } } } catch (Exception $e) { // An exception has occurred, maybe the class or action does not exist? } } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } ); ``` 上面的例子已经简化了。开发人员可以改进它,以便在执行之前在操作中注入任何类型的依赖关系或模型。 从3.1.x开始,调度程序还提供了一个选项,可以通过使用`Phalcon\Mvc\Model\Binder`在内部处理传递到控制器操作的所有模型。 ```php use Phalcon\Mvc\Dispatcher; use Phalcon\Mvc\Model\Binder; $dispatcher = new Dispatcher(); $dispatcher->setModelBinder(new Binder()); return $dispatcher; ``` >[warning] 由于Binder对象使用内部可能很重的Reflection Api,因此可以设置缓存。这可以通过在`setModelBinder()`中使用第二个参数来完成,该参数也可以接受服务名称或仅通过将缓存实例传递给Binder构造函数。 它还引入了一个新接口`Phalcon\Mvc\Model\Binder\BindableInterface` ,它允许您定义控制器关联模型,以允许模型在基本控制器中绑定。 例如,你有一个 `PostsController`继承于基础`CrudController`。你的`CrudController`看起来像这样: ```php use Phalcon\Mvc\Controller; use Phalcon\Mvc\Model; class CrudController extends Controller { /** * Show action * * @param Model $model */ public function showAction(Model $model) { $this->view->model = $model; } } ``` 在`PostsController`中,您需要定义控制器与哪个模型相关联。这是通过实现`Phalcon\Mvc\Model\Binder\BindableInterface`来完成的,它将添加`getModelName()`方法,您可以从中返回模型名称。它只返回一个模型名称或关联数组的字符串,其中key是参数名称。 ```php use Phalcon\Mvc\Model\Binder\BindableInterface; use Models\Posts; class PostsController extends CrudController implements BindableInterface { public static function getModelName() { return Posts::class; } } ``` 通过声明与`PostsController`关联的模型,绑定器可以在将定义的模型传递到父显示操作之前检查控制器的`getModelName()`方法。 如果您的项目结构不使用任何父控制器,您当然仍然可以将模型直接绑定到控制器操作中: ```php use Phalcon\Mvc\Controller; use Models\Posts; class PostsController extends Controller { /** * Shows posts * * @param Posts $post */ public function showAction(Posts $post) { $this->view->post = $post; } } ``` >[warning] 目前,活页夹仅使用模型主键来执行`findFirst()`。以上的示例路线是`/posts/show/{1}` ## 处理 Not-Found 异常 使用EventsManager,可以在调度程序在未找到控制器/操作组合时抛出异常时插入挂钩点: ```php setShared( 'dispatcher', function () { // Create an EventsManager $eventsManager = new EventsManager(); // Attach a listener $eventsManager->attach( 'dispatch:beforeException', function (Event $event, $dispatcher, Exception $exception) { // Handle 404 exceptions if ($exception instanceof DispatchException) { $dispatcher->forward( [ 'controller' => 'index', 'action' => 'show404', ] ); return false; } // Alternative way, controller or action doesn't exist switch ($exception->getCode()) { case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND: case Dispatcher::EXCEPTION_ACTION_NOT_FOUND: $dispatcher->forward( [ 'controller' => 'index', 'action' => 'show404', ] ); return false; } } ); $dispatcher = new MvcDispatcher(); // Bind the EventsManager to the dispatcher $dispatcher->setEventsManager($eventsManager); return $dispatcher; } ); ``` 当然,这个方法可以移动到独立的插件类上,允许多个类在调度循环中产生异常时采取操作: ```php forward( [ 'controller' => 'index', 'action' => $action, ] ); return false; } } ``` >[danger] 只有调度程序生成的异常和执行的操作中产生的异常才会在`beforeException`事件中得到通知。在侦听器或控制器事件中生成的异常将重定向到最新的try/catch。 ## 实现自己的Dispatcher 必须实现 `Phalcon\Mvc\DispatcherInterface` 接口才能创建自己的调度程序,替换Phalcon提供的调度程序。
';

控制器

最后更新于:2022-04-02 05:15:11

[TOC] # 控制器 ## 使用控制器 操作是处理请求的控制器上的方法。默认情况下,控制器上的所有公共方法都映射到操作,并且可由URL访问。操作负责解释请求和创建响应。通常,响应采用渲染视图的形式,但也有其他方法可以创建响应。 例如,当您访问这样的URL时:`http://localhost/blog/posts/show/2015/the-post-title` 默认情况下,Phalcon将分解每个部分,如下所示: | 描述 | Slug | | --------------------- | -------------- | | **Phalcon目录** | blog | | **Controller** | posts | | **Action** | show | | **Parameter** | 2015 | | **Parameter** | the-post-title | 在这种情况下,`PostsController`将处理此请求。将控制器放在应用程序中没有特殊的位置,可以使用 `Phalcon\Loader`加载它们,因此您可以根据需要自由组织控制器。 控制器必须具有后缀`Controller`,同时操作后缀`Action`。控制器的样本如下: ```php dispatcher->getParam('year'); $postTitle = $this->dispatcher->getParam('postTitle'); } } ``` ## 调度循环 调度循环将在Dispatcher中执行,直到没有剩余的动作要执行。在前面的示例中,只执行了一个操作。现在我们将看到`forward()` 方法如何通过将执行转发到不同的控制器/操作来在分派循环中提供更复杂的操作流。 ```php flash->error( "You don't have permission to access this area" ); // Forward flow to another action $this->dispatcher->forward( [ 'controller' => 'users', 'action' => 'signin', ] ); } } ``` 如果用户没有访问某个操作的权限,那么他们将被转发到`UsersController`中的 `signin` 操作。 ```php settings = [ 'mySetting' => 'value', ]; } public function saveAction() { if ($this->settings['mySetting'] === 'value') { // ... } } } ``` >[warning] 仅当`beforeExecuteRoute`事件成功执行时,才会调用`initialize()`方法。这样可以避免初始化程序中的应用程序逻辑在未经授权的情况下执行。 ```php [warning] 请注意,即使控制器中不存在要执行的操作或用户无权访问它(根据开发人员提供的自定义控件访问权限),也会执行`onConstruct()`方法。 ## 注入服务 如果控制器扩展`Phalcon\Mvc\Controller`,则可以轻松访问应用程序中的服务容器。例如,如果我们注册了这样的服务: ```php set( 'storage', function () { return new Storage( '/some/directory' ); }, true ); ``` 然后,我们可以通过多种方式访问​​该服务: ```php storage->save('/some/file'); // Accessing the service from the DI $this->di->get('storage')->save('/some/file'); // Another way to access the service using the magic getter $this->di->getStorage()->save('/some/file'); // Another way to access the service using the magic getter $this->getDi()->getStorage()->save('/some/file'); // Using the array-syntax $this->di['storage']->save('/some/file'); } } ``` 如果您将Phalcon用作全栈框架,则可以阅读框架中默认提供的服务。 ## 请求和响应 假设框架提供了一组预先注册的服务。我们将解释如何与HTTP环境进行交互。`request`服务包含`Phalcon\Http\Request` 的实例,`response` 包含`Phalcon\Http\Response` ,表示将要发送回客户端的内容。 ```php request->isPost()) { // Access POST data $customerName = $this->request->getPost('name'); $customerBorn = $this->request->getPost('born'); } } } ``` 响应对象通常不直接使用,但是在执行操作之前构建,有时 - 就像在`afterDispatch`事件中一样 - 直接访问响应会很有用: ```php response->setStatusCode(404, 'Not Found'); } } ``` 在文档`请求`和`响应`中了解有关HTTP环境的更多信息。 ## 会话数据 会话帮助我们维护请求之间的持久数据。您可以从任何控制器访问`Phalcon\Session\Bag`以封装需要持久化的数据: ```php persistent->name = 'Michael'; } public function welcomeAction() { echo 'Welcome, ', $this->persistent->name; } } ``` ## 使用服务作为控制器 服务可以充当控制器,始终从服务容器请求控制器类。因此,使用其名称注册的任何其他类都可以轻松替换控制器: ```php set( 'IndexController', function () { $component = new Component(); return $component; } ); // Register a namespaced controller as a service $di->set( 'Backend\Controllers\IndexController', function () { $component = new Component(); return $component; } ); ``` ## 控制器中的事件 控制器自动充当`调度`程序事件的侦听器,使用这些事件名称实现方法允许您在执行操作之前/之后实现挂钩点: ```php getActionName() === 'save') { $this->flash->error( "You don't have permission to save posts" ); $this->dispatcher->forward( [ 'controller' => 'home', 'action' => 'index', ] ); return false; } } public function afterExecuteRoute($dispatcher) { // Executed after every found action } } ```
';

注解解析器

最后更新于:2022-04-02 05:15:09

[TOC] # 注解解析器 这是第一次用C语言为PHP世界编写注解解析器组件。`Phalcon\Annotations`是一个通用组件,可以在PHP类中轻松解析和缓存注解,以便在应用程序中使用。 从类,方法和属性中的docblock读取注解。注解可以放在docblock中的任何位置: ```php 'annotations', 'lifetime' => '3600', 'adapter' => 'memory', // Load the Memory adapter ]; $annotations = Factory::load($options); ``` 在处理从配置文件中实例化注解适配器时,Factory加载程序提供了更大的灵活性。 ## 阅读注解 实现反射器以使用面向对象的接口轻松获取在类上定义的注解: ```php get('Example'); // Read the annotations in the class' docblock $annotations = $reflector->getClassAnnotations(); // Traverse the annotations foreach ($annotations as $annotation) { // Print the annotation name echo $annotation->getName(), PHP_EOL; // Print the number of arguments echo $annotation->numberArguments(), PHP_EOL; // Print the arguments print_r($annotation->getArguments()); } ``` 注解读取过程非常快,但出于性能原因,建议使用适配器存储解析的注解。适配器缓存已处理的注解,无需一次又一次地解析注解。 在上面的示例中使用了`Phalcon\Annotations\Adapter\Memory` 。此适配器仅在请求运行时缓存注解,因此适配器更适合开发。当应用程序处于生产阶段时,还有其他适配器可以换出。 ## 注解的类型 注解可能有参数或没有。参数可以是简单的文字(字符串,数字,布尔值,空),数组,散列列表或其他注解: ```php attach( 'dispatch', new CacheEnablerPlugin() ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; }; ``` `CacheEnablerPlugin` 是一个插件,它拦截调度程序中执行的每个操作,如果需要,启用缓存: ```php annotations->getMethod( $dispatcher->getControllerClass(), $dispatcher->getActiveMethod() ); // Check if the method has an annotation 'Cache' if ($annotations->has('Cache')) { // The method has the annotation 'Cache' $annotation = $annotations->get('Cache'); // Get the lifetime $lifetime = $annotation->getNamedParameter('lifetime'); $options = [ 'lifetime' => $lifetime, ]; // Check if there is a user defined cache key if ($annotation->hasNamedParameter('key')) { $options['key'] = $annotation->getNamedParameter('key'); } // Enable the cache for the current method $this->view->cache($options); } } } ``` 现在,我们可以在控制器中使用注解: ```php view->article = Articles::find(); } /** * This is a comment * * @Cache(key='my-key', lifetime=86400) */ public function showAction($slug) { $this->view->article = Articles::findFirstByTitle($slug); } } ``` ### 带注解的私人/公共区域 您可以使用注解告诉ACL哪些控制器属于管理区域: ```php getControllerClass(); // Possible method name $actionName = $dispatcher->getActiveMethod(); // Get annotations in the controller class $annotations = $this->annotations->get($controllerName); // The controller is private? if ($annotations->getClassAnnotations()->has('Private')) { // Check if the session variable is active? if (!$this->session->get('auth')) { // The user is no logged redirect to login $dispatcher->forward( [ 'controller' => 'session', 'action' => 'login', ] ); return false; } } // Continue normally return true; } } ``` ## 注解适配器 该组件使用适配器来缓存或不缓存已解析和处理的注解,从而提高性能或为开发/测试提供便利: | 类 | 描述 | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Phalcon\Annotations\Adapter\Memory` | 注解仅缓存在内存中。当请求结束时,清除缓存,重新加载每个请求中的注解。此适配器适用于开发阶段 | | `Phalcon\Annotations\Adapter\Files` | 经过解析和处理的注解将永久存储在PHP文件中,从而提高性能。此适配器必须与字节码缓存一起使用。 | | `Phalcon\Annotations\Adapter\Apc` | 经过解析和处理的注解会永久存储在APC高速缓存中,从而提高性能。这是更快的适配器 | | `Phalcon\Annotations\Adapter\Xcache` | 经过解析和处理的注解会永久存储在XCache缓存中,从而提高性能。这也是一个快速适配器 | ### 实现自己的适配器 必须实现`Phalcon\Annotations\AdapterInterface`接口才能创建自己的注解适配器或扩展现有注解适配器。 ## 外部资源 * [教程:使用Annotations创建自定义模型的初始化程序](https://blog.phalconphp.com/post/tutorial-creating-a-custom-models-initializer)
';

访问控制列表(ACL)

最后更新于:2022-04-02 05:15:06

[TOC] # 访问控制列表(ACL) `Phalcon\Acl` 提供简单轻量级的ACL管理以及附加的权限。[访问控制列表](http://en.wikipedia.org/wiki/Access_control_list)(ACL)允许应用程序控制对其区域和来自请求的底层对象的访问。建议您阅读有关ACL方法的更多信息,以便熟悉其概念。 总之,ACL具有角色和资源。资源是遵守ACL为其定义的权限的对象。角色是请求访问资源的对象,ACL机制可以允许或拒绝访问。 ## 创建ACL 该组件最初用于内存。这提供了易用性和访问列表的每个方面的速度。`Phalcon\Acl` 构造函数将第一个参数作为用于检索与控制列表相关的信息的适配器。使用内存适配器的示例如下: ```php setDefaultAction( Acl::DENY ); ``` ## 将角色添加到ACL 角色是可以或不可以访问访问列表中的某些资源的对象。例如,我们将角色定义为组织中的人员组。`Phalcon\Acl\Role` 类可用于以更结构化的方式创建角色。让我们在最近创建的列表中添加一些角色: ```php addRole($roleGuests); // Add 'Designers' role to ACL without a Phalcon\Acl\Role $acl->addRole('Designers'); ``` 如您所见,角色是直接定义的,不使用实例。 ## 添加资源 资源是控制访问的对象。通常在MVC应用程序中,资源是指控制器。虽然这不是强制性的,但 `Phalcon\Acl\Resource` 类可用于定义资源。将相关操作或操作添加到资源非常重要,这样ACL才能理解应该控制的内容。 ```php addResource( $customersResource, 'search' ); $acl->addResource( $customersResource, [ 'create', 'update', ] ); ``` ## 定义访问控制 现在我们有角色和资源,是时候定义ACL(即哪些角色可以访问哪些资源)。这部分非常重要,特别是考虑到您的默认访问级别`allow`或`deny`。 ```php allow('Guests', 'Customers', 'search'); $acl->allow('Guests', 'Customers', 'create'); $acl->deny('Guests', 'Customers', 'update'); ``` `allow()`方法指定特定角色已授予对特定资源的访问权限。 `deny()` 方法恰恰相反。 ## 查询ACL 列表完全定义后。我们可以查询它以检查角色是否具有给定的权限。 ```php isAllowed('Guests', 'Customers', 'edit'); // Returns 1 $acl->isAllowed('Guests', 'Customers', 'search'); // Returns 1 $acl->isAllowed('Guests', 'Customers', 'create'); ``` ## 基于功能的访问 您还可以添加自定义函数作为第4个参数,该函数必须返回布尔值。使用`isAllowed()`方法时将调用它。您可以将参数作为关联数组传递给`isAllowed()`方法作为第4个参数,其中key是我们定义的函数中的参数名称。 ```php allow( 'Guests', 'Customers', 'search', function ($a) { return $a % 2 === 0; } ); // Check whether role has access to the operation with custom function // Returns true $acl->isAllowed( 'Guests', 'Customers', 'search', [ 'a' => 4, ] ); // Returns false $acl->isAllowed( 'Guests', 'Customers', 'search', [ 'a' => 3, ] ); ``` 此外,如果您未在`isAllowed()`方法中提供任何参数,则默认行为将为`Acl::ALLOW`。您可以使用方法`setNoArgumentsDefaultAction()`更改它。 ```php allow( 'Guests', 'Customers', 'search', function ($a) { return $a % 2 === 0; } ); // Check whether role has access to the operation with custom function // Returns true $acl->isAllowed( 'Guests', 'Customers', 'search' ); // Change no arguments default action $acl->setNoArgumentsDefaultAction( Acl::DENY ); // Returns false $acl->isAllowed( 'Guests', 'Customers', 'search' ); ``` ## 对象作为角色名称和资源名称 您可以将对象作为`roleName`和`resourceName`传递。您的类必须为`roleName`实现`Phalcon\Acl\RoleAware`,为`resourceName`实现`Phalcon\Acl\ResourceAware`。 我们的 `UserRole` 类 ```php id = $id; $this->roleName = $roleName; } public function getId() { return $this->id; } // Implemented function from RoleAware Interface public function getRoleName() { return $this->roleName; } } ``` 我们的 `ModelResource` 类 ```php id = $id; $this->resourceName = $resourceName; $this->userId = $userId; } public function getId() { return $this->id; } public function getUserId() { return $this->userId; } // Implemented function from ResourceAware Interface public function getResourceName() { return $this->resourceName; } } ``` 然后你可以在`isAllowed()`方法中使用它们。 ```php allow('Guests', 'Customers', 'search'); $acl->allow('Guests', 'Customers', 'create'); $acl->deny('Guests', 'Customers', 'update'); // Create our objects providing roleName and resourceName $customer = new ModelResource( 1, 'Customers', 2 ); $designer = new UserRole( 1, 'Designers' ); $guest = new UserRole( 2, 'Guests' ); $anotherGuest = new UserRole( 3, 'Guests' ); // Check whether our user objects have access to the operation on model object // Returns false $acl->isAllowed( $designer, $customer, 'search' ); // Returns true $acl->isAllowed( $guest, $customer, 'search' ); // Returns true $acl->isAllowed( $anotherGuest, $customer, 'search' ); ``` 您还可以在`allow()`或 `deny()`中访问自定义函数中的这些对象。它们按函数类型自动绑定到参数。 ```php allow( 'Guests', 'Customers', 'search', function (UserRole $user, ModelResource $model) { // User and Model classes are necessary return $user->getId == $model->getUserId(); } ); $acl->allow( 'Guests', 'Customers', 'create' ); $acl->deny( 'Guests', 'Customers', 'update' ); // Create our objects providing roleName and resourceName $customer = new ModelResource( 1, 'Customers', 2 ); $designer = new UserRole( 1, 'Designers' ); $guest = new UserRole( 2, 'Guests' ); $anotherGuest = new UserRole( 3, 'Guests' ); // Check whether our user objects have access to the operation on model object // Returns false $acl->isAllowed( $designer, $customer, 'search' ); // Returns true $acl->isAllowed( $guest, $customer, 'search' ); // Returns false $acl->isAllowed( $anotherGuest, $customer, 'search' ); ``` 您仍然可以在函数中添加任何自定义参数,并在`isAllowed()`方法中传递关联数组。订单也无所谓。 ## 角色继承 您可以使用`Phalcon\Acl\Role`提供的继承来构建复杂的角色结构。角色可以从其他角色继承,从而允许访问超集或资源子集。要使用角色继承,在列表中添加该角色时,需要将继承的角色作为方法调用的第二个参数传递。 ```php addRole($roleGuests); // Add 'Administrators' role inheriting from 'Guests' its accesses $acl->addRole($roleAdmins, $roleGuests); ``` ## 序列化ACL列表 为了提高性能,可以将 `Phalcon\Acl` 实例序列化并存储在APC,会话,文本文件或数据库表中,以便可以随意加载它们,而无需重新定义整个列表。你可以这样做: ```php isAllowed('Guests', 'Customers', 'edit')) { echo 'Access granted!'; } else { echo 'Access denied :('; } ``` 建议在开发期间使用Memory适配器,并在生产中使用其他适配器之一。 ## 事件 `Phalcon\Acl` 能够将事件发送到`EventsManager`(如果存在)。使用“acl”类型触发事件。返回布尔值false时的某些事件可能会停止活动操作。支持以下事件: | 事件名称 | 触发 | Can stop operation? | | ----------------- | ------------------------------------------------------- |:-------------------:| | beforeCheckAccess | 在检查角色/资源是否具有访问权限之前触发 | Yes | | afterCheckAccess | 在检查角色/资源是否具有访问权限之后触发 | No | 以下示例演示如何将侦听器附加到此组件: ```php attach( 'acl:beforeCheckAccess', function (Event $event, $acl) { echo $acl->getActiveRole(); echo $acl->getActiveResource(); echo $acl->getActiveAccess(); } ); $acl = new AclList(); // Setup the $acl // ... // Bind the eventsManager to the ACL component $acl->setEventsManager($eventsManager); ``` ## 实现自己的适配器 必须实现`Phalcon\Acl\AdapterInterface`接口才能创建自己的ACL适配器或扩展现有ACL适配器。
';