Commit 62f43e17 by Philippe Gaultier

Merge branch 'master' into feature/CORS

parents ba74d97a f66e28c4
Components
==========
Components are the main building blocks of Yii applications. Components are instances of [[yii\base\Component]]
Components are the main building blocks of Yii applications. Components are instances of [[yii\base\Component]],
or an extended class. The three main features that components provide to other classes are:
* [Properties](concept-properties.md)
......@@ -26,10 +26,10 @@ echo DatePicker::widget([
The widget's properties are easily writable because the class extends [[yii\base\Component]].
While components are very powerful, they are a bit heavier than normal objects, due to the fact that
it takes extra memory and CPU time to support [events](concept-events.md) and [behaviors](concept-behaviors.md) in particular.
it takes extra memory and CPU time to support [event](concept-events.md) and [behavior](concept-behaviors.md) functionality in particular.
If your components do not need these two features, you may consider extending your component class from
[[yii\base\Object]] instead of [[yii\base\Component]]. Doing so will make your components as efficient as normal PHP objects,
but with the added support for [properties](concept-properties.md).
but with added support for [properties](concept-properties.md).
When extending your class from [[yii\base\Component]] or [[yii\base\Object]], it is recommended that you follow
these conventions:
......@@ -67,7 +67,7 @@ class MyClass extends Object
}
```
Following these guideliness will make your components [configurable](concept-configurations.md) when they are created. For example:
Following these guideliness will make your components [configurable](concept-configurations.md) when they are instantiated. For example:
```php
$component = new MyClass(1, 2, ['prop1' => 3, 'prop2' => 4]);
......@@ -79,8 +79,7 @@ $component = \Yii::createObject([
], [1, 2]);
```
> Info: While the approach of calling [[Yii::createObject()]] looks more complicated, it is more powerful due to
the fact that it is implemented on top of a [dependency injection container](concept-di-container.md).
> Info: While the approach of calling [[Yii::createObject()]] looks more complicated, it is more powerful because it is implemented on top of a [dependency injection container](concept-di-container.md).
The [[yii\base\Object]] class enforces the following object lifecycle:
......@@ -90,5 +89,5 @@ The [[yii\base\Object]] class enforces the following object lifecycle:
3. Post-initialization within [[yii\base\Object::init()|init()]]. You may override this method to perform sanity checks and normalization of the properties.
4. Object method calls.
The first three steps all happen within the object's constructor. This means that once you get an object instance,
it has already been initialized to a proper state that you can reliably work with.
The first three steps all happen within the object's constructor. This means that once you get a class instance (i.e., an object),
that object has already been initialized to a proper, reliable state.
......@@ -7,119 +7,53 @@ a mailer object may trigger a `messageSent` event when it successfully sends a m
track of the messages that are successfully sent, you could then simply attach the tracking code to the `messageSent` event.
Yii introduces a base class called [[yii\base\Component]] to support events. If a class needs to trigger
events, it should extend from [[yii\base\Component]] or a child class.
Triggering Events <a name="triggering-events"></a>
-----------------
Events are triggered by calling the [[yii\base\Component::trigger()]] method. The method requires an *event name*,
and optionally an event object that describes the parameters to be passed to the event handlers. For example:
```php
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class Foo extends Component
{
const EVENT_HELLO = 'hello';
public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
}
```
In the above code, when you call `bar()`, it will trigger an event named `hello`.
> Tip: It is recommended to use class constants to represent event names. In the above example, the constant
`EVENT_HELLO` is used to represent `hello`. This approach has two benefits. First, it prevents typos and can impact IDE
auto-completion support. Second, you can tell what events are supported by a class by simply checking the constant
declarations.
Sometimes when triggering an event, you may want to pass along additional information to the event handlers.
For example, a mailer may want pass the message information to the handlers of the `messageSent` event so that the handlers
can know the particulars of the sent messages. To do so, you can provide an event object as the second parameter to
the [[yii\base\Component::trigger()]] method. The event object must be an instance of the [[yii\base\Event]] class
or a child class. For example:
```php
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event
{
public $message;
}
class Mailer extends Component
{
const EVENT_MESSAGE_SENT = 'messageSent';
public function send($message)
{
// ...sending $message...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
```
When the [[yii\base\Component::trigger()]] method is called, it will call handlers that are attached to
the named event.
events, it should extend from [[yii\base\Component]], or from a child class.
Event Handlers <a name="event-handlers"></a>
--------------
An event handler is a [PHP callback](http://www.php.net/manual/en/language.types.callable.php) that gets executed
when the event it is attached to is triggered. You can use one of the following callbacks:
when the event it is attached to is triggered. You can use any of the following callbacks:
- a global PHP function specified in terms of a string, e.g., `'trim()'`;
- an object method specified in terms of an array of an object and a method name, e.g., `[$object, $method]`;
- a static class method specified in terms of an array of a class name and a method name, e.g., `[$class, $method]`;
- a global PHP function specified as a string (without parentheses), e.g., `'trim'`;
- an object method specified as an array of an object and a method name as a string (without parenthess), e.g., `[$object, 'methodName']`;
- a static class method specified as an array of a class name and a method name as a string (without parentheses), e.g., `[$class, 'methodName']`;
- an anonymous function, e.g., `function ($event) { ... }`.
The signature of an event handler is:
```php
function ($event) {
// $event is an object of yii\base\Event or its child class
// $event is an object of yii\base\Event or a child class
}
```
Through the `$event` parameter, an event handler may get the following information about an event:
Through the `$event` parameter, an event handler may get the following information about the event that occurred:
- [[yii\base\Event::name|event name]]
- [[yii\base\Event::sender|event sender]]: the object whose `trigger()` method is called.
- [[yii\base\Event::data|custom data]]: the data that is provided when attaching the event handler (to be explained shortly).
- [[yii\base\Event::sender|event sender]]: the object whose `trigger()` method was called
- [[yii\base\Event::data|custom data]]: the data that is provided when attaching the event handler (to be explained next)
Attaching Event Handlers <a name="attaching-event-handlers"></a>
------------------------
You can attach a handler to an event by calling the [[yii\base\Component::on()]] method. For example,
You can attach a handler to an event by calling the [[yii\base\Component::on()]] method. For example:
```php
$foo = new Foo;
// the handler is a global function
// this handler is a global function
$foo->on(Foo::EVENT_HELLO, 'function_name');
// the handler is an object method
// this handler is an object method
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
// the handler is a static class method
// this handler is a static class method
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// the handler is an anonymous function
// this handler is an anonymous function
$foo->on(Foo::EVENT_HELLO, function ($event) {
// event handling logic
});
......@@ -130,20 +64,24 @@ refer to the [Configurations](concept-configurations.md#configuration-format) se
When attaching an event handler, you may provide additional data as the third parameter to [[yii\base\Component::on()]].
The data will be made available to the handler when the event is triggered and the handler is called. For example,
The data will be made available to the handler when the event is triggered and the handler is called. For example:
```php
// The following code will display "abc" when the event is triggered
// because $event->data contains the data passed to "on"
$foo->on(Foo::EVENT_HELLO, function ($event) {
// because $event->data contains the data passed as the 3rd argument to "on"
$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc');
function function_name($event) {
echo $event->data;
}, 'abc');
}
```
You may attach one or multiple handlers to a single event. When an event is triggered, the attached handlers
will be called in the order they are attached to the event. If a handler needs to stop the invocation of the
handlers behind it, it may set the [[yii\base\Event::handled]] property of the `$event` parameter to be true,
like the following,
Event Handler Order
-------------------
You may attach one or more handlers to a single event. When an event is triggered, the attached handlers
will be called in the order that they were attached to the event. If a handler needs to stop the invocation of the
handlers that follow it, it may set the [[yii\base\Event::handled]] property of the `$event` parameter to be true:
```php
$foo->on(Foo::EVENT_HELLO, function ($event) {
......@@ -153,8 +91,7 @@ $foo->on(Foo::EVENT_HELLO, function ($event) {
By default, a newly attached handler is appended to the existing handler queue for the event.
As a result, the handler will be called in the last place when the event is triggered.
To insert the new handler at the start of the handler queue so that the handler gets called first, y
ou may call [[yii\base\Component::on()]] by passing the fourth parameter `$append` as false:
To insert the new handler at the start of the handler queue so that the handler gets called first, you may call [[yii\base\Component::on()]], passing false for the fourth parameter `$append`:
```php
$foo->on(Foo::EVENT_HELLO, function ($event) {
......@@ -162,23 +99,75 @@ $foo->on(Foo::EVENT_HELLO, function ($event) {
}, $data, false);
```
Besides calling the `on()` method, you may also attach event handlers in [configurations](concept-configurations.md)
like the following. For more details, please refer to the [Configurations](concept-configurations.md#configuration-format)
section.
Triggering Events <a name="triggering-events"></a>
-----------------
Events are triggered by calling the [[yii\base\Component::trigger()]] method. The method requires an *event name*,
and optionally an event object that describes the parameters to be passed to the event handlers. For example:
```php
[
'on hello' => function ($event) {
echo 'hello event is triggered';
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class Foo extends Component
{
const EVENT_HELLO = 'hello';
public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
]
}
```
With the above code, any calls to `bar()` will trigger an event named `hello`.
> Tip: It is recommended to use class constants to represent event names. In the above example, the constant
`EVENT_HELLO` represents the `hello` event. This approach has three benefits. First, it prevents typos. Second, it can make events recognizable for IDE
auto-completion support. Third, you can tell what events are supported in a class by simply checking its constant declarations.
Sometimes when triggering an event you may want to pass along additional information to the event handlers.
For example, a mailer may want pass the message information to the handlers of the `messageSent` event so that the handlers
can know the particulars of the sent messages. To do so, you can provide an event object as the second parameter to
the [[yii\base\Component::trigger()]] method. The event object must be an instance of the [[yii\base\Event]] class,
or of a child class. For example:
```php
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event
{
public $message;
}
class Mailer extends Component
{
const EVENT_MESSAGE_SENT = 'messageSent';
public function send($message)
{
// ...sending $message...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
```
When the [[yii\base\Component::trigger()]] method is called, it will call all handlers attached to
the named event.
Detaching Event Handlers <a name="detaching-event-handlers"></a>
------------------------
To detach a handler from an event, call the [[yii\base\Component::off()]] method. For example,
To detach a handler from an event, call the [[yii\base\Component::off()]] method. For example:
```php
// the handler is a global function
......@@ -195,7 +184,7 @@ $foo->off(Foo::EVENT_HELLO, $anonymousFunction);
```
Note that in general you should not try to detach an anonymous function unless you store it
somewhere when it is attached to the event. In the above example, we assume the anonymous
somewhere when it is attached to the event. In the above example, it is assumed that the anonymous
function is stored as a variable `$anonymousFunction`.
To detach ALL handlers from an event, simply call [[yii\base\Component::off()]] without the second parameter:
......@@ -208,14 +197,14 @@ $foo->off(Foo::EVENT_HELLO);
Class-Level Event Handlers <a name="class-level-event-handlers"></a>
--------------------------
In the above subsections, we have described how to attach a handler to an event at *instance level*.
Sometimes, you may want to respond to an event triggered by EVERY instance of a class instead of
The above subsections described how to attach a handler to an event on an *instance level*.
Sometimes, you may want to respond to an event triggered by *every* instance of a class instead of only by
a specific instance. Instead of attaching an event handler to every instance, you may attach the handler
at *class level* by calling the static method [[yii\base\Event::on()]].
on the *class level* by calling the static method [[yii\base\Event::on()]].
For example, an [Active Record](db-active-record.md) object will trigger a [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]]
event whenever it inserts a new record into the database. In order to track insertions done by EVERY
[Active Record](db-active-record.md) object, you may write the following code:
For example, an [Active Record](db-active-record.md) object will trigger an [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]]
event whenever it inserts a new record into the database. In order to track insertions done by *every*
[Active Record](db-active-record.md) object, you may use the following code:
```php
use Yii;
......@@ -227,15 +216,15 @@ Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function
});
```
The event handler will get invoked whenever an instance of [[yii\base\ActiveRecord|ActiveRecord]] or its child class triggers
The event handler will be invoked whenever an instance of [[yii\base\ActiveRecord|ActiveRecord]], or one of its child classes, triggers
the [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] event. In the handler, you can get the object
that triggers the event through `$event->sender`.
that triggered the event through `$event->sender`.
When an object triggers an event, it will first call instance-level handlers, followed by class-level handlers.
When an object triggers an event, it will first call instance-level handlers, followed by the class-level handlers.
You may trigger an *class-level* event by calling the static method [[yii\base\Event::trigger()]]. A class-level
You may trigger a *class-level* event by calling the static method [[yii\base\Event::trigger()]]. A class-level
event is not associated with a particular object. As a result, it will cause the invocation of class-level event
handlers only. For example,
handlers only. For example:
```php
use yii\base\Event;
......@@ -247,12 +236,12 @@ Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
Event::trigger(Foo::className(), Foo::EVENT_HELLO);
```
Note that in this case, `$event->sender` refers to the name of the class triggering the event instead of an object instance.
Note that, in this case, `$event->sender` refers to the name of the class triggering the event instead of an object instance.
> Note: Because a class-level handler will respond to an event triggered by any instance of that class or its child
class, you should use it carefully, especially if the class is a low-level base class, such as [[yii\base\Object]].
> Note: Because a class-level handler will respond to an event triggered by any instance of that class, or any child
classes, you should use it carefully, especially if the class is a low-level base class, such as [[yii\base\Object]].
To detach a class-level event handler, call [[yii\base\Event::off()]]. For example,
To detach a class-level event handler, call [[yii\base\Event::off()]]. For example:
```php
// detach $handler
......@@ -266,11 +255,11 @@ Event::off(Foo::className(), Foo::EVENT_HELLO);
Global Events <a name="global-events"></a>
-------------
The so-called *global event* is actually a trick based on the event mechanism described above.
It requires a globally accessible singleton, such as the [application](structure-applications.md) instance.
Yii supports a so-called *global event*, which is actually a trick based on the event mechanism described above.
The global event requires a globally accessible Singleton, such as the [application](structure-applications.md) instance itself.
An event sender, instead of calling its own `trigger()` method, will call the singleton's `trigger()` method
to trigger the event. Similarly, the event handlers are attached to the event of the singleton. For example,
To create the global evant, an event sender calls the Singleton's `trigger()` method
to trigger the event, instead of calling the sender's own `trigger()` method. Similarly, the event handlers are attached to the event on the Singleton. For example:
```php
use Yii;
......@@ -284,9 +273,9 @@ Yii::$app->on('bar', function ($event) {
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
```
A benefit of global events is that you do not need the object when attaching a handler to the event
A benefit of using global events is that you do not need an object when attaching a handler to the event
which will be triggered by the object. Instead, the handler attachment and the event triggering are both
done through the singleton (e.g. the application instance).
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").
......@@ -2,19 +2,19 @@ Properties
==========
In PHP, class member variables are also called *properties*. These variables are part of the class definition, and are used
to represent the state of a class instance (i.e., to differentiate one instance of the class from another). In practice, you may often want to handle the reading or writing of properties in special ways. For example, you may want to trim a string when it is being assigned
to a `label` property. You could use the following code to achieve this task:
to represent the state of a class instance (i.e., to differentiate one instance of the class from another). In practice, you may often want to handle the reading or writing of properties in special ways. For example, you may want to always trim a string when it is being assigned
to a `label` property. You *could* use the following code to achieve this task:
```php
$object->label = trim($label);
```
The drawback of the above code is that you have to call `trim()` everywhere in your code where you might set the `label`
property. If in the future, the `label` property gets a new requirement, such as the first letter must be captialized, you would again have to modify every bit of code that assigns a value to `label`. The repetition of code leads to bugs and is a practice you want to avoid as much as possible.
The drawback of the above code is that you would have to call `trim()` everywhere in your code where you might set the `label`
property. If, in the future, the `label` property gets a new requirement, such as the first letter must be captialized, you would again have to modify every bit of code that assigns a value to `label`. The repetition of code leads to bugs, and is a practice you want to avoid as much as possible.
To solve this problem, Yii introduces a base class called [[yii\base\Object]] that supports defining properties
based on *getter* and *setter* class methods. If a class needs such support, it should extend from
[[yii\base\Object]] or a child class.
based on *getter* and *setter* class methods. If a class needs that functionality, it should extend from
[[yii\base\Object]], or from a child class.
> Info: Nearly every core class in the Yii framework extends from [[yii\base\Object]] or a child class.
This means that whenever you see a getter or setter in a core class, you can use it like a property.
......@@ -69,9 +69,9 @@ There are several special rules for, and limitations on, the properties defined
This is because method names in PHP are case-insensitive.
* If the name of such a property is the same as a class member variable, the latter will take precedence.
For example, if the above `Foo` class has a member variable `label`, then the assignment `$object->label = 'abc'`
will affect the member variable 'label', that line would not call the `setLabel()` setter method.
will affect the *member variable* 'label'; that line would not call the `setLabel()` setter method.
* These properties do not support visibility. It makes no difference for the visibility of a property
if the defining getter or setter method is public, protected or private.
* The properties can only be defined by *non-static* getters and/or setters. Static methods will not be treated in this same manner.
Returning back to the problem described at the beginning of this guide, instead of calling `trim()` everywhere a `label` value is assigned, `trim()` only needs to be invoked within the setter `setLabel()`. And if a new requirement comes that requires the label be initially capitalized, the `setLabel()` method can quickly be modified without touching any other code. The one change will universally affect every assignment to `label`.
Returning back to the problem described at the beginning of this guide, instead of calling `trim()` everywhere a `label` value is assigned, `trim()` now only needs to be invoked within the setter `setLabel()`. And if a new requirement comes that requires the label be initially capitalized, the `setLabel()` method can quickly be modified without touching any other code. The one change will universally affect every assignment to `label`.
......@@ -71,6 +71,7 @@ Yii Framework 2 Change Log
- Bug: Fixed Object of class Imagick could not be converted to string in CaptchaAction (eXprojects, cebe)
- Enh #87: Helper `yii\helpers\Security` converted into application component, cryptographic strength improved (klimov-paul)
- Enh #422: Added Support for BIT(M) data type default values in Schema (cebe)
- Enh #1160: Added $strict parameter to Inflector::camel2id() to handle consecutive uppercase chars (schmunk)
- Enh #1452: Added `Module::getInstance()` to allow accessing the module instance from anywhere within the module (qiangxue)
- Enh #2264: `CookieCollection::has()` will return false for expired or removed cookies (qiangxue)
- Enh #2435: `yii\db\IntegrityException` is now thrown on database integrity errors instead of general `yii\db\Exception` (samdark)
......
......@@ -77,9 +77,9 @@ class RateLimiter extends ActionFilter
$action
);
} elseif ($user) {
Yii::info('Rate limit skipped: "user" does not implement RateLimitInterface.');
Yii::info('Rate limit skipped: "user" does not implement RateLimitInterface.', __METHOD__);
} else {
Yii::info('Rate limit skipped: user not logged in.');
Yii::info('Rate limit skipped: user not logged in.', __METHOD__);
}
return true;
}
......
......@@ -326,14 +326,16 @@ class BaseInflector
* For example, 'PostTag' will be converted to 'post-tag'.
* @param string $name the string to be converted
* @param string $separator the character used to concatenate the words in the ID
* @param string $strict whether to insert a separator between two consecutive uppercase chars, defaults to false
* @return string the resulting ID
*/
public static function camel2id($name, $separator = '-')
public static function camel2id($name, $separator = '-', $strict = false)
{
$regex = $strict ? '/[A-Z]/' : '/(?<![A-Z])[A-Z]/';
if ($separator === '_') {
return trim(strtolower(preg_replace('/(?<![A-Z])[A-Z]/', '_\0', $name)), '_');
return trim(strtolower(preg_replace($regex, '_\0', $name)), '_');
} else {
return trim(strtolower(str_replace('_', $separator, preg_replace('/(?<![A-Z])[A-Z]/', $separator . '\0', $name))), $separator);
return trim(strtolower(str_replace('_', $separator, preg_replace($regex, $separator . '\0', $name))), $separator);
}
}
......
......@@ -113,7 +113,7 @@ class I18N extends Component
$result = $formatter->format($message, $params, $language);
if ($result === false) {
$errorMessage = $formatter->getErrorMessage();
Yii::warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message.");
Yii::warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message.", __METHOD__);
return $message;
} else {
......
......@@ -94,7 +94,7 @@ class AssetConverter extends Component implements AssetConverterInterface
} elseif (YII_DEBUG) {
throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
} else {
Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
}
return $status === 0;
......
......@@ -95,6 +95,12 @@ class InflectorTest extends TestCase
$this->assertEquals('post-tag', Inflector::camel2id('postTag'));
$this->assertEquals('post_tag', Inflector::camel2id('postTag', '_'));
$this->assertEquals('foo-ybar', Inflector::camel2id('FooYBar', '-', false));
$this->assertEquals('foo_ybar', Inflector::camel2id('fooYBar', '_', false));
$this->assertEquals('foo-y-bar', Inflector::camel2id('FooYBar', '-', true));
$this->assertEquals('foo_y_bar', Inflector::camel2id('fooYBar', '_', true));
}
public function testId2camel()
......@@ -104,6 +110,9 @@ class InflectorTest extends TestCase
$this->assertEquals('PostTag', Inflector::id2camel('post-tag'));
$this->assertEquals('PostTag', Inflector::id2camel('post_tag', '_'));
$this->assertEquals('FooYBar', Inflector::id2camel('foo-y-bar'));
$this->assertEquals('FooYBar', Inflector::id2camel('foo_y_bar', '_'));
}
public function testHumanize()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment