Commit 4408f520 by Qiang Xue

Merge commit 'ec05c0ea' into feature-restapi

parents 39947be6 ec05c0ea
......@@ -4,7 +4,14 @@ php:
- 5.4
- 5.5
- 5.6
# - hhvm # commented until composer or hhvm get fixed: https://github.com/facebook/hhvm/issues/1347
- hhvm
# run build against PHP 5.6 and hhvm but allow them to fail
# http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail
matrix:
allow_failures:
- php: hhvm
- php: 5.6
services:
- redis-server
......
......@@ -172,7 +172,7 @@ Note that [[yii\db\ActiveRecord::updateAll()|updateAll()]], [[yii\db\ActiveRecor
```php
// to insert a new customer record
$customer = new Customer;
$customer = new Customer();
$customer->name = 'James';
$customer->email = 'james@example.com';
$customer->save(); // equivalent to $customer->insert();
......@@ -634,7 +634,7 @@ order owned by the customer:
```php
$customer = Customer::find(1);
$order = new Order;
$order = new Order();
$order->subtotal = 100;
$customer->link('orders', $order);
```
......
......@@ -160,7 +160,7 @@ class BlogController extends Controller
{
$post = Post::find($id);
if (!$post) {
throw new NotFoundHttpException;
throw new NotFoundHttpException();
}
if (\Yii::$app->request->isPost) {
......
......@@ -29,7 +29,7 @@ $posts = $provider->getModels();
And the following example shows how to use ActiveDataProvider without ActiveRecord:
```php
$query = new Query;
$query = new Query();
$provider = new ActiveDataProvider([
'query' => $query->from('tbl_post'),
'pagination' => [
......@@ -62,7 +62,7 @@ because it needs to have [[allModels]] ready.
ArrayDataProvider may be used in the following way:
```php
$query = new Query;
$query = new Query();
$provider = new ArrayDataProvider([
'allModels' => $query->from('tbl_post')->all(),
'sort' => [
......
......@@ -24,7 +24,7 @@ be accessed like the member variables of any object. For example, a `Post` model
may contain a `title` attribute and a `content` attribute, accessible as follows:
```php
$post = new Post;
$post = new Post();
$post->title = 'Hello, world';
$post->content = 'Something interesting is happening.';
echo $post->title;
......@@ -35,7 +35,7 @@ Since [[yii\base\Model|Model]] implements the [ArrayAccess](http://php.net/manua
you can also access the attributes as if they were array elements:
```php
$post = new Post;
$post = new Post();
$post['title'] = 'Hello, world';
$post['content'] = 'Something interesting is happening';
echo $post['title'];
......@@ -160,7 +160,7 @@ class EmployeeController extends \yii\web\Controller
$employee = new Employee(['scenario' => 'managementPanel']);
// second way
$employee = new Employee;
$employee = new Employee();
$employee->scenario = 'managementPanel';
// third way
......@@ -187,7 +187,7 @@ only, etc. If errors are found in validation, they may be presented to the user
The following example shows how the validation is performed:
```php
$model = new LoginForm;
$model = new LoginForm();
$model->username = $_POST['username'];
$model->password = $_POST['password'];
if ($model->validate()) {
......
......@@ -9,7 +9,7 @@ The Query Builder provides an object-oriented vehicle for generating queries to
A typical usage of the query builder looks like the following:
```php
$rows = (new \yii\db\Query)
$rows = (new \yii\db\Query())
->select('id, name')
->from('tbl_user')
->limit(10)
......@@ -17,7 +17,7 @@ $rows = (new \yii\db\Query)
// which is equivalent to the following code:
$query = (new \yii\db\Query)
$query = (new \yii\db\Query())
->select('id, name')
->from('tbl_user')
->limit(10);
......@@ -116,7 +116,7 @@ You may specify a sub-query using a `Query` object. In this case, the correspond
as the alias for the sub-query.
```php
$subQuery = (new Query)->select('id')->from('tbl_user')->where('status=1');
$subQuery = (new Query())->select('id')->from('tbl_user')->where('status=1');
$query->select('*')->from(['u' => $subQuery]);
```
......@@ -324,10 +324,10 @@ $query->leftJoin(['u' => $subQuery], 'u.id=author_id');
In Yii in order to build it you can first form two query objects and then use `union` method:
```php
$query = new Query;
$query = new Query();
$query->select("id, 'post' as type, name")->from('tbl_post')->limit(10);
$anotherQuery = new Query;
$anotherQuery = new Query();
$anotherQuery->select('id, 'user' as type, name')->from('tbl_user')->limit(10);
$query->union($anotherQuery);
......@@ -347,7 +347,7 @@ Batch query can be used like the following:
```php
use yii\db\Query;
$query = (new Query)
$query = (new Query())
->from('tbl_user')
->orderBy('id');
......@@ -376,7 +376,7 @@ will still keep the proper index. For example,
```php
use yii\db\Query;
$query = (new Query)
$query = (new Query())
->from('tbl_user')
->indexBy('username');
......
......@@ -158,7 +158,7 @@ in controllers or widgets:
```php
$content = Yii::$app->view->renderFile($viewFile, $params);
// You can also explicitly create a new View instance to do the rendering
// $view = new View;
// $view = new View();
// $view->renderFile($viewFile, $params);
```
......@@ -186,7 +186,7 @@ New methods called [[yii\base\Model::load()|load()] and [[yii\base\Model::loadMu
introduced to simplify the data population from user inputs to a model. For example,
```php
$model = new Post;
$model = new Post();
if ($model->load($_POST)) {...}
// which is equivalent to:
if (isset($_POST['Post'])) {
......@@ -394,7 +394,7 @@ In 1.1, query building is scattered among several classes, including `CDbCommand
and [[yii\db\QueryBuilder|QueryBuilder]] to generate SQL statements from query objects. For example:
```php
$query = new \yii\db\Query;
$query = new \yii\db\Query();
$query->select('id, name')
->from('tbl_user')
->limit(10);
......
......@@ -22,6 +22,7 @@
"yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*",
"phpdocumentor/reflection": ">=1.0.3",
"phpdocumentor/reflection-docblock": ">2.0.1",
"nikic/php-parser": "0.9.*"
},
"autoload": {
......
......@@ -150,8 +150,10 @@ Yii Framework 2 Change Log
- Enh: Added support for reading page size from query parameters by `Pagination` (qiangxue)
- Enh: LinkPager can now register relational link tags in the html header for prev, next, first and last page (cebe)
- Enh: Added `yii\web\UrlRuleInterface` and `yii\web\CompositeUrlRule` (qiangxue)
- Enh: Added `yii\web\Request::getAuthUser()` and `getAuthPassword()` (qiangxue)
- Chg #1186: Changed `Sort` to use comma to separate multiple sort fields and use negative sign to indicate descending sort (qiangxue)
- Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue)
- Chg #1564: Removed `yii\web\Session::autoStart` and added `hasSessionId`. Session will be automatically started when accessing session data (qiangxue)
- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue)
- Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue)
- Chg #1643: Added default value for `Captcha::options` (qiangxue)
......
......@@ -86,6 +86,10 @@ class ActiveQuery extends Query implements ActiveQueryInterface
* @see onCondition()
*/
public $on;
/**
* @var array a list of relations that this query should be joined with
*/
public $joinWith;
/**
......@@ -232,6 +236,10 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*/
protected function createCommandInternal($db)
{
if (!empty($this->joinWith)) {
$this->buildJoinWith();
}
/** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass;
if ($db === null) {
......@@ -330,24 +338,32 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*/
public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
{
$with = (array)$with;
$this->joinWithRelations(new $this->modelClass, $with, $joinType);
$this->joinWith[] = [(array)$with, $eagerLoading, $joinType];
return $this;
}
if (is_array($eagerLoading)) {
foreach ($with as $name => $callback) {
if (is_integer($name)) {
if (!in_array($callback, $eagerLoading, true)) {
private function buildJoinWith()
{
foreach ($this->joinWith as $config) {
list ($with, $eagerLoading, $joinType) = $config;
$this->joinWithRelations(new $this->modelClass, $with, $joinType);
if (is_array($eagerLoading)) {
foreach ($with as $name => $callback) {
if (is_integer($name)) {
if (!in_array($callback, $eagerLoading, true)) {
unset($with[$name]);
}
} elseif (!in_array($name, $eagerLoading, true)) {
unset($with[$name]);
}
} elseif (!in_array($name, $eagerLoading, true)) {
unset($with[$name]);
}
} elseif (!$eagerLoading) {
$with = [];
}
} elseif (!$eagerLoading) {
$with = [];
}
return $this->with($with);
$this->with($with);
}
}
/**
......
......@@ -819,6 +819,22 @@ class Request extends \yii\base\Request
return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
}
/**
* @return string the username sent via HTTP authentication, null if the username is not given
*/
public function getAuthUser()
{
return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
}
/**
* @return string the password sent via HTTP authentication, null if the username is not given
*/
public function getAuthPassword()
{
return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
}
private $_port;
/**
......
......@@ -75,10 +75,6 @@ use yii\base\InvalidParamException;
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable, Arrayable
{
/**
* @var boolean whether the session should be automatically started when the session component is initialized.
*/
public $autoStart = true;
/**
* @var string the name of the session variable that stores the flash message data.
*/
public $flashParam = '__flash';
......@@ -100,9 +96,6 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
public function init()
{
parent::init();
if ($this->autoStart) {
$this->open();
}
register_shutdown_function([$this, 'close']);
}
......@@ -123,10 +116,32 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function open()
{
if (session_status() == PHP_SESSION_ACTIVE) {
if ($this->getIsActive()) {
return;
}
$this->registerSessionHandler();
$this->setCookieParamsInternal();
@session_start();
if ($this->getIsActive()) {
Yii::info('Session started', __METHOD__);
$this->updateFlashCounters();
} else {
$error = error_get_last();
$message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
Yii::error($message, __METHOD__);
}
}
/**
* Registers session handler.
* @throws \yii\base\InvalidConfigException
*/
protected function registerSessionHandler()
{
if ($this->handler !== null) {
if (!is_object($this->handler)) {
$this->handler = Yii::createObject($this->handler);
......@@ -145,18 +160,6 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
[$this, 'gcSession']
);
}
$this->setCookieParamsInternal();
@session_start();
if (session_id() == '') {
$error = error_get_last();
$message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
Yii::error($message, __METHOD__);
} else {
$this->updateFlashCounters();
}
}
/**
......@@ -164,7 +167,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function close()
{
if (session_id() !== '') {
if ($this->getIsActive()) {
@session_write_close();
}
}
......@@ -174,7 +177,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function destroy()
{
if (session_id() !== '') {
if ($this->getIsActive()) {
@session_unset();
@session_destroy();
}
......@@ -188,6 +191,42 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
return session_status() == PHP_SESSION_ACTIVE;
}
private $_hasSessionId;
/**
* Returns a value indicating whether the current request has sent the session ID.
* The default implementation will check cookie and $_GET using the session name.
* If you send session ID via other ways, you may need to override this method
* or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
* @return boolean whether the current request has sent the session ID.
*/
public function getHasSessionId()
{
if ($this->_hasSessionId === null) {
$name = $this->getName();
$request = Yii::$app->getRequest();
if (ini_get('session.use_cookies') && !empty($_COOKIE[$name])) {
$this->_hasSessionId = true;
} elseif (!ini_get('use_only_cookies') && ini_get('use_trans_sid')) {
$this->_hasSessionId = $request->get($name) !== null;
} else {
$this->_hasSessionId = false;
}
}
return $this->_hasSessionId;
}
/**
* Sets the value indicating whether the current request has sent the session ID.
* This method is provided so that you can override the default way of determining
* whether the session ID is sent.
* @param boolean $value whether the current request has sent the session ID.
*/
public function setHasSessionId($value)
{
$this->_hasSessionId = $value;
}
/**
* @return string the current session ID
*/
......@@ -506,6 +545,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function get($key, $defaultValue = null)
{
$this->open();
return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
}
......@@ -517,6 +557,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function set($key, $value)
{
$this->open();
$_SESSION[$key] = $value;
}
......@@ -527,6 +568,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function remove($key)
{
$this->open();
if (isset($_SESSION[$key])) {
$value = $_SESSION[$key];
unset($_SESSION[$key]);
......@@ -541,6 +583,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function removeAll()
{
$this->open();
foreach (array_keys($_SESSION) as $key) {
unset($_SESSION[$key]);
}
......@@ -552,6 +595,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function has($key)
{
$this->open();
return isset($_SESSION[$key]);
}
......@@ -560,6 +604,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function toArray()
{
$this->open();
return $_SESSION;
}
......@@ -689,6 +734,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function offsetExists($offset)
{
$this->open();
return isset($_SESSION[$offset]);
}
......@@ -699,6 +745,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function offsetGet($offset)
{
$this->open();
return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
}
......@@ -709,6 +756,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function offsetSet($offset, $item)
{
$this->open();
$_SESSION[$offset] = $item;
}
......@@ -718,6 +766,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function offsetUnset($offset)
{
$this->open();
unset($_SESSION[$offset]);
}
}
......@@ -258,6 +258,16 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertTrue($orders[0]->isRelationPopulated('customer'));
$this->assertTrue($orders[1]->isRelationPopulated('customer'));
// inner join filtering, eager loading, conditions on both primary and relation
$orders = Order::find()->innerJoinWith([
'customer' => function ($query) {
$query->where(['tbl_customer.id' => 2]);
},
])->where(['tbl_order.id' => [1, 2]])->orderBy('tbl_order.id')->all();
$this->assertEquals(1, count($orders));
$this->assertEquals(2, $orders[0]->id);
$this->assertTrue($orders[0]->isRelationPopulated('customer'));
// inner join filtering without eager loading
$orders = Order::find()->innerJoinWith([
'customer' => function ($query) {
......@@ -270,6 +280,16 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertFalse($orders[0]->isRelationPopulated('customer'));
$this->assertFalse($orders[1]->isRelationPopulated('customer'));
// inner join filtering without eager loading, conditions on both primary and relation
$orders = Order::find()->innerJoinWith([
'customer' => function ($query) {
$query->where(['tbl_customer.id' => 2]);
},
], false)->where(['tbl_order.id' => [1, 2]])->orderBy('tbl_order.id')->all();
$this->assertEquals(1, count($orders));
$this->assertEquals(2, $orders[0]->id);
$this->assertFalse($orders[0]->isRelationPopulated('customer'));
// join with via-relation
$orders = Order::find()->innerJoinWith('books')->orderBy('tbl_order.id')->all();
$this->assertEquals(2, count($orders));
......@@ -282,6 +302,9 @@ class ActiveRecordTest extends DatabaseTestCase
// join with sub-relation
$orders = Order::find()->innerJoinWith([
'items' => function ($q) {
$q->orderBy('tbl_item.id');
},
'items.category' => function ($q) {
$q->where('tbl_category.id = 2');
},
......@@ -361,7 +384,11 @@ class ActiveRecordTest extends DatabaseTestCase
$this->assertNull($customers[1]->profile);
// hasMany
$customers = Customer::find()->active()->joinWith('orders')->orderBy('tbl_customer.id DESC, tbl_order.id')->all();
$customers = Customer::find()->active()->joinWith([
'orders' => function ($q) {
$q->orderBy('tbl_order.id');
}
])->orderBy('tbl_customer.id DESC, tbl_order.id')->all();
$this->assertEquals(2, count($customers));
$this->assertEquals(2, $customers[0]->id);
$this->assertEquals(1, $customers[1]->id);
......
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