事件管理器¶
概览¶
此组件的目的是通过创建拦截点来截获框架中组件的执行。拦截点。这些拦截点允许开发人员获取状态信息、操作数据,或在组件执行过程中改变执行流程。该组件由一个Phalcon\Events\Manager管理事件传播和事件执行的Phalcon\Events\Event对象,其中包含每个拦截点/事件的信息。
<?php
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;
$eventsManager = new EventsManager();
$eventsManager->attach(
'db:afterQuery',
function (Event $event, $connection) {
echo $connection->getSQLStatement();
}
);
$connection = new DbAdapter(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
$connection->setEventsManager($eventsManager);
$connection->query(
'SELECT * FROM products p WHERE p.status = 1'
);
命名约定¶
Phalcon 的事件使用命名空间以避免命名冲突。Phalcon 中的每个组件都占据不同的事件命名空间,您可以根据需要自由创建自己的命名空间。事件名称格式为component:event
。例如,Phalcon\Db占据了db
命名空间,其afterQuery
事件全名为db:afterQuery
.
在将事件监听器附加到事件管理器时,可以使用component
来捕获来自该组件的所有事件(例如db
来捕获所有Phalcon\Db事件),或者component:event
来定位特定事件(例如db:afterQuery
)。
管理器¶
The Phalcon\Events\Manager是一个负责处理 Phalcon 中所有事件的主要组件。其他框架中的不同实现可能将此组件称为处理器。不管名称如何,功能和目的都是相同的。
该组件使用SplPriorityQueue内部封装了一个对象队列。它以一定优先级(默认100
)注册这些对象,然后在适当时机执行它们。
管理器暴露的方法包括:
将监听器附加到事件管理器。其中handler
是一个对象或callable
. 返回优先级是否启用 告知事件管理器是否需要收集所有已注册监听器在单次fire
调用中返回的响应 从事件管理器中分离监听器 从EventsManager中移除所有事件 设置事件管理器是否启用优先级(默认false
)。 在事件管理器中触发事件,导致活跃的监听器接收到该通知 final public function fireQueue(SplPriorityQueue $queue, EventInterface $event): mixed
```
Internal handler to call a queue of events
```php
public function getListeners(string $type): array
fire
执行 检查某类事件是否有监听器 检查事件管理器是否正在收集所有已注册监听器在单次fire
检查处理器是对象还是可调用函数 使用¶
如果你使用的是Phalcon\Di\FactoryDefaultDI 容器,即Phalcon\Events\Manager已经为您以名称eventsManager
。这是一个全局事件管理器。不过,您并不局限于只使用这一个管理器。您可以始终创建一个单独的管理器来处理任意所需组件的事件。
下面的示例展示了如何使用全局事件管理器来创建查询日志机制:
<?php
use Phalcon\Di\FactoryDefault;
use Phalcon\Events\Event;
use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;
$container = Di::getDefault();
$eventsManager = $container->get('eventsManager');
$eventsManager->attach(
'db:afterQuery',
function (Event $event, $connection) {
echo $connection->getSQLStatement();
}
);
$connection = new DbAdapter(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
$connection->setEventsManager($eventsManager);
$connection->query(
'SELECT * FROM products p WHERE p.status = 1'
);
或者如果您想要一个独立的事件管理器:
<?php
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;
$eventsManager = new EventsManager();
$eventsManager->attach(
'db:afterQuery',
function (Event $event, $connection) {
echo $connection->getSQLStatement();
}
);
$connection = new DbAdapter(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
$connection->setEventsManager($eventsManager);
$connection->query(
'SELECT * FROM products p WHERE p.status = 1'
);
在上面的例子中,我们使用事件管理器监听由afterQuery
服务产生的db
事件,在本例中为 MySQL。我们使用attach
方法将我们的事件附加到管理器并监听db:afterQuery
事件。我们添加了一个匿名函数作为该事件的处理程序,它接受一个Phalcon\Events\Event作为第一个参数。这个对象包含了该事件触发时的上下文信息。数据库连接对象作为第二个参数。通过连接变量我们可以打印出 SQL 语句。您还可以随时传递第三个参数,携带与事件相关的任意数据,甚至可以在匿名函数中传入一个日志记录器对象,以便将您的查询记录到单独的日志文件中。
注意
您必须使用setEventsManager()
方法显式地将事件管理器设置给某个组件,这样该组件才能触发事件。您可以为每个组件创建一个新的事件管理器实例,也可以将同一个事件管理器设置给多个组件,因为命名约定会避免冲突。
处理程序¶
事件管理器将一个处理器绑定到一个事件。当事件触发时,处理器是一段用来执行某些操作的代码。如上例所示,您可以使用匿名函数作为您的处理器:
<?php
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;
$eventsManager = new EventsManager();
$eventsManager->attach(
'db:afterQuery',
function (Event $event, $connection) {
echo $connection->getSQLStatement();
}
);
$connection = new DbAdapter(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
$connection->setEventsManager($eventsManager);
$connection->query(
'SELECT * FROM products p WHERE p.status = 1'
);
您还可以创建一个监听器类,这种形式提供了更多灵活性。在一个监听器中,您可以监听多个事件,甚至可以继承 [Phalcon\Di\Injectable][di-injectable],从而获得对 DI 容器中服务的完整访问权限。可以通过实现下面的监听器来增强上面的例子:
<?php
namespace MyApp\Listeners;
use Phalcon\Logger;
use Phalcon\Config;
use Phalcon\Db\AdapterInterface;
use Phalcon\Di\Injectable;
use Phalcon\Events\Event;
/**
* Class QueryListener
*
* @property Config $config
* @property Logger $logger
*/
class QueryListener extends Injectable
{
public function beforeQuery(Event $event, AdapterInterface $connection)
{
if ($this->config->path('app.logLevel') > 1) {
$this->logger->info(
sprintf(
'%s - [%s]',
$connection->getSQLStatement(),
json_encode($connection->getSQLVariables())
)
);
}
}
public function rollbackTransaction(Event $event)
{
if ($this->config->path('app.logLevel') > 1) {
$this->logger->warning($event->getType());
}
}
}
将监听器附加到我们的事件管理器非常简单:
最终的行为将是,如果app.logLevel
配置变量被设为大于1
(表示我们处于开发模式),则所有查询及其实际绑定参数都将被记录。此外,每次事务回滚时也将进行日志记录。
另一个有用的监听器是404
监听器:
<?php
namespace MyApp\Listeners\Dispatcher;
use Phalcon\Logger;
use Phalcon\Di\Injectable;
use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
use MyApp\Auth\Adapters\AbstractAdapter;
/**
* Class NotFoundListener
*
* @property AbstractAdapter $auth
* @property Logger $logger
*/
class NotFoundListener extends Injectable
{
public function beforeException(
Event $event,
Dispatcher $dispatcher,
\Exception $ex
) {
switch ($ex->getCode()) {
case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
$dispatcher->setModuleName('main');
$params = [
'namespace' => 'MyApp\Controllers',
'controller' => 'session',
'action' => 'fourohfour',
];
/**
* 404 not logged in
*/
if (true !== $this->auth->isLoggedIn()) {
$params['action'] = 'login';
}
$dispatcher->forward($params);
return false;
default:
$this->logger->error($ex->getMessage());
$this->logger->error($ex->getTraceAsString());
return false;
}
}
}
并将其附加到事件管理器:
首先,我们将监听器附加到dispatcher
组件和beforeException
事件。这意味着事件管理器仅在该事件触发时调用我们的监听器。我们也可以将钩子点更改为dispatcher
,以便将来可以在同一监听器中添加更多的调度器事件。
The beforeException
函数接受$event
作为第一个参数,$dispatcher
作为第二个参数,$ex
从调度器组件抛出的异常。利用这些信息,我们可以确定是否找不到处理器(或控制器)或动作。如果是这种情况,我们就将用户转发到特定的模块、控制器和动作。如果用户未登录,则发送他们到登录页面。或者,我们只是将异常信息记录到日志器中。
该示例清楚地展示了事件管理器的功能,以及如何使用监听器改变应用程序的流程。
事件:触发¶
您可以在应用程序中创建向事件管理器触发事件的组件。当事件被触发时,附加到这些事件的监听器将被调用。为了创建一个可以触发事件的组件,我们需要实现以下接口:Phalcon\Events\EventsAwareInterface.
自定义组件¶
考虑以下示例:
<?php
namespace MyApp\Components;
use Phalcon\Di\Injectable;
use Phalcon\Events\EventsAwareInterface;
use Phalcon\Events\ManagerInterface;
/**
* @property ManagerInterface $eventsManager
* @property Logger $logger
*/
class NotificationsAware extends Injectable implements EventsAwareInterface
{
protected $eventsManager;
public function getEventsManager()
{
return $this->eventsManager;
}
public function setEventsManager(ManagerInterface $eventsManager)
{
$this->eventsManager = $eventsManager;
}
public function process()
{
$this->eventsManager->fire('notifications:beforeSend', $this);
$this->logger->info('Processing.... ');
$this->eventsManager->fire('notifications:afterSend', $this);
}
}
上述组件实现了Phalcon\Events\EventsAwareInterface接口,并且因此使用了getEventsManager
和setEventsManager
。最后一个方法是执行工作的部分。在这个示例中,我们想向用户发送一些通知,并希望在通知发送前后触发事件。
我们选择将组件命名为notification
,事件被命名为beforeSend
和afterSend
。在process
方法中,你可以在调用触发相关事件之间添加所需的任何代码。此外,你可以在此组件中注入更多有助于实现和处理通知的数据。
自定义监听器¶
现在我们需要为此组件创建一个监听器:
<?php
namespace MyApp\Listeners;
use Phalcon\Events\Event;
use Phalcon\Logger;
/**
* @property Logger $logger
*/
class MotificationsListener
{
/**
* @var Logger
*/
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function afterSend(
Event $event,
NotificationsAware $component
) {
$this->logger->info('After Notification');
}
public function beforeSend(
Event $event,
NotificationsAware $component
) {
$this->logger->info('Before Notification');
}
}
将所有内容整合在一起
<?php
use MyApp\Components\NotificationAware;
use MyApp\Listeners\MotificationsListener;
use Phalcon\Events\Manager as EventsManager;
$eventsManager = new EventsManager();
$component = new NotificationAware();
$component->setEventsManager($eventsManager);
$eventsManager->attach(
'notifications',
new NotificationsListener()
);
$component->process();
当process
被执行时,监听器中的两个方法将被执行。你的日志将包含以下条目:
[2019-12-25 01:02:03][INFO] Before Notification
[2019-12-25 01:02:03][INFO] Processing...
[2019-12-25 01:02:03][INFO] After Notification
自定义数据¶
在使用fire()
:
<?php
$data = [
'name' => 'Darth Vader',
'password' => '12345',
];
$eventsManager->fire('notifications:afterSend', $this, $data);
在监听器中,第三个参数也会接收到数据:
<?php
use Phalcon\Events\Event;
$data = [
'name' => 'Darth Vader',
'password' => '12345',
];
$eventsManager->attach(
'notifications',
function (Event $event, $component, $data) {
print_r($data);
}
);
$eventsManager->attach(
'notifications',
function (Event $event, $component) {
print_r($event->getData());
}
);
传播¶
一个事件管理器可以有多个附加到它的监听器。一旦事件触发,所有能接收到该特定事件的通知的监听器都会被通知到。这是默认行为,但在需要时可以通过提前停止传播来更改:
<?php
use Phalcon\Events\Event;
$eventsManager->attach(
'db',
function (Event $event, $connection) {
if ('2019-01-01' < date('Y-m-d')) {
$event->stop();
}
}
);
在上述简单示例中,如果今天早于2019-01-01
.
取消¶
默认情况下,所有事件都可以取消。但是,您可能希望将某个特定事件设置为不可取消,使得该特定事件将在所有实现它的可用监听器上执行。
<?php
use Phalcon\Events\Event;
$eventsManager->attach(
'db',
function (Event $event, $connection) {
if ($event->isCancelable()) {
$event->stop();
}
}
);
在上面的例子中,如果事件是可以取消的,我们将停止传播。你可以通过使用不使用fire()
:
The afterSend
事件将不再可取消,并将在所有实现它的监听器上执行。
注意
您可以通过在事件中返回false
来停止执行(但不是总是如此)。例如,如果您将事件附加到dispatch:beforeDispatchLoop
并且您的监听器返回false
分派过程将停止。只有当您只有一个监听器正在监听dispatch:beforeDispatchLoop
事件并返回false
的情况下才成立。如果有两个监听器附加到该事件,而执行的第二个监听器返回true
那么进程将继续执行。如果您希望阻止后续事件触发,则必须在您的监听器中对Event对象发出stop()
in your listener on the Event object.
优先级¶
添加监听器时,可以设置特定的优先级。在将监听器附加到事件管理器时设置优先级定义了它们被调用的顺序:
<?php
use Phalcon\Events\Manager as EventsManager;
$eventsManager = new EventsManager();
$eventsManager->enablePriorities(true);
$eventsManager->attach(
'db',
new QueryListener(),
150
);
$eventsManager->attach(
'db',
new QueryListener(),
100
);
$eventsManager->attach(
'db',
new QueryListener(),
50
);
注意
为了让优先级生效,enablePriorities()
必须与true
一起调用以启用它们。优先级默认是禁用的。
注意
高优先级数字意味着该监听器将在低优先级监听器之前被处理。
响应¶
事件管理器还可以收集每个事件返回的响应,并使用getResponses()
方法将它们返回。该方法返回一个包含响应的数组:
<?php
use Phalcon\Events\Manager as EventsManager;
$eventsManager = new EventsManager();
$eventsManager->collectResponses(true);
$eventsManager->attach(
'custom:custom',
function () {
return 'first response';
}
);
$eventsManager->attach(
'custom:custom',
function () {
return 'second response';
}
);
$eventsManager->fire('custom:custom', $eventsManager, null);
print_r($eventsManager->getResponses());
上面的示例输出如下:
注意
为了让优先级生效,collectResponses()
必须与true
一起调用以启用收集。
异常¶
Paginator 组件中抛出的任何异常都将属于类型Phalcon\Events\Exception。你可以使用此异常选择性地捕获仅从此组件抛出的异常。
<?php
use Phalcon\Events\EventsManager;
use Phalcon\Events\Exception;
try {
$eventsManager = new EventsManager();
$eventsManager->attach('custom:custom', true);
} catch (Exception $ex) {
echo $ex->getMessage();
}
控制器¶
控制器本身已经作为监听器注册在事件管理器中。因此,您只需要创建一个与已注册事件同名的方法,它就会被触发。
例如,如果我们想将未登录的用户引导至/login
页面,我们可以在主控制器中添加以下代码:
<?php
namespace MyApp\Controller;
use Phalcon\Logger;
use Phalcon\Dispatcher;
use Phalcon\Http\Response;
use Phalcon\Mvc\Controller;
use MyApp\Auth\Adapters\AbstractAdapter;
/**
* Class BaseController
*
* @property AbstractAdapter $auth
* @property Logger $logger
* @property Response $response
*/
class BaseController extends Controller
{
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
/**
* Send them to the login page if no identity exists
*/
if (true !== $this->auth->isLoggedIn()) {
$this->response->redirect(
'/login',
true
);
return false;
}
return true;
}
}
模型¶
类似于控制器,模型也已经作为监听器注册在事件管理器中。因此,您只需要创建一个与已注册事件同名的方法,它就会被触发。
在下面的示例中,我们使用beforeCreate
事件来自动生成发票号码:
<?php
namespace MyApp\Models;
use Phalcon\Mvc\Model;
use function str_pad;
/**
* Class Invoices
*
* @property string $inv_created_at
* @property int $inv_cst_id
* @property int $inv_id
* @property string $inv_number
* @property string $inv_title
* @property float $inv_total
*/
class Invoices extends Model
{
/**
* @var int
*/
public $inv_cst_id;
/**
* @var string
*/
public $inv_created_at;
/**
* @var int
*/
public $inv_id;
/**
* @var string
*/
public $inv_number;
/**
* @var string
*/
public $inv_title;
/**
* @var float
*/
public $inv_total;
public function beforeCreate()
{
$date = date('YmdHis');
$customer = substr(
str_pad(
$this->inv_cst_id, 6, '0', STR_PAD_LEFT
),
-6
);
$this->inv_number = 'INV-' . $customer . '-' . $date;
}
}
自定义¶
The Phalcon\Events\ManagerInterface接口必须被实现,以创建自己的事件管理器替换Phalcon提供的事件管理器。
<?php
namespace MyApp\Events;
use Phalcon\Events\ManagerInterface;
class EventsManager implements ManagerInterface
{
/**
* @param string $eventType
* @param object|callable $handler
*/
public function attach(string $eventType, $handler);
/**
* @param string $eventType
* @param object|callable $handler
*/
public function detach(string $eventType, $handler);
/**
* @param string $type
*/
public function detachAll(string $type = null);
/**
* @param string $eventType
* @param object $source
* @param mixed $data
* @param mixed $cancelable
*
* @return mixed
*/
public function fire(
string $eventType,
$source,
$data = null,
bool $cancelable = false
);
/**
* @param string $type
*
* @return array
*/
public function getListeners(string $type): array;
/**
* @param string $type
*
* @return bool
*/
public function hasListeners(string $type): bool;
}
事件列表¶
Phalcon中可用的事件包括:
组件 | 事件 | 参数 |
---|---|---|
ACL | acl:afterCheckAccess | Acl |
ACL | acl:beforeCheckAccess | Acl |
应用程序 | application:afterHandleRequest | 应用程序、控制器 |
应用程序 | application:afterStartModule | 应用程序、模块 |
应用程序 | application:beforeHandleRequest | 应用程序、调度器 |
应用程序 | application:beforeSendResponse | 应用程序、响应 |
应用程序 | application:beforeStartModule | 应用程序、模块 |
应用程序 | application:boot | 应用程序 |
应用程序 | application:viewRender | 应用程序、视图 |
缓存 | cache:afterSet | 缓存 |
缓存 | cache:afterGet | 缓存 |
缓存 | cache:afterHas | 缓存 |
缓存 | cache:afterIncrement | 缓存 |
缓存 | cache:afterDecrement | 缓存 |
缓存 | cache:afterDelete | 缓存 |
缓存 | cache:beforeSet | 缓存 |
缓存 | cache:beforeGet | 缓存 |
缓存 | cache:beforeHas | 缓存 |
缓存 | cache:beforeIncrement | 缓存 |
缓存 | cache:beforeDecrement | 缓存 |
缓存 | cache:beforeDelete | 缓存 |
CLI | dispatch:beforeException | 控制台、异常 |
控制台 | console:afterHandleTask | 控制台、任务 |
控制台 | console:afterStartModule | 控制台、模块 |
控制台 | console:beforeHandleTask | 控制台、调度器 |
控制台 | console:beforeStartModule | 控制台、模块 |
控制台 | console:boot | 控制台 |
数据库 | db:afterQuery | 数据库 |
数据库 | db:beforeQuery | 数据库 |
数据库 | db:beginTransaction | 数据库 |
数据库 | db:createSavepoint | 数据库、保存点名称 |
数据库 | db:commitTransaction | 数据库 |
数据库 | db:releaseSavepoint | 数据库、保存点名称 |
数据库 | db:rollbackTransaction | 数据库 |
数据库 | db:rollbackSavepoint | 数据库、保存点名称 |
分派器 | dispatch:afterBinding | 分派器 |
分派器 | dispatch:afterCallAction | 分派器 |
分派器 | dispatch:afterDispatch | 分派器 |
分派器 | dispatch:afterDispatchLoop | 分派器 |
分派器 | dispatch:afterExecuteRoute | 分派器 |
分派器 | dispatch:afterInitialize | 分派器 |
分派器 | dispatch:beforeDispatch | 分派器 |
分派器 | dispatch:beforeCallAction | 分派器 |
分派器 | dispatch:beforeDispatchLoop | 分派器 |
分派器 | dispatch:beforeException | 调度器、异常 |
分派器 | dispatch:beforeExecuteRoute | 分派器 |
分派器 | dispatch:beforeForward | 调度器、数组(MVC调度器) |
分派器 | dispatch:beforeNotFoundAction | 分派器 |
加载器 | loader:afterCheckClass | 加载器、类名 |
加载器 | loader:beforeCheckClass | 加载器、类名 |
加载器 | loader:beforeCheckPath | 加载器 |
加载器 | loader:pathFound | 加载器、文件路径 |
微型 | micro:afterBinding | 微型 |
微型 | micro:afterHandleRoute | 微服务、混合返回值 |
微型 | micro:afterExecuteRoute | 微型 |
微型 | micro:beforeException | 微服务、异常 |
微型 | micro:beforeExecuteRoute | 微型 |
微型 | micro:beforeHandleRoute | 微型 |
微型 | micro:beforeNotFound | 微型 |
模型 | model:afterCreate | 模型 |
模型 | model:afterDelete | 模型 |
模型 | model:afterFetch | 模型 |
模型 | model:afterSave | 模型 |
模型 | model:afterUpdate | 模型 |
模型 | model:afterValidation | 模型 |
模型 | model:afterValidationOnCreate | 模型 |
模型 | model:afterValidationOnUpdate | 模型 |
模型 | model:beforeDelete | 模型 |
模型 | model:beforeCreate | 模型 |
模型 | model:beforeSave | 模型 |
模型 | model:beforeUpdate | 模型 |
模型 | model:beforeValidation | 模型 |
模型 | model:beforeValidationOnCreate | 模型 |
模型 | model:beforeValidationOnUpdate | 模型 |
模型 | model:notDeleted | 模型 |
模型 | model:notSaved | 模型 |
模型 | model:onValidationFails | 模型 |
模型 | model:prepareSave | 模型 |
模型 | model:validation | 模型 |
模型管理器 | modelsManager:afterInitialize | 管理器、模型 |
请求 | request:afterAuthorizationResolve | 请求、['server' => Server array] |
请求 | request:beforeAuthorizationResolve | 请求、['headers' => [Headers], 'server' => [Server]] |
响应 | response:afterSendHeaders | 响应 |
响应 | response:beforeSendHeaders | 响应 |
路由器 | router:afterCheckRoutes | 路由器 |
路由器 | router:beforeCheckRoutes | 路由器 |
路由器 | router:beforeCheckRoute | 路由器、路由 |
路由器 | router:beforeMount | 路由器、组 |
路由器 | router:matchedRoute | 路由器、路由 |
路由器 | router:notMatchedRoute | 路由器、路由 |
存储 | storage:afterSet | 存储 |
存储 | storage:afterGet | 存储 |
存储 | storage:afterHas | 存储 |
存储 | storage:afterIncrement | 存储 |
存储 | storage:afterDecrement | 存储 |
存储 | storage:afterDelete | 存储 |
存储 | storage:beforeSet | 存储 |
存储 | storage:beforeGet | 存储 |
存储 | storage:beforeHas | 存储 |
存储 | storage:beforeIncrement | 存储 |
存储 | storage:beforeDecrement | 存储 |
存储 | storage:beforeDelete | 存储 |
视图 | view:afterCompile | Volt |
视图 | view:afterRender | 视图 |
视图 | view:afterRenderView | 视图 |
视图 | view:beforeCompile | Volt |
视图 | view:beforeRender | 视图 |
视图 | view:beforeRenderView | 视图、视图引擎路径 |
视图 | view:notFoundView | 视图、视图引擎路径 |
Volt | compileFilter | Volt, [name, arguments, function arguments] |
Volt | compileFunction | Volt, [name, arguments, function arguments] |
Volt | compileStatement | Volt, [语句] |
Volt | resolveExpression | Volt, [表达式] |