Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/yiisoft/yii2 into command…
Browse files Browse the repository at this point in the history
…-txn-retry
  • Loading branch information
sergeymakinen committed Dec 26, 2017
2 parents 7c91044 + 2ba98c6 commit f199b2b
Show file tree
Hide file tree
Showing 15 changed files with 536 additions and 73 deletions.
73 changes: 72 additions & 1 deletion docs/guide/concept-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Attaching Event Handlers <span id="attaching-event-handlers"></span>
You can attach a handler to an event by calling the [[yii\base\Component::on()]] method. For example:

```php
$foo = new Foo;
$foo = new Foo();

// this handler is a global function
$foo->on(Foo::EVENT_HELLO, 'function_name');
Expand Down Expand Up @@ -355,3 +355,74 @@ done through the Singleton (e.g. the application instance).

However, because the namespace of the global events is shared by all parties, you should name the global events
wisely, such as introducing some sort of namespace (e.g. "frontend.mail.sent", "backend.mail.sent").


Wildcard Events <span id="wildcard-events"></span>
---------------

Since 2.0.14 you can setup event handler for multiple events matching wildcard pattern.
For example:

```php
use Yii;

$foo = new Foo();

$foo->on('foo.event.*', function ($event) {
// triggered for any event, which name starts on 'foo.event.'
Yii::trace('trigger event: ' . $event->name);
});
```

Wildcard patterns can be used for class-level events as well. For example:

```php
use yii\base\Event;
use Yii;

Event::on('app\models\*', 'before*', function ($event) {
// triggered for any class in namespace 'app\models' for any event, which name starts on 'before'
Yii::trace('trigger event: ' . $event->name . ' for class: ' . get_class($event->sender));
});
```

This allows you catching all application events by single handler using following code:

```php
use yii\base\Event;
use Yii;

Event::on('*', '*', function ($event) {
// triggered for any event at any class
Yii::trace('trigger event: ' . $event->name);
});
```

> Note: usage wildcards for event handlers setup may reduce the application performance.
It is better to be avoided if possible.

In order to detach event handler specified by wildcard pattern, you should repeat same pattern at
[[yii\base\Component::off()]] or [[yii\base\Event::off()]] invocation. Keep in mind that passing wildcard
during detaching of event handler will detach ony the handler specified for this wildcard, while handlers
attached for regular event names will remain even if they match the pattern. For example:

```php
use Yii;

$foo = new Foo();

// attach regular handler
$foo->on('event.hello', function ($event) {
echo 'direct-handler'
});

// attach wildcard handler
$foo->on('*', function ($event) {
echo 'wildcard-handler'
});

// detach wildcard handler only!
$foo->off('*');

$foo->trigger('event.hello'); // outputs: 'direct-handler'
```
26 changes: 16 additions & 10 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,57 @@ Yii Framework 2 Change Log
2.0.14 under development
------------------------

- Bug #15356: Fixed multiple bugs in `yii\db\Query::getTablesUsedInFrom()` (vladis84, samdark)
- Bug #14157: Add support for loading default value `CURRENT_TIMESTAMP` of MySQL `datetime` field (rossoneri)
- Bug #15046: Throw an `yii\web\HeadersAlreadySentException` if headers were sent before web response (dmirogin)
- Enh #3087: Added `yii\helpers\BaseHtml::error()` "errorMethod" option to be able to customize errors display (yanggs07, developeruz)
- Bug #15355: Fixed `yii\db\Query::from()` does not work with `yii\db\Expression` (vladis84)
- Bug #8983: Only truncate the original log file for rotation (matthewyang, developeruz)
- Bug #14157: Add support for loading default value `CURRENT_TIMESTAMP` of MySQL `datetime` field (rossoneri)
- Bug #14276: Fixed I18N format with dotted parameters (developeruz)
- Bug #14484: Fixed `yii\validators\UniqueValidator` for target classes with a default scope (laszlovl, developeruz)
- Bug #14604: Fixed `yii\validators\CompareValidator` `compareAttribute` does not work if `compareAttribute` form ID has been changed (mikk150)
- Bug #15046: Throw an `yii\web\HeadersAlreadySentException` if headers were sent before web response (dmirogin)
- Bug #15142: Fixed array params replacing in `yii\helpers\BaseUrl::current()` (IceJOKER)
- Bug #15194: Fixed `yii\db\QueryBuilder::insert()` to preserve passed params when building a `INSERT INTO ... SELECT` query for MSSQL, PostgreSQL and SQLite (sergeymakinen)
- Bug #15229: Fixed `yii\console\widgets\Table` default value for `getScreenWidth()`, when `Console::getScreenSize()` can't determine screen size (webleaf)
- Bug #15234: Fixed `\yii\widgets\LinkPager` removed `tag` from `disabledListItemSubTagOptions` (SDKiller)
- Enh #7988: Added `\yii\helpers\Console::errorSummary()` and `\yii\helpers\Json::errorSummary()` (developeruz)
- Bug #15249: Controllers in subdirectories were not visible in commands list (IceJOKER)
- Bug #15270: Resolved potential race conditions when writing generated php-files (kalessil)
- Bug #15301: Fixed `ArrayHelper::filter()` to work properly with `0` in values (hhniao)
- Bug #15302: Fixed `yii\caching\DbCache` so that `getValues` now behaves the same as `getValue` with regards to streams (edwards-sj)
- Bug #15317: Regenerate CSRF token if an empty value is given (sammousa)
- Bug #15320: Fixed special role checks in `yii\filters\AccessRule::matchRole()` (Izumi-kun)
- Bug #15322: Fixed PHP 7.2 compatibility of `FileHelper::getExtensionsByMimeType()` (samdark)
- Bug #15355: Fixed `yii\db\Query::from()` does not work with `yii\db\Expression` (vladis84, silverfire, samdark)
- Bug #15356: Fixed multiple bugs in `yii\db\Query::getTablesUsedInFrom()` (vladis84, samdark)
- Bug #15380: `FormatConverter::convertDateIcuToPhp()` now converts `a` ICU symbols to `A` (brandonkelly)
- Bug #15407: Fixed rendering rows with associative arrays in `yii\console\widgets\Table` (dmrogin)
- Enh #3087: Added `yii\helpers\BaseHtml::error()` "errorSource" option to be able to customize errors display (yanggs07, developeruz, silverfire)
- Enh #3250: Added support for events partial wildcard matching (klimov-paul)
- Enh #5515: Added default value for `yii\behaviors\BlameableBehavior` for cases when the user is guest (dmirogin)
- Enh #7988: Added `\yii\helpers\Console::errorSummary()` and `\yii\helpers\Json::errorSummary()` (developeruz)
- Enh #7996: Short syntax for verb in GroupUrlRule (schojniak, developeruz)
- Enh #8752: Allow specify `$attributeNames` as a string for `yii\base\Model` `validate()` method (developeruz)
- Enh #9137: Added `Access-Control-Allow-Method` header for the OPTIONS request (developeruz)
- Enh #9253: Allow `variations` to be a string for `yii\filters\PageCache` and `yii\widgets\FragmentCache` (schojniak, developeruz)
- Enh #12623: Added `yii\helpers\StringHelper::matchWildcard()` replacing usage of `fnmatch()`, which may be unreliable (klimov-paul)
- Enh #14043: Added `yii\helpers\IpHelper` (silverfire, cebe)
- Enh #7996: Short syntax for verb in GroupUrlRule (schojniak, developeruz)
- Enh #14568: Refactored migration templates to use `safeUp()` and `safeDown()` methods (Kolyunya)
- Enh #15219: Added `yii\filters\auth\HttpHeaderAuth` (bboure)
- Enh #14662: Added support for custom `Content-Type` specification to `yii\web\JsonResponseFormatter` (Kolyunya)
- Enh #15024: `yii\web\Pjax` widget does not prevent CSS files from sending anymore because they are handled by client-side plugin correctly (onmotion)
- Enh #15135: Automatic completion for help in bash and zsh (Valkeru)
- Enh #15219: Added `yii\filters\auth\HttpHeaderAuth` (bboure)
- Enh #15221: Added support for specifying `--camelCase` console options in `--kebab-case` (brandonkelly)
- Enh #15221: Added support for the `--<option> <value>` console option syntax (brandonkelly)
- Enh #15221: Improved the `help/list-action-options` console command output for command options without a description (brandonkelly)
- Enh #15226: Auto generate placeholder from fields (vladis84)
- Enh #15332: Always check for availability of `openssl_pseudo_random_bytes`, even if LibreSSL is available (sammousa)
- Enh #15335: Added `FileHelper::unlink()` that works well under all OSes (samdark)
- Enh #15357: Added multi statement support for `yii\db\sqlite\Command` (sergeymakinen)
- Enh #15347: Add `Instance` support for object property in DI container (kojit2009)
- Enh #15340: Test CHANGELOG.md for valid format (sammousa)
- Enh #15347: Add `Instance` support for object property in DI container (kojit2009)
- Enh #15357: Added multi statement support for `yii\db\sqlite\Command` (sergeymakinen)
- Enh #15360: Refactored `BaseConsole::updateProgress()` (developeruz)
- Bug #15317: Regenerate CSRF token if an empty value is given (sammousa)
- Bug #15380: `FormatConverter::convertDateIcuToPhp()` now converts `a` ICU symbols to `A` (brandonkelly)
- Enh #15415: Added transaction/retry support for `yii\db\Command` (sergeymakinen)

- Enh: Added check to `yii\base\Model::formName()` to prevent source path disclosure when form is represented by an anonymous class (silverfire)


2.0.13.1 November 14, 2017
Expand Down
81 changes: 76 additions & 5 deletions framework/base/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace yii\base;

use Yii;
use yii\helpers\StringHelper;

/**
* Component is the base class that implements the *property*, *event* and *behavior* features.
Expand Down Expand Up @@ -103,6 +104,11 @@ class Component extends BaseObject
* @var array the attached event handlers (event name => handlers)
*/
private $_events = [];
/**
* @var array the event handlers attached for wildcard patterns (event name wildcard => handlers)
* @since 2.0.14
*/
private $_eventWildcards = [];
/**
* @var Behavior[]|null the attached behaviors (behavior name => behavior). This is `null` when not initialized.
*/
Expand Down Expand Up @@ -301,6 +307,7 @@ public function __call($name, $params)
public function __clone()
{
$this->_events = [];
$this->_eventWildcards = [];
$this->_behaviors = null;
}

Expand Down Expand Up @@ -456,6 +463,13 @@ public function behaviors()
public function hasEventHandlers($name)
{
$this->ensureBehaviors();

foreach ($this->_eventWildcards as $wildcard => $handlers) {
if (!empty($handlers) && StringHelper::matchWildcard($wildcard, $name)) {
return true;
}
}

return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
}

Expand All @@ -480,6 +494,14 @@ public function hasEventHandlers($name)
*
* where `$event` is an [[Event]] object which includes parameters associated with the event.
*
* Since 2.0.14 you can specify event name as a wildcard pattern:
*
* ```php
* $component->on('event.group.*', function ($event) {
* Yii::trace($event->name . ' is triggered.');
* });
* ```
*
* @param string $name the event name
* @param callable $handler the event handler
* @param mixed $data the data to be passed to the event handler when the event is triggered.
Expand All @@ -492,6 +514,16 @@ public function hasEventHandlers($name)
public function on($name, $handler, $data = null, $append = true)
{
$this->ensureBehaviors();

if (strpos($name, '*') !== false) {
if ($append || empty($this->_eventWildcards[$name])) {
$this->_eventWildcards[$name][] = [$handler, $data];
} else {
array_unshift($this->_eventWildcards[$name], [$handler, $data]);
}
return;
}

if ($append || empty($this->_events[$name])) {
$this->_events[$name][] = [$handler, $data];
} else {
Expand All @@ -501,7 +533,12 @@ public function on($name, $handler, $data = null, $append = true)

/**
* Detaches an existing event handler from this component.
*
* This method is the opposite of [[on()]].
*
* Note: in case wildcard pattern is passed for event name, only the handlers registered with this
* wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
*
* @param string $name event name
* @param callable $handler the event handler to be removed.
* If it is null, all handlers attached to the named event will be removed.
Expand All @@ -511,23 +548,44 @@ public function on($name, $handler, $data = null, $append = true)
public function off($name, $handler = null)
{
$this->ensureBehaviors();
if (empty($this->_events[$name])) {
if (empty($this->_events[$name]) && empty($this->_eventWildcards[$name])) {
return false;
}
if ($handler === null) {
unset($this->_events[$name]);
unset($this->_eventWildcards[$name]);
return true;
}

// plain event names
if (isset($this->_events[$name])) {
$removed = false;
foreach ($this->_events[$name] as $i => $event) {
if ($event[0] === $handler) {
unset($this->_events[$name][$i]);
$removed = true;
}
}
if ($removed) {
$this->_events[$name] = array_values($this->_events[$name]);
return $removed;
}
}

// wildcard event names
$removed = false;
foreach ($this->_events[$name] as $i => $event) {
foreach ($this->_eventWildcards[$name] as $i => $event) {
if ($event[0] === $handler) {
unset($this->_events[$name][$i]);
unset($this->_eventWildcards[$name][$i]);
$removed = true;
}
}
if ($removed) {
$this->_events[$name] = array_values($this->_events[$name]);
$this->_eventWildcards[$name] = array_values($this->_eventWildcards[$name]);
// remove empty wildcards to save future redundant regex checks :
if (empty($this->_eventWildcards[$name])) {
unset($this->_eventWildcards[$name]);
}
}

return $removed;
Expand All @@ -543,7 +601,19 @@ public function off($name, $handler = null)
public function trigger($name, Event $event = null)
{
$this->ensureBehaviors();

$eventHandlers = [];
foreach ($this->_eventWildcards as $wildcard => $handlers) {
if (StringHelper::matchWildcard($wildcard, $name)) {
$eventHandlers = array_merge($eventHandlers, $handlers);
}
}

if (!empty($this->_events[$name])) {
$eventHandlers = array_merge($eventHandlers, $this->_events[$name]);
}

if (!empty($eventHandlers)) {
if ($event === null) {
$event = new Event();
}
Expand All @@ -552,7 +622,7 @@ public function trigger($name, Event $event = null)
}
$event->handled = false;
$event->name = $name;
foreach ($this->_events[$name] as $handler) {
foreach ($eventHandlers as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
// stop further handling if the event is handled
Expand All @@ -561,6 +631,7 @@ public function trigger($name, Event $event = null)
}
}
}

// invoke class-level attached handlers
Event::trigger($this, $name, $event);
}
Expand Down
Loading

0 comments on commit f199b2b

Please sign in to comment.