Commit f6811002 by Carsten Brandt

Merge branch 'master' into elasticsearch

* master: (59 commits) updated ext composer.json for issue labels moved redis to extensions fixed whitespaces update composer to include sphinx Added initial steps about configuring DB-based RBAC Fixes #1297: CSRF not generated on error pages fixes #1158 mentioned @web alias in docs Merged RBAC draft into authorization "yii\sphinx\ActiveQuery" updated to throw exception on conflict between "asArray" and "snippetByModel" options. Code style and docs at "yii\sphinx\*" fixed. Docs for Sphinx extension updated. Redundant typecast removed from yii\sphinx\ActiveRecord::create() yii\sphinx\Command reworked to extend yii\db\Command. yii\sphinx\DataReader removed. Sphinx Active Record updated to be compatible with ActiveDataProvider. Sphinx documentation updated. Sphinx documentation updated. Sphinx has many relation test prepared. Sphinx Query refactored. Create relation methods added to Sphinx Active Record. Sphinx Query Builder updated to respect column types for where statements ...
parents 2cd0a2cd e837e44a
......@@ -19,7 +19,7 @@ before_script:
- tests/unit/data/travis/cubrid-setup.sh
script:
- phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor
- phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor,sphinx
after_script:
- php vendor/bin/coveralls
......@@ -63,17 +63,19 @@
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"minimum-stability": "dev",
"replace": {
"yiisoft/yii2-bootstrap": "self.version",
"yiisoft/yii2-debug": "self.version",
"yiisoft/yii2-gii": "self.version",
"yiisoft/yii2-jui": "self.version",
"yiisoft/yii2-smarty": "self.version",
"yiisoft/yii2-swiftmailer": "self.version",
"yiisoft/yii2-twig": "self.version",
"yiisoft/yii2": "self.version"
},
"minimum-stability": "dev",
"replace": {
"yiisoft/yii2-bootstrap": "self.version",
"yiisoft/yii2-debug": "self.version",
"yiisoft/yii2-gii": "self.version",
"yiisoft/yii2-jui": "self.version",
"yiisoft/yii2-redis": "self.version",
"yiisoft/yii2-smarty": "self.version",
"yiisoft/yii2-swiftmailer": "self.version",
"yiisoft/yii2-sphinx": "self.version",
"yiisoft/yii2-twig": "self.version",
"yiisoft/yii2": "self.version"
},
"require": {
"php": ">=5.4.0",
"ext-mbstring": "*",
......@@ -90,14 +92,16 @@
},
"autoload": {
"psr-0": {
"yii\\bootstrap\\": "extensions/bootstrap/",
"yii\\bootstrap\\": "extensions/bootstrap/",
"yii\\debug\\": "extensions/debug/",
"yii\\gii\\": "extensions/gii/",
"yii\\jui\\": "extensions/jui/",
"yii\\redis\\": "extensions/redis/",
"yii\\smarty\\": "extensions/smarty/",
"yii\\swiftmailer\\": "extensions/swiftmailer/",
"yii\\sphinx\\": "extensions/sphinx/",
"yii\\twig\\": "extensions/twig/",
"yii\\": "framework/yii/"
}
}
}
}
}
......@@ -76,16 +76,149 @@ Role based access control (RBAC)
Role based access control is very flexible approach to controlling access that is a perfect match for complex systems
where permissions are customizable.
### Using file-based config for RBAC
In order to start using it some extra steps are required. First of all we need to configure `authManager` application
component:
component in application config file (`web.php` or `main.php` depending on template you've used):
```php
'authManager' => [
'class' => 'app\components\PhpManager',
'defaultRoles' => ['guest'],
],
```
Often use role is stored in the same database table as other user data. In this case we may defined it by creating our
own component (`app/components/PhpManager.php`):
```php
<?php
namespace app\components;
use Yii;
class PhpManager extends \yii\rbac\PhpManager
{
public function init()
{
parent::init();
if (!Yii::$app->user->isGuest) {
// we suppose that user's role is stored in identity
$this->assign(Yii::$app->user->identity->id, Yii::$app->user->identity->role);
}
}
}
```
Then create permissions hierarchy.
Then create permissions hierarchy in `@app/data/rbac.php`:
```php
<?php
use yii\rbac\Item;
return [
// HERE ARE YOUR MANAGEMENT TASKS
'manageThing0' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
'manageThing1' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
// AND THE ROLES
'guest' => [
'type' => Item::TYPE_ROLE,
'description' => 'Guest',
'bizRule' => NULL,
'data' => NULL
],
'user' => [
'type' => Item::TYPE_ROLE,
'description' => 'User',
'children' => [
'guest',
'manageThing0', // User can edit thing0
],
'bizRule' => 'return !Yii::$app->user->isGuest;',
'data' => NULL
],
'moderator' => [
'type' => Item::TYPE_ROLE,
'description' => 'Moderator',
'children' => [
'user', // Can manage all that user can
'manageThing1', // and also thing1
],
'bizRule' => NULL,
'data' => NULL
],
'admin' => [
'type' => Item::TYPE_ROLE,
'description' => 'Admin',
'children' => [
'moderator', // can do all the stuff that moderator can
'manageThing2', // and also manage thing2
],
'bizRule' => NULL,
'data' => NULL
],
'godmode' => [
'type' => Item::TYPE_ROLE,
'description' => 'Super admin',
'children' => [
'admin', // can do all that admin can
'manageThing3', // and also thing3
],
'bizRule' => NULL,
'data' => NULL
],
];
```
Now you can specify roles from RBAC in controller's access control configuration:
```php
public function behaviors()
{
return [
'access' => [
'class' => 'yii\web\AccessControl',
'except' => ['something'],
'rules' => [
[
'allow' => true,
'roles' => ['manageThing1'],
],
],
],
];
}
```
Another way is to call [[User::checkAccess()]] where appropriate.
### Using DB-based storage for RBAC
Storing RBAC hierarchy in database is less efficient performancewise but is much more flexible. It is easier to create
a good management UI for it so in case you need permissions structure that is managed by end user DB is your choice.
In order to get started you need to configure database connection in `db` component. After it is done [get `schema-*.sql`
file for your database](https://github.com/yiisoft/yii2/tree/master/framework/yii/rbac) and execute it.
Next step is to configure `authManager` application component in application config file (`web.php` or `main.php`
depending on template you've used):
```php
'authManager' => [
'class' => 'yii\rbac\DbManager',
'defaultRoles' => ['guest'],
],
```
Specify roles from RBAC in controller's access control configuration or call [[User::checkAccess()]] where appropriate.
TBD
### How it works
......
......@@ -63,9 +63,9 @@ Path Aliases
Yii 2.0 expands the usage of path aliases to both file/directory paths and URLs. An alias
must start with a `@` character so that it can be differentiated from file/directory paths and URLs.
For example, the alias `@yii` refers to the Yii installation directory. Path aliases are
supported in most places in the Yii core code. For example, `FileCache::cachePath` can take
both a path alias and a normal directory path.
For example, the alias `@yii` refers to the Yii installation directory while `@web` contains base URL for currently
running web application. Path aliases are supported in most places in the Yii core code. For example,
`FileCache::cachePath` can take both a path alias and a normal directory path.
Path alias is also closely related with class namespaces. It is recommended that a path
alias be defined for each root namespace so that you can use Yii the class autoloader without
......
......@@ -22,14 +22,14 @@ return [
],
```
After it is done in case of error Yii will launch `SiteController::actionError()`. Since errors are converted to
exceptions we can get exception from error handler:
After it is done in case of error, Yii will launch `SiteController::actionError()`:
```php
public function actionError()
{
$exception = \Yii::$app->getErrorHandler()->exception;
$this->render('myerror', ['message' => $exception->getMessage()]);
if (\Yii::$app->exception !== null) {
return $this->render('error', ['exception' => \Yii::$app->exception]);
}
}
```
......@@ -48,7 +48,7 @@ public function actions()
```
After defining `actions` in `SiteController` as shown above you can create `views/site/error.php`. In the view there
are three varialbes available:
are three variables available:
- `$name`: the error name
- `$message`: the error message
......
......@@ -56,7 +56,6 @@ Security and access control
- [Authorization](authorization.md) - Access control and RBAC
- [Security](security.md) - Hashing and verifying passwords, encryption
- [Views security](view.md#security) - how to prevent XSS
- [RBAC](rbac.md) - Role-based Access Control
Data providers, lists and grids
===============================
......
Using RBAC
===========
Lacking proper documentation, this guide is a stub copied from a [topic on the forum](http://www.yiiframework.com/forum/index.php/topic/49104-does-anyone-have-a-working-example-of-rbac/page__view__findpost__p__229098).
First af all, you modify your config (web.php or main.php),
```php
'authManager' => [
'class' => 'app\components\PhpManager', // THIS IS YOUR AUTH MANAGER
'defaultRoles' => ['guest'],
],
```
Next, create the manager itself (app/components/PhpManager.php)
```php
<?php
namespace app\components;
use Yii;
class PhpManager extends \yii\rbac\PhpManager
{
public function init()
{
if ($this->authFile === NULL)
$this->authFile = Yii::getAlias('@app/data/rbac') . '.php'; // HERE GOES YOUR RBAC TREE FILE
parent::init();
if (!Yii::$app->user->isGuest) {
$this->assign(Yii::$app->user->identity->id, Yii::$app->user->identity->role); // we suppose that user's role is stored in identity
}
}
}
```
Now, the rules tree (@app/data/rbac.php):
```php
<?php
use yii\rbac\Item;
return [
// HERE ARE YOUR MANAGEMENT TASKS
'manageThing0' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
'manageThing1' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL],
// AND THE ROLES
'guest' => [
'type' => Item::TYPE_ROLE,
'description' => 'Guest',
'bizRule' => NULL,
'data' => NULL
],
'user' => [
'type' => Item::TYPE_ROLE,
'description' => 'User',
'children' => [
'guest',
'manageThing0', // User can edit thing0
],
'bizRule' => 'return !Yii::$app->user->isGuest;',
'data' => NULL
],
'moderator' => [
'type' => Item::TYPE_ROLE,
'description' => 'Moderator',
'children' => [
'user', // Can manage all that user can
'manageThing1', // and also thing1
],
'bizRule' => NULL,
'data' => NULL
],
'admin' => [
'type' => Item::TYPE_ROLE,
'description' => 'Admin',
'children' => [
'moderator', // can do all the stuff that moderator can
'manageThing2', // and also manage thing2
],
'bizRule' => NULL,
'data' => NULL
],
'godmode' => [
'type' => Item::TYPE_ROLE,
'description' => 'Super admin',
'children' => [
'admin', // can do all that admin can
'manageThing3', // and also thing3
],
'bizRule' => NULL,
'data' => NULL
],
];
```
As a result, you can now add access control filters to controllers
```php
public function behaviors()
{
return [
'access' => [
'class' => 'yii\web\AccessControl',
'except' => ['something'],
'rules' => [
[
'allow' => true,
'roles' => ['manageThing1'],
],
],
],
];
}
```
......@@ -5,7 +5,7 @@
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Abootstrap",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
......
......@@ -5,7 +5,7 @@
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Adebug",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
......
......@@ -5,7 +5,7 @@
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Agii",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
......
......@@ -5,7 +5,7 @@
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Ajui",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
......
......@@ -5,36 +5,35 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\caching;
use yii\redis\Connection;
namespace yii\redis;
/**
* RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6.12 or higher.
* Redis Cache implements a cache application component based on [redis](http://redis.io/) key-value store.
*
* Redis Cache requires redis version 2.6.12 or higher to work properly.
*
* RedisCache needs to be configured with [[hostname]], [[port]] and [[database]] of the server
* to connect to. By default RedisCache assumes there is a redis server running on localhost at
* port 6379 and uses the database number 0.
* It needs to be configured with a redis [[Connection]] that is also configured as an application component.
* By default it will use the `redis` application component.
*
* RedisCache also supports [the AUTH command](http://redis.io/commands/auth) of redis.
* When the server needs authentication, you can set the [[password]] property to
* authenticate with the server after connect.
* See [[Cache]] manual for common cache operations that redis Cache supports.
*
* See [[Cache]] manual for common cache operations that RedisCache supports.
* Unlike the [[CCache]], RedisCache allows the expire parameter of
* [[set]] and [[add]] to be a floating point number, so you may specify the time in milliseconds.
* Unlike the [[Cache]], redis Cache allows the expire parameter of [[set]], [[add]], [[mset]] and [[madd]] to
* be a floating point number, so you may specify the time in milliseconds (e.g. 0.1 will be 100 milliseconds).
*
* To use RedisCache as the cache application component, configure the application as follows,
* To use redis Cache as the cache application component, configure the application as follows,
*
* ~~~
* [
* 'components' => [
* 'cache' => [
* 'class' => 'RedisCache',
* 'class' => 'yii\redis\Cache',
* ],
* 'redis' => [
* 'class' => 'yii\redis\Connection',
* 'hostname' => 'localhost',
* 'port' => 6379,
* 'database' => 0,
* ],
* ]
* ],
* ]
* ~~~
......@@ -44,64 +43,14 @@ use yii\redis\Connection;
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class RedisCache extends Cache
class Cache extends \yii\caching\Cache
{
/**
* @var string hostname to use for connecting to the redis server. Defaults to 'localhost'.
*/
public $hostname = 'localhost';
/**
* @var int the port to use for connecting to the redis server. Default port is 6379.
*/
public $port = 6379;
/**
* @var string the password to use to authenticate with the redis server. If not set, no AUTH command will be sent.
*/
public $password;
/**
* @var int the redis database to use. This is an integer value starting from 0. Defaults to 0.
*/
public $database = 0;
/**
* @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
*/
public $connectionTimeout = null;
/**
* @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
* @var string the id of the application component to use as the redis connection.
* It should be configured as a [[yii\redis\Connection]]. Defaults to `redis`.
*/
public $dataTimeout = null;
/**
* @var Connection the redis connection
*/
private $_connection;
/**
* Initializes the cache component by establishing a connection to the redis server.
*/
public function init()
{
parent::init();
$this->getConnection();
}
public $connectionId = 'redis';
/**
* Returns the redis connection object.
* Establishes a connection to the redis server if it does not already exists.
* @return Connection the redis connection object.
*/
public function getConnection()
{
if ($this->_connection === null) {
$this->_connection = new Connection([
'dsn' => 'redis://' . $this->hostname . ':' . $this->port . '/' . $this->database,
'password' => $this->password,
'connectionTimeout' => $this->connectionTimeout,
'dataTimeout' => $this->dataTimeout,
]);
}
return $this->_connection;
}
/**
* Checks whether a specified key exists in the cache.
......@@ -115,7 +64,9 @@ class RedisCache extends Cache
*/
public function exists($key)
{
return (bool) $this->_connection->executeCommand('EXISTS', [$this->buildKey($key)]);
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
return (bool) $connection->executeCommand('EXISTS', [$this->buildKey($key)]);
}
/**
......@@ -123,7 +74,9 @@ class RedisCache extends Cache
*/
protected function getValue($key)
{
return $this->_connection->executeCommand('GET', [$key]);
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
return $connection->executeCommand('GET', [$key]);
}
/**
......@@ -131,7 +84,9 @@ class RedisCache extends Cache
*/
protected function getValues($keys)
{
$response = $this->_connection->executeCommand('MGET', $keys);
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
$response = $connection->executeCommand('MGET', $keys);
$result = [];
$i = 0;
foreach ($keys as $key) {
......@@ -145,11 +100,13 @@ class RedisCache extends Cache
*/
protected function setValue($key, $value, $expire)
{
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
if ($expire == 0) {
return (bool) $this->_connection->executeCommand('SET', [$key, $value]);
return (bool) $connection->executeCommand('SET', [$key, $value]);
} else {
$expire = (int) ($expire * 1000);
return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire]);
return (bool) $connection->executeCommand('SET', [$key, $value, 'PX', $expire]);
}
}
......@@ -158,6 +115,9 @@ class RedisCache extends Cache
*/
protected function setValues($data, $expire)
{
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
$args = [];
foreach($data as $key => $value) {
$args[] = $key;
......@@ -166,17 +126,17 @@ class RedisCache extends Cache
$failedKeys = [];
if ($expire == 0) {
$this->_connection->executeCommand('MSET', $args);
$connection->executeCommand('MSET', $args);
} else {
$expire = (int) ($expire * 1000);
$this->_connection->executeCommand('MULTI');
$this->_connection->executeCommand('MSET', $args);
$connection->executeCommand('MULTI');
$connection->executeCommand('MSET', $args);
$index = [];
foreach ($data as $key => $value) {
$this->_connection->executeCommand('PEXPIRE', [$key, $expire]);
$connection->executeCommand('PEXPIRE', [$key, $expire]);
$index[] = $key;
}
$result = $this->_connection->executeCommand('EXEC');
$result = $connection->executeCommand('EXEC');
array_shift($result);
foreach($result as $i => $r) {
if ($r != 1) {
......@@ -192,11 +152,13 @@ class RedisCache extends Cache
*/
protected function addValue($key, $value, $expire)
{
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
if ($expire == 0) {
return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'NX']);
return (bool) $connection->executeCommand('SET', [$key, $value, 'NX']);
} else {
$expire = (int) ($expire * 1000);
return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']);
return (bool) $connection->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']);
}
}
......@@ -205,7 +167,9 @@ class RedisCache extends Cache
*/
protected function deleteValue($key)
{
return (bool) $this->_connection->executeCommand('DEL', [$key]);
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
return (bool) $connection->executeCommand('DEL', [$key]);
}
/**
......@@ -213,6 +177,8 @@ class RedisCache extends Cache
*/
protected function flushValues()
{
return $this->_connection->executeCommand('FLUSHDB');
/** @var Connection $connection */
$connection = \Yii::$app->getComponent($this->connectionId);
return $connection->executeCommand('FLUSHDB');
}
}
......@@ -13,6 +13,15 @@ use yii\db\Exception;
use yii\helpers\Inflector;
/**
* The redis connection class is used to establish a connection to a [redis](http://redis.io/) server.
*
* By default it assumes there is a redis server running on localhost at port 6379 and uses the database number 0.
*
* It also supports [the AUTH command](http://redis.io/commands/auth) of redis.
* When the server needs authentication, you can set the [[password]] property to
* authenticate with the server after connect.
*
* The ecexution of [redis commands](http://redis.io/commands) is possible with via [[executeCommand()]].
*
* @method mixed set($key, $value) Set the string value of a key
* @method mixed get($key) Set the string value of a key
......@@ -33,20 +42,23 @@ class Connection extends Component
const EVENT_AFTER_OPEN = 'afterOpen';
/**
* @var string the Data Source Name, or DSN, contains the information required to connect to the database.
* DSN format: redis://server:port[/db]
* Where db is a zero based integer which refers to the DB to use.
* If no DB is given, ID 0 is used.
*
* Example: redis://localhost:6379/2
* @var string the hostname or ip address to use for connecting to the redis server. Defaults to 'localhost'.
*/
public $dsn;
public $hostname = 'localhost';
/**
* @var int the port to use for connecting to the redis server. Default port is 6379.
*/
public $port = 6379;
/**
* @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is send.
* See http://redis.io/commands/auth
*/
public $password;
/**
* @var int the redis database to use. This is an integer value starting from 0. Defaults to 0.
*/
public $database = 0;
/**
* @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
*/
public $connectionTimeout = null;
......@@ -230,38 +242,30 @@ class Connection extends Component
*/
public function open()
{
if ($this->_socket === null) {
if (empty($this->dsn)) {
throw new InvalidConfigException('Connection.dsn cannot be empty.');
}
$dsn = explode('/', $this->dsn);
$host = $dsn[2];
if (strpos($host, ':')===false) {
$host .= ':6379';
if ($this->_socket !== null) {
return;
}
$connection = $this->hostname . ':' . $this->port . ', database=' . $this->database;
\Yii::trace('Opening redis DB connection: ' . $connection, __CLASS__);
$this->_socket = @stream_socket_client(
'tcp://' . $this->hostname . ':' . $this->port,
$errorNumber,
$errorDescription,
$this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout")
);
if ($this->_socket) {
if ($this->dataTimeout !== null) {
stream_set_timeout($this->_socket, $timeout=(int)$this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
}
$db = isset($dsn[3]) ? $dsn[3] : 0;
\Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__);
$this->_socket = @stream_socket_client(
$host,
$errorNumber,
$errorDescription,
$this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout")
);
if ($this->_socket) {
if ($this->dataTimeout !== null) {
stream_set_timeout($this->_socket, $timeout=(int)$this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
}
if ($this->password !== null) {
$this->executeCommand('AUTH', [$this->password]);
}
$this->executeCommand('SELECT', [$db]);
$this->initConnection();
} else {
\Yii::error("Failed to open DB connection ({$this->dsn}): " . $errorNumber . ' - ' . $errorDescription, __CLASS__);
$message = YII_DEBUG ? 'Failed to open DB connection: ' . $errorNumber . ' - ' . $errorDescription : 'Failed to open DB connection.';
throw new Exception($message, $errorDescription, (int)$errorNumber);
if ($this->password !== null) {
$this->executeCommand('AUTH', [$this->password]);
}
$this->executeCommand('SELECT', [$this->database]);
$this->initConnection();
} else {
\Yii::error("Failed to open DB connection ($connection): " . $errorNumber . ' - ' . $errorDescription, __CLASS__);
$message = YII_DEBUG ? 'Failed to open DB connection: ' . $errorNumber . ' - ' . $errorDescription : 'Failed to open DB connection.';
throw new Exception($message, $errorDescription, (int)$errorNumber);
}
}
......@@ -272,7 +276,8 @@ class Connection extends Component
public function close()
{
if ($this->_socket !== null) {
\Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__);
$connection = $this->hostname . ':' . $this->port . ', database=' . $this->database;
\Yii::trace('Closing DB connection: ' . $connection, __CLASS__);
$this->executeCommand('QUIT');
stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR);
$this->_socket = null;
......@@ -295,11 +300,7 @@ class Connection extends Component
*/
public function getDriverName()
{
if (($pos = strpos($this->dsn, ':')) !== false) {
return strtolower(substr($this->dsn, 0, $pos));
} else {
return 'redis';
}
return 'redis';
}
/**
......
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Redis Cache and ActiveRecord for Yii 2
======================================
This extension provides the [redis](http://redis.io/) key-value store support for the Yii2 framework.
It includes a `Cache` class and implents the `ActiveRecord` pattern that allows you to store active
records in redis.
To use this extension, you have to configure the Connection class in your application configuration:
```php
return [
//....
'components' => [
'redis' => [
'class' => 'yii\redis\Connection',
'hostname' => 'localhost',
'port' => 6379,
'database' => 0,
],
]
];
```
To use the `Cache` component, you also have to configure the `cache` component to be `yii\redis\Cache`:
```php
return [
//....
'components' => [
// ...
'cache' => [
'class' => 'yii\redis\Cache',
],
]
];
```
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require yiisoft/yii2-redis "*"
```
or add
```json
"yiisoft/yii2-redis": "*"
```
to the require section of your composer.json.
Using the redis ActiveRecord
----------------------------
For general information on how to use yii's ActiveRecord please refer to the [guide](https://github.com/yiisoft/yii2/blob/master/docs/guide/active-record.md).
For defining a redis ActiveRecord class your record class needs to extend from `yii\redis\ActiveRecord` and
implement at least the `attributes()` method to define the attributes of the record.
A primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified.
The primaryKey needs to be part of the attributes so make sure you have an `id` attribute defined if you do
not specify your own primary key.
The following is an example model called `Customer`:
```php
class Customer extends \yii\redis\ActiveRecord
{
public function attributes()
{
return ['id', 'name', 'address', 'registration_date'];
}
}
```
The general usage of redis ActiveRecord is very similar to the database ActiveRecord as described in the
[guide](https://github.com/yiisoft/yii2/blob/master/docs/guide/active-record.md).
It supports the same interface and features except the following limitations:
- As redis does not support SQL the query API is limited to the following methods:
`where()`, `limit()`, `offset()`, `orderBy()` and `indexBy()`.
(orderBy() is not yet implemented: [#1305](https://github.com/yiisoft/yii2/issues/1305))
- `via`-relations can not be defined via a table as there are not tables in redis. You can only define relations via other records.
It is also possible to define relations from redis ActiveRecords to normal ActiveRecord classes and vice versa.
\ No newline at end of file
{
"name": "yiisoft/yii2-redis",
"description": "Redis Cache and ActiveRecord for the Yii framework",
"keywords": ["yii", "redis", "active-record", "cache"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aredis",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"authors": [
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc"
}
],
"require": {
"yiisoft/yii2": "*"
},
"autoload": {
"psr-0": { "yii\\redis\\": "" }
},
"target-dir": "yii/redis"
}
......@@ -5,7 +5,7 @@
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Asmarty",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\sphinx;
use yii\base\InvalidCallException;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
/**
* ActiveQuery represents a Sphinx query associated with an Active Record class.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]].
*
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
* [[orderBy()]] to customize the query options.
*
* ActiveQuery also provides the following additional query options:
*
* - [[with()]]: list of relations that this query should be performed with.
* - [[indexBy()]]: the name of the column by which the query result should be indexed.
* - [[asArray()]]: whether to return each record as an array.
*
* These options can be configured using methods of the same name. For example:
*
* ~~~
* $articles = Article::find()->with('source')->asArray()->all();
* ~~~
*
* ActiveQuery allows to build the snippets using sources provided by ActiveRecord.
* You can use [[snippetByModel()]] method to enable this.
* For example:
*
* ~~~
* class Article extends ActiveRecord
* {
* public function getSource()
* {
* return $this->hasOne('db', ArticleDb::className(), ['id' => 'id']);
* }
*
* public function getSnippetSource()
* {
* return $this->source->content;
* }
*
* ...
* }
*
* $articles = Article::find()->with('source')->snippetByModel()->all();
* ~~~
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
use ActiveQueryTrait;
/**
* @var string the SQL statement to be executed for retrieving AR records.
* This is set by [[ActiveRecord::findBySql()]].
*/
public $sql;
/**
* Sets the [[snippetCallback]] to [[fetchSnippetSourceFromModels()]], which allows to
* fetch the snippet source strings from the Active Record models, using method
* [[ActiveRecord::getSnippetSource()]].
* For example:
*
* ~~~
* class Article extends ActiveRecord
* {
* public function getSnippetSource()
* {
* return file_get_contents('/path/to/source/files/' . $this->id . '.txt');;
* }
* }
*
* $articles = Article::find()->snippetByModel()->all();
* ~~~
*
* Warning: this option should NOT be used with [[asArray]] at the same time!
* @return static the query object itself
*/
public function snippetByModel()
{
$this->snippetCallback([$this, 'fetchSnippetSourceFromModels']);
return $this;
}
/**
* Executes query and returns all results as an array.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @return array the query results. If the query results in nothing, an empty array will be returned.
*/
public function all($db = null)
{
$command = $this->createCommand($db);
$rows = $command->queryAll();
if (!empty($rows)) {
$models = $this->createModels($rows);
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
$models = $this->fillUpSnippets($models);
return $models;
} else {
return [];
}
}
/**
* Executes query and returns a single row of result.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. Null will be returned
* if the query results in nothing.
*/
public function one($db = null)
{
$command = $this->createCommand($db);
$row = $command->queryOne();
if ($row !== false) {
if ($this->asArray) {
$model = $row;
} else {
/** @var $class ActiveRecord */
$class = $this->modelClass;
$model = $class::create($row);
}
if (!empty($this->with)) {
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
list ($model) = $this->fillUpSnippets([$model]);
return $model;
} else {
return null;
}
}
/**
* Creates a DB command that can be used to execute this query.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @return Command the created DB command instance.
*/
public function createCommand($db = null)
{
/** @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
$this->setConnection($db);
$db = $this->getConnection();
$params = $this->params;
if ($this->sql === null) {
if ($this->from === null) {
$tableName = $modelClass::indexName();
if ($this->select === null && !empty($this->join)) {
$this->select = ["$tableName.*"];
}
$this->from = [$tableName];
}
list ($this->sql, $params) = $db->getQueryBuilder()->build($this);
}
return $db->createCommand($this->sql, $params);
}
/**
* @inheritdoc
*/
protected function defaultConnection()
{
$modelClass = $this->modelClass;
return $modelClass::getDb();
}
/**
* Fetches the source for the snippets using [[ActiveRecord::getSnippetSource()]] method.
* @param ActiveRecord[] $models raw query result rows.
* @throws \yii\base\InvalidCallException if [[asArray]] enabled.
* @return array snippet source strings
*/
protected function fetchSnippetSourceFromModels($models)
{
if ($this->asArray) {
throw new InvalidCallException('"' . __METHOD__ . '" unable to determine snippet source from plain array. Either disable "asArray" option or use regular "snippetCallback"');
}
$result = [];
foreach ($models as $model) {
$result[] = $model->getSnippetSource();
}
return $result;
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\sphinx;
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation to Sphinx Active Record class.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\sphinx;
use yii\base\Object;
use yii\db\Expression;
/**
* ColumnSchema class describes the metadata of a column in a Sphinx index.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ColumnSchema extends Object
{
/**
* @var string name of this column (without quotes).
*/
public $name;
/**
* @var string abstract type of this column. Possible abstract types include:
* string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
* timestamp, time, date, binary, and money.
*/
public $type;
/**
* @var string the PHP type of this column. Possible PHP types include:
* string, boolean, integer, double.
*/
public $phpType;
/**
* @var string the DB type of this column. Possible DB types vary according to the type of DBMS.
*/
public $dbType;
/**
* @var boolean whether this column is a primary key
*/
public $isPrimaryKey;
/**
* @var boolean whether this column is an attribute
*/
public $isAttribute;
/**
* @var boolean whether this column is a indexed field
*/
public $isField;
/**
* @var boolean whether this column is a multi value attribute (MVA)
*/
public $isMva;
/**
* Converts the input value according to [[phpType]].
* If the value is null or an [[Expression]], it will not be converted.
* @param mixed $value input value
* @return mixed converted value
*/
public function typecast($value)
{
if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
return $value;
}
if ($value === '' && $this->type !== Schema::TYPE_STRING) {
return null;
}
switch ($this->phpType) {
case 'string':
return (string)$value;
case 'integer':
return (integer)$value;
case 'boolean':
return (boolean)$value;
}
return $value;
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\sphinx;
use Yii;
use yii\base\NotSupportedException;
/**
* Command represents a SQL statement to be executed against a Sphinx.
*
* A command object is usually created by calling [[Connection::createCommand()]].
* The SQL statement it represents can be set via the [[sql]] property.
*
* To execute a non-query SQL (such as INSERT, REPLACE, DELETE, UPDATE), call [[execute()]].
* To execute a SQL statement that returns result data set (such as SELECT, CALL SNIPPETS, CALL KEYWORDS),
* use [[queryAll()]], [[queryOne()]], [[queryColumn()]], [[queryScalar()]], or [[query()]].
* For example,
*
* ~~~
* $articles = $connection->createCommand("SELECT * FROM `idx_article` WHERE MATCH('programming')")->queryAll();
* ~~~
*
* Command supports SQL statement preparation and parameter binding just as [[\yii\db\Command]] does.
*
* Command also supports building SQL statements by providing methods such as [[insert()]],
* [[update()]], etc. For example,
*
* ~~~
* $connection->createCommand()->update('idx_article', [
* 'genre_id' => 15,
* 'author_id' => 157,
* ])->execute();
* ~~~
*
* To build SELECT SQL statements, please use [[Query]] and [[QueryBuilder]] instead.
*
* @property \yii\sphinx\Connection $db the Sphinx connection that this command is associated with.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Command extends \yii\db\Command
{
/**
* Creates a batch INSERT command.
* For example,
*
* ~~~
* $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [
* ['Tom', 30],
* ['Jane', 20],
* ['Linda', 25],
* ])->execute();
* ~~~
*
* Note that the values in each row must match the corresponding column names.
*
* @param string $index the index that new rows will be inserted into.
* @param array $columns the column names
* @param array $rows the rows to be batch inserted into the index
* @return static the command object itself
*/
public function batchInsert($index, $columns, $rows)
{
$params = [];
$sql = $this->db->getQueryBuilder()->batchInsert($index, $columns, $rows, $params);
return $this->setSql($sql)->bindValues($params);
}
/**
* Creates an REPLACE command.
* For example,
*
* ~~~
* $connection->createCommand()->insert('idx_user', [
* 'name' => 'Sam',
* 'age' => 30,
* ])->execute();
* ~~~
*
* The method will properly escape the column names, and bind the values to be replaced.
*
* Note that the created command is not executed until [[execute()]] is called.
*
* @param string $index the index that new rows will be replaced into.
* @param array $columns the column data (name => value) to be replaced into the index.
* @return static the command object itself
*/
public function replace($index, $columns)
{
$params = [];
$sql = $this->db->getQueryBuilder()->replace($index, $columns, $params);
return $this->setSql($sql)->bindValues($params);
}
/**
* Creates a batch REPLACE command.
* For example,
*
* ~~~
* $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [
* ['Tom', 30],
* ['Jane', 20],
* ['Linda', 25],
* ])->execute();
* ~~~
*
* Note that the values in each row must match the corresponding column names.
*
* @param string $index the index that new rows will be replaced.
* @param array $columns the column names
* @param array $rows the rows to be batch replaced in the index
* @return static the command object itself
*/
public function batchReplace($index, $columns, $rows)
{
$params = [];
$sql = $this->db->getQueryBuilder()->batchReplace($index, $columns, $rows, $params);
return $this->setSql($sql)->bindValues($params);
}
/**
* Creates an UPDATE command.
* For example,
*
* ~~~
* $connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute();
* ~~~
*
* The method will properly escape the column names and bind the values to be updated.
*
* Note that the created command is not executed until [[execute()]] is called.
*
* @param string $index the index to be updated.
* @param array $columns the column data (name => value) to be updated.
* @param string|array $condition the condition that will be put in the WHERE part. Please
* refer to [[Query::where()]] on how to specify condition.
* @param array $params the parameters to be bound to the command
* @param array $options list of options in format: optionName => optionValue
* @return static the command object itself
*/
public function update($index, $columns, $condition = '', $params = [], $options = [])
{
$sql = $this->db->getQueryBuilder()->update($index, $columns, $condition, $params, $options);
return $this->setSql($sql)->bindValues($params);
}
/**
* Creates a SQL command for truncating a runtime index.
* @param string $index the index to be truncated. The name will be properly quoted by the method.
* @return static the command object itself
*/
public function truncateIndex($index)
{
$sql = $this->db->getQueryBuilder()->truncateIndex($index);
return $this->setSql($sql);
}
/**
* Builds a snippet from provided data and query, using specified index settings.
* @param string $index name of the index, from which to take the text processing settings.
* @param string|array $source is the source data to extract a snippet from.
* It could be either a single string or array of strings.
* @param string $match the full-text query to build snippets for.
* @param array $options list of options in format: optionName => optionValue
* @return static the command object itself
*/
public function callSnippets($index, $source, $match, $options = [])
{
$params = [];
$sql = $this->db->getQueryBuilder()->callSnippets($index, $source, $match, $options, $params);
return $this->setSql($sql)->bindValues($params);
}
/**
* Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics.
* @param string $index the name of the index from which to take the text processing settings
* @param string $text the text to break down to keywords.
* @param boolean $fetchStatistic whether to return document and hit occurrence statistics
* @return string the SQL statement for call keywords.
*/
public function callKeywords($index, $text, $fetchStatistic = false)
{
$params = [];
$sql = $this->db->getQueryBuilder()->callKeywords($index, $text, $fetchStatistic, $params);
return $this->setSql($sql)->bindValues($params);
}
// Not Supported :
/**
* @inheritdoc
*/
public function createTable($table, $columns, $options = null)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function renameTable($table, $newName)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function dropTable($table)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function truncateTable($table)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function addColumn($table, $column, $type)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function dropColumn($table, $column)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function renameColumn($table, $oldName, $newName)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function alterColumn($table, $column, $type)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function addPrimaryKey($name, $table, $columns)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function dropPrimaryKey($name, $table)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function dropForeignKey($name, $table)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function createIndex($name, $table, $columns, $unique = false)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function dropIndex($name, $table)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function resetSequence($table, $value = null)
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function checkIntegrity($check = true, $schema = '')
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\sphinx;
use yii\base\NotSupportedException;
/**
* Connection represents the Sphinx connection via MySQL protocol.
* This class uses [PDO](http://www.php.net/manual/en/ref.pdo.php) to maintain such connection.
* Note: although PDO supports numerous database drivers, this class supports only MySQL.
*
* In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added:
* ~~~
* searchd
* {
* listen = localhost:9306:mysql41
* ...
* }
* ~~~
*
* The following example shows how to create a Connection instance and establish
* the Sphinx connection:
* ~~~
* $connection = new \yii\db\Connection([
* 'dsn' => 'mysql:host=127.0.0.1;port=9306;',
* 'username' => $username,
* 'password' => $password,
* ]);
* $connection->open();
* ~~~
*
* After the Sphinx connection is established, one can execute SQL statements like the following:
* ~~~
* $command = $connection->createCommand("SELECT * FROM idx_article WHERE MATCH('programming')");
* $articles = $command->queryAll();
* $command = $connection->createCommand('UPDATE idx_article SET status=2 WHERE id=1');
* $command->execute();
* ~~~
*
* For more information about how to perform various DB queries, please refer to [[Command]].
*
* This class supports transactions exactly as "yii\db\Connection".
*
* Note: while this class extends "yii\db\Connection" some of its methods are not supported.
*
* @property Schema $schema The schema information for this Sphinx connection. This property is read-only.
* @property \yii\sphinx\QueryBuilder $queryBuilder The query builder for this Sphinx connection. This property is
* read-only.
* @method \yii\sphinx\Schema getSchema() The schema information for this Sphinx connection
* @method \yii\sphinx\QueryBuilder getQueryBuilder() the query builder for this Sphinx connection
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Connection extends \yii\db\Connection
{
/**
* @inheritdoc
*/
public $schemaMap = [
'mysqli' => 'yii\sphinx\Schema', // MySQL
'mysql' => 'yii\sphinx\Schema', // MySQL
];
/**
* Obtains the schema information for the named index.
* @param string $name index name.
* @param boolean $refresh whether to reload the table schema even if it is found in the cache.
* @return IndexSchema index schema information. Null if the named index does not exist.
*/
public function getIndexSchema($name, $refresh = false)
{
return $this->getSchema()->getIndexSchema($name, $refresh);
}
/**
* Quotes a index name for use in a query.
* If the index name contains schema prefix, the prefix will also be properly quoted.
* If the index name is already quoted or contains special characters including '(', '[[' and '{{',
* then this method will do nothing.
* @param string $name index name
* @return string the properly quoted index name
*/
public function quoteIndexName($name)
{
return $this->getSchema()->quoteIndexName($name);
}
/**
* Alias of [[quoteIndexName()]].
* @param string $name table name
* @return string the properly quoted table name
*/
public function quoteTableName($name)
{
return $this->quoteIndexName($name);
}
/**
* Creates a command for execution.
* @param string $sql the SQL statement to be executed
* @param array $params the parameters to be bound to the SQL statement
* @return Command the Sphinx command
*/
public function createCommand($sql = null, $params = [])
{
$this->open();
$command = new Command([
'db' => $this,
'sql' => $sql,
]);
return $command->bindValues($params);
}
/**
* This method is not supported by Sphinx.
* @param string $sequenceName name of the sequence object
* @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
* @throws \yii\base\NotSupportedException always.
*/
public function getLastInsertID($sequenceName = '')
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\sphinx;
use yii\base\Object;
use yii\base\InvalidParamException;
/**
* IndexSchema represents the metadata of a Sphinx index.
*
* @property array $columnNames List of column names. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class IndexSchema extends Object
{
/**
* @var string name of this index.
*/
public $name;
/**
* @var string type of the index.
*/
public $type;
/**
* @var boolean whether this index is a runtime index.
*/
public $isRuntime;
/**
* @var string primary key of this index.
*/
public $primaryKey;
/**
* @var ColumnSchema[] column metadata of this index. Each array element is a [[ColumnSchema]] object, indexed by column names.
*/
public $columns = [];
/**
* Gets the named column metadata.
* This is a convenient method for retrieving a named column even if it does not exist.
* @param string $name column name
* @return ColumnSchema metadata of the named column. Null if the named column does not exist.
*/
public function getColumn($name)
{
return isset($this->columns[$name]) ? $this->columns[$name] : null;
}
/**
* Returns the names of all columns in this table.
* @return array list of column names
*/
public function getColumnNames()
{
return array_keys($this->columns);
}
}
\ No newline at end of file
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Yii 2.0 Public Preview - Sphinx Extension
=========================================
Thank you for choosing Yii - a high-performance component-based PHP framework.
If you are looking for a production-ready PHP framework, please use
[Yii v1.1](https://github.com/yiisoft/yii).
Yii 2.0 is still under heavy development. We may make significant changes
without prior notices. **Yii 2.0 is not ready for production use yet.**
[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2)
This is the yii2-sphinx extension.
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require yiisoft/yii2-sphinx "*"
```
or add
```
"yiisoft/yii2-sphinx": "*"
```
to the require section of your composer.json.
*Note: You might have to run `php composer.phar selfupdate`*
Usage & Documentation
---------------------
This extension adds [Sphinx](http://sphinxsearch.com/docs) full text search engine extension for the Yii framework.
This extension interact with Sphinx search daemon using MySQL protocol and [SphinxQL](http://sphinxsearch.com/docs/current.html#sphinxql) query language.
In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added:
```
searchd
{
listen = localhost:9306:mysql41
...
}
```
This extension supports all Sphinx features including [Runtime Indexes](http://sphinxsearch.com/docs/current.html#rt-indexes).
Since this extension uses MySQL protocol to access Sphinx, it shares base approach and much code from the
regular "yii\db" package.
To use this extension, simply add the following code in your application configuration:
```php
return [
//....
'components' => [
'sphinx' => [
'class' => 'yii\sphinx\Connection',
'dsn' => 'mysql:host=127.0.0.1;port=9306;',
'username' => '',
'password' => '',
],
],
];
```
This extension provides ActiveRecord solution similar ot the [[\yii\db\ActiveRecord]].
To declare an ActiveRecord class you need to extend [[\yii\sphinx\ActiveRecord]] and
implement the `indexName` method:
```php
use yii\sphinx\ActiveRecord;
class Article extends ActiveRecord
{
/**
* @return string the name of the index associated with this ActiveRecord class.
*/
public static function indexName()
{
return 'idx_article';
}
}
```
You can use [[\yii\data\ActiveDataProvider]] with the [[\yii\sphinx\Query]] and [[\yii\sphinx\ActiveQuery]]:
```php
use yii\data\ActiveDataProvider;
use yii\sphinx\Query;
$query = new Query;
$query->from('yii2_test_article_index')->match('development');
$provider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
]
]);
$models = $provider->getModels();
```
```php
use yii\data\ActiveDataProvider;
use app\models\Article;
$provider = new ActiveDataProvider([
'query' => Article::find(),
'pagination' => [
'pageSize' => 10,
]
]);
$models = $provider->getModels();
```
\ No newline at end of file
{
"name": "yiisoft/yii2-sphinx",
"description": "Sphinx full text search engine extension for the Yii framework",
"keywords": ["yii", "sphinx", "active-record", "search", "fulltext"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Asphinx",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"authors": [
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com"
}
],
"minimum-stability": "dev",
"require": {
"yiisoft/yii2": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*"
},
"autoload": {
"psr-0": { "yii\\sphinx\\": "" }
}
}
......@@ -5,7 +5,7 @@
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aswiftmailer",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
......
......@@ -5,7 +5,7 @@
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Atwig",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
......
......@@ -127,6 +127,11 @@ abstract class Application extends Module
* ~~~
*/
public $extensions = [];
/**
* @var \Exception the exception that is being handled currently. When this is not null,
* it means the application is handling some exception and extra care should be taken.
*/
public $exception;
/**
* @var string Used to reserve memory for fatal error handler.
......@@ -487,6 +492,8 @@ abstract class Application extends Module
*/
public function handleException($exception)
{
$this->exception = $exception;
// disable error capturing to avoid recursive errors while handling exceptions
restore_error_handler();
restore_exception_handler();
......@@ -574,6 +581,7 @@ abstract class Application extends Module
if (ErrorException::isFatalError($error)) {
$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
$this->exception = $exception;
// use error_log because it's too late to use Yii log
error_log($exception);
......
......@@ -40,7 +40,7 @@ class ErrorHandler extends Component
/**
* @var string the route (e.g. 'site/error') to the controller action that will be used
* to display external errors. Inside the action, it can retrieve the error information
* by Yii::$app->errorHandler->exception. This property defaults to null, meaning ErrorHandler
* by Yii::$app->exception. This property defaults to null, meaning ErrorHandler
* will handle the error display.
*/
public $errorAction;
......@@ -96,8 +96,6 @@ class ErrorHandler extends Component
$response->getHeaders()->removeAll();
if ($useErrorView && $this->errorAction !== null) {
// disable CSRF validation so that errorAction can run in case the error is caused by CSRF validation failure
Yii::$app->getRequest()->enableCsrfValidation = false;
$result = Yii::$app->runAction($this->errorAction);
if ($result instanceof Response) {
$response = $result;
......
......@@ -61,7 +61,6 @@ return [
'yii\caching\GroupDependency' => YII_PATH . '/caching/GroupDependency.php',
'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php',
'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php',
'yii\caching\RedisCache' => YII_PATH . '/caching/RedisCache.php',
'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php',
'yii\caching\XCache' => YII_PATH . '/caching/XCache.php',
'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php',
......@@ -168,8 +167,6 @@ return [
'yii\rbac\Item' => YII_PATH . '/rbac/Item.php',
'yii\rbac\Manager' => YII_PATH . '/rbac/Manager.php',
'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php',
'yii\redis\Connection' => YII_PATH . '/redis/Connection.php',
'yii\redis\Transaction' => YII_PATH . '/redis/Transaction.php',
'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php',
'yii\test\DbFixtureManager' => YII_PATH . '/test/DbFixtureManager.php',
'yii\test\DbTestTrait' => YII_PATH . '/test/DbTestTrait.php',
......
......@@ -91,7 +91,7 @@ class Controller extends \yii\base\Controller
public function beforeAction($action)
{
if (parent::beforeAction($action)) {
if ($this->enableCsrfValidation && !Yii::$app->getRequest()->validateCsrfToken()) {
if ($this->enableCsrfValidation && Yii::$app->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.'));
}
return true;
......
......@@ -69,7 +69,7 @@ class ErrorAction extends Action
public function run()
{
if (!($exception = Yii::$app->getErrorHandler()->exception)) {
if (($exception = Yii::$app->exception) === null) {
return '';
}
......
......@@ -33,8 +33,23 @@ return [
'dsn' => 'elasticsearch://localhost:9200'
],
'redis' => [
'dsn' => 'redis://localhost:6379/0',
'hostname' => 'localhost',
'port' => 6379,
'database' => 0,
'password' => null,
],
],
'sphinx' => [
'sphinx' => [
'dsn' => 'mysql:host=127.0.0.1;port=9306;',
'username' => '',
'password' => '',
],
'db' => [
'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest',
'username' => 'travis',
'password' => '',
'fixture' => __DIR__ . '/sphinx/source.sql',
],
]
];
<?php
namespace yiiunit\data\sphinx\ar;
/**
* Test Sphinx ActiveRecord class
*/
class ActiveRecord extends \yii\sphinx\ActiveRecord
{
public static $db;
public static function getDb()
{
return self::$db;
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
use yii\sphinx\ActiveRelation;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
class ArticleDb extends ActiveRecordDb
{
public static function tableName()
{
return 'yii2_test_article';
}
public function getIndex()
{
$config = [
'modelClass' => ArticleIndex::className(),
'primaryModel' => $this,
'link' => ['id' => 'id'],
'multiple' => false,
];
return new ActiveRelation($config);
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
use yii\db\ActiveRelation;
class ArticleIndex extends ActiveRecord
{
public $custom_column;
public static function indexName()
{
return 'yii2_test_article_index';
}
public static function favoriteAuthor($query)
{
$query->andWhere('author_id=1');
}
public function getSource()
{
return $this->hasOne('db', ArticleDb::className(), ['id' => 'id']);
}
public function getTags()
{
return $this->hasMany('db', TagDb::className(), ['id' => 'tag']);
}
public function getSnippetSource()
{
return $this->source->content;
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
class ItemDb extends ActiveRecordDb
{
public static function tableName()
{
return 'yii2_test_item';
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
class ItemIndex extends ActiveRecord
{
public static function indexName()
{
return 'yii2_test_item_index';
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
class RuntimeIndex extends ActiveRecord
{
public static function indexName()
{
return 'yii2_test_rt_index';
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
class TagDb extends ActiveRecordDb
{
public static function tableName()
{
return 'yii2_test_tag';
}
}
\ No newline at end of file
/**
* This is the MySQL database schema for creation of the test Sphinx index sources.
*/
DROP TABLE IF EXISTS yii2_test_article;
DROP TABLE IF EXISTS yii2_test_item;
DROP TABLE IF EXISTS yii2_test_tag;
DROP TABLE IF EXISTS yii2_test_article_tag;
CREATE TABLE IF NOT EXISTS `yii2_test_article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`author_id` int(11) NOT NULL,
`create_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
CREATE TABLE IF NOT EXISTS `yii2_test_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`category_id` int(11) NOT NULL,
`price` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;
CREATE TABLE IF NOT EXISTS `yii2_test_tag` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=5;
CREATE TABLE IF NOT EXISTS `yii2_test_article_tag` (
`article_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`article_id`,`tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `yii2_test_article` (`id`, `title`, `content`, `author_id`, `create_date`) VALUES
(1, 'About cats', 'This article is about cats', 1, '2013-10-23 00:00:00'),
(2, 'About dogs', 'This article is about dogs', 2, '2013-11-15 00:00:00');
INSERT INTO `yii2_test_item` (`id`, `name`, `description`, `category_id`, `price`) VALUES
(1, 'pencil', 'Simple pencil', 1, 2.5),
(2, 'table', 'Wooden table', 2, 100);
INSERT INTO `yii2_test_tag` (`id`, `name`) VALUES
(1, 'tag1'),
(2, 'tag2'),
(3, 'tag3'),
(4, 'tag4');
INSERT INTO `yii2_test_article_tag` (`article_id`, `tag_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 3),
(2, 4);
\ No newline at end of file
# Sphinx configuration for the unit tests
#
# Setup test environment:
# - initialize test database source:
# mysql -D yii2test -u test < /path/to/yii/tests/unit/data/sphinx/source.sql
# - setup test Sphinx indexes:
# indexer --config /path/to/yii/tests/unit/data/sphinx/sphinx.conf --all [--rotate]
# - run the "searchd" daemon:
# searchd --config /path/to/yii/tests/unit/data/sphinx/sphinx.conf
source yii2_test_article_src
{
type = mysql
sql_host = localhost
sql_user =
sql_pass =
sql_db = yii2test
sql_port = 3306 # optional, default is 3306
sql_query = \
SELECT *, UNIX_TIMESTAMP(create_date) AS add_date \
FROM yii2_test_article
sql_attr_uint = id
sql_attr_uint = author_id
sql_attr_timestamp = add_date
sql_attr_multi = uint tag from query; SELECT article_id AS id, tag_id AS tag FROM yii2_test_article_tag
sql_query_info = SELECT * FROM yii2_test_article WHERE id=$id
}
source yii2_test_item_src
{
type = mysql
sql_host = localhost
sql_user =
sql_pass =
sql_db = yii2test
sql_port = 3306 # optional, default is 3306
sql_query = \
SELECT *, CURRENT_TIMESTAMP() AS add_date \
FROM yii2_test_item \
WHERE id <= 100
sql_attr_uint = id
sql_attr_uint = category_id
sql_attr_float = price
sql_attr_timestamp = add_date
sql_query_info = SELECT * FROM yii2_test_item WHERE id=$id
}
source yii2_test_item_delta_src : yii2_test_item_src
{
sql_query = \
SELECT *, CURRENT_TIMESTAMP() AS add_date \
FROM yii2_test_item \
WHERE id > 100
}
index yii2_test_article_index
{
source = yii2_test_article_src
path = /var/lib/sphinx/yii2_test_article
docinfo = extern
charset_type = sbcs
}
index yii2_test_item_index
{
source = yii2_test_item_src
path = /var/lib/sphinx/yii2_test_item
docinfo = extern
charset_type = sbcs
}
index yii2_test_item_delta_index : yii2_test_item_index
{
source = yii2_test_item_delta_src
path = /var/lib/sphinx/yii2_test_item_delta
}
index yii2_test_rt_index
{
type = rt
path = /var/lib/sphinx/yii2_test_rt
rt_field = title
rt_field = content
rt_attr_uint = type_id
rt_attr_multi = category
}
indexer
{
mem_limit = 32M
}
searchd
{
listen = 127.0.0.1:9312
listen = 9306:mysql41
log = /var/log/sphinx/searchd.log
query_log = /var/log/sphinx/query.log
read_timeout = 5
max_children = 30
pid_file = /var/run/sphinx/searchd.pid
max_matches = 1000
seamless_rotate = 1
preopen_indexes = 1
unlink_old = 1
workers = threads # for RT to work
binlog_path = /var/lib/sphinx
}
<?php
namespace yiiunit\framework\redis;
namespace yiiunit\extensions\redis;
use yii\db\Query;
use yii\redis\ActiveQuery;
use yiiunit\data\ar\redis\ActiveRecord;
use yiiunit\data\ar\redis\Customer;
......
<?php
namespace yiiunit\framework\caching;
use yii\caching\MemCache;
use yii\caching\RedisCache;
namespace yiiunit\extensions\redis;
use Yii;
use yii\redis\Cache;
use yii\redis\Connection;
use yiiunit\framework\caching\CacheTestCase;
Yii::setAlias('@yii/redis', __DIR__ . '/../../../../extensions/redis');
/**
* Class for testing redis cache backend
......@@ -13,23 +18,24 @@ class RedisCacheTest extends CacheTestCase
private $_cacheInstance = null;
/**
* @return MemCache
* @return Cache
*/
protected function getCacheInstance()
{
$config = [
'hostname' => 'localhost',
'port' => 6379,
'database' => 0,
'dataTimeout' => 0.1,
];
$dsn = $config['hostname'] . ':' .$config['port'];
if (!@stream_socket_client($dsn, $errorNumber, $errorDescription, 0.5)) {
$this->markTestSkipped('No redis server running at ' . $dsn .' : ' . $errorNumber . ' - ' . $errorDescription);
$databases = $this->getParam('databases');
$params = isset($databases['redis']) ? $databases['redis'] : null;
if ($params === null) {
$this->markTestSkipped('No redis server connection configured.');
}
$connection = new Connection($params);
if(!@stream_socket_client($connection->hostname . ':' . $connection->port, $errorNumber, $errorDescription, 0.5)) {
$this->markTestSkipped('No redis server running at ' . $connection->hostname . ':' . $connection->port . ' : ' . $errorNumber . ' - ' . $errorDescription);
}
$this->mockApplication(['components' => ['redis' => $connection]]);
if ($this->_cacheInstance === null) {
$this->_cacheInstance = new RedisCache($config);
$this->_cacheInstance = new Cache();
}
return $this->_cacheInstance;
}
......
<?php
namespace yiiunit\framework\redis;
namespace yiiunit\extensions\redis;
use yii\redis\Connection;
......@@ -10,35 +10,24 @@ use yii\redis\Connection;
class RedisConnectionTest extends RedisTestCase
{
/**
* Empty DSN should throw exception
* @expectedException \yii\base\InvalidConfigException
*/
public function testEmptyDSN()
{
$db = new Connection();
$db->open();
}
/**
* test connection to redis and selection of db
*/
public function testConnect()
{
$db = new Connection();
$db->dsn = 'redis://localhost:6379';
$db = $this->getConnection(false);
$db->open();
$this->assertTrue($db->ping());
$db->set('YIITESTKEY', 'YIITESTVALUE');
$db->close();
$db = new Connection();
$db->dsn = 'redis://localhost:6379/0';
$db = $this->getConnection(false);
$db->database = 0;
$db->open();
$this->assertEquals('YIITESTVALUE', $db->get('YIITESTKEY'));
$db->close();
$db = new Connection();
$db->dsn = 'redis://localhost:6379/1';
$db = $this->getConnection(false);
$db->database = 1;
$db->open();
$this->assertNull($db->get('YIITESTKEY'));
$db->close();
......
<?php
namespace yiiunit\framework\redis;
namespace yiiunit\extensions\redis;
use Yii;
use yii\redis\Connection;
use yiiunit\TestCase;
Yii::setAlias('@yii/redis', __DIR__ . '/../../../../extensions/redis');
/**
* RedisTestCase is the base class for all redis related test cases
*/
......@@ -12,22 +15,18 @@ abstract class RedisTestCase extends TestCase
{
protected function setUp()
{
$this->mockApplication();
$databases = $this->getParam('databases');
$params = isset($databases['redis']) ? $databases['redis'] : null;
if ($params === null || !isset($params['dsn'])) {
if ($params === null) {
$this->markTestSkipped('No redis server connection configured.');
}
$dsn = explode('/', $params['dsn']);
$host = $dsn[2];
if (strpos($host, ':')===false) {
$host .= ':6379';
}
if(!@stream_socket_client($host, $errorNumber, $errorDescription, 0.5)) {
$this->markTestSkipped('No redis server running at ' . $params['dsn'] . ' : ' . $errorNumber . ' - ' . $errorDescription);
$connection = new Connection($params);
if(!@stream_socket_client($connection->hostname . ':' . $connection->port, $errorNumber, $errorDescription, 0.5)) {
$this->markTestSkipped('No redis server running at ' . $connection->hostname . ':' . $connection->port . ' : ' . $errorNumber . ' - ' . $errorDescription);
}
$this->mockApplication(['components' => ['redis' => $connection]]);
parent::setUp();
}
......@@ -38,13 +37,11 @@ abstract class RedisTestCase extends TestCase
public function getConnection($reset = true)
{
$databases = $this->getParam('databases');
$params = isset($databases['redis']) ? $databases['redis'] : array();
$db = new Connection;
$db->dsn = $params['dsn'];
$db->password = $params['password'];
$params = isset($databases['redis']) ? $databases['redis'] : [];
$db = new Connection($params);
if ($reset) {
$db->open();
$db->flushall();
$db->flushdb();
}
return $db;
}
......
<?php
namespace yiiunit\extensions\sphinx;
use yii\data\ActiveDataProvider;
use yii\sphinx\Query;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\sphinx\ar\ArticleIndex;
/**
* @group sphinx
*/
class ActiveDataProviderTest extends SphinxTestCase
{
protected function setUp()
{
parent::setUp();
ActiveRecord::$db = $this->getConnection();
}
// Tests :
public function testQuery()
{
$query = new Query;
$query->from('yii2_test_article_index');
$provider = new ActiveDataProvider([
'query' => $query,
'db' => $this->getConnection(),
]);
$models = $provider->getModels();
$this->assertEquals(2, count($models));
$provider = new ActiveDataProvider([
'query' => $query,
'db' => $this->getConnection(),
'pagination' => [
'pageSize' => 1,
]
]);
$models = $provider->getModels();
$this->assertEquals(1, count($models));
}
public function testActiveQuery()
{
$provider = new ActiveDataProvider([
'query' => ArticleIndex::find()->orderBy('id ASC'),
]);
$models = $provider->getModels();
$this->assertEquals(2, count($models));
$this->assertTrue($models[0] instanceof ArticleIndex);
$this->assertTrue($models[1] instanceof ArticleIndex);
$this->assertEquals([1, 2], $provider->getKeys());
$provider = new ActiveDataProvider([
'query' => ArticleIndex::find(),
'pagination' => [
'pageSize' => 1,
]
]);
$models = $provider->getModels();
$this->assertEquals(1, count($models));
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yii\sphinx\ActiveQuery;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\sphinx\ar\ArticleIndex;
use yiiunit\data\sphinx\ar\RuntimeIndex;
/**
* @group sphinx
*/
class ActiveRecordTest extends SphinxTestCase
{
protected function setUp()
{
parent::setUp();
ActiveRecord::$db = $this->getConnection();
}
protected function tearDown()
{
$this->truncateRuntimeIndex('yii2_test_rt_index');
parent::tearDown();
}
// Tests :
public function testFind()
{
// find one
$result = ArticleIndex::find();
$this->assertTrue($result instanceof ActiveQuery);
$article = $result->one();
$this->assertTrue($article instanceof ArticleIndex);
// find all
$articles = ArticleIndex::find()->all();
$this->assertEquals(2, count($articles));
$this->assertTrue($articles[0] instanceof ArticleIndex);
$this->assertTrue($articles[1] instanceof ArticleIndex);
// find fulltext
$articles = ArticleIndex::find('cats');
$this->assertEquals(1, count($articles));
$this->assertTrue($articles[0] instanceof ArticleIndex);
$this->assertEquals(1, $articles[0]->id);
// find by column values
$article = ArticleIndex::find(['id' => 2, 'author_id' => 2]);
$this->assertTrue($article instanceof ArticleIndex);
$this->assertEquals(2, $article->id);
$this->assertEquals(2, $article->author_id);
$article = ArticleIndex::find(['id' => 2, 'author_id' => 1]);
$this->assertNull($article);
// find by attributes
$article = ArticleIndex::find()->where(['author_id' => 2])->one();
$this->assertTrue($article instanceof ArticleIndex);
$this->assertEquals(2, $article->id);
// find custom column
$article = ArticleIndex::find()->select(['*', '(5*2) AS custom_column'])
->where(['author_id' => 1])->one();
$this->assertEquals(1, $article->id);
$this->assertEquals(10, $article->custom_column);
// find count, sum, average, min, max, scalar
$this->assertEquals(2, ArticleIndex::find()->count());
$this->assertEquals(1, ArticleIndex::find()->where('id=1')->count());
$this->assertEquals(3, ArticleIndex::find()->sum('id'));
$this->assertEquals(1.5, ArticleIndex::find()->average('id'));
$this->assertEquals(1, ArticleIndex::find()->min('id'));
$this->assertEquals(2, ArticleIndex::find()->max('id'));
$this->assertEquals(2, ArticleIndex::find()->select('COUNT(*)')->scalar());
// scope
$this->assertEquals(1, ArticleIndex::find()->favoriteAuthor()->count());
// asArray
$article = ArticleIndex::find()->where('id=2')->asArray()->one();
$this->assertEquals([
'id' => '2',
'author_id' => '2',
'add_date' => '1384466400',
'tag' => '3,4',
], $article);
// indexBy
$articles = ArticleIndex::find()->indexBy('author_id')->orderBy('id DESC')->all();
$this->assertEquals(2, count($articles));
$this->assertTrue($articles['1'] instanceof ArticleIndex);
$this->assertTrue($articles['2'] instanceof ArticleIndex);
// indexBy callable
$articles = ArticleIndex::find()->indexBy(function ($article) {
return $article->id . '-' . $article->author_id;
})->orderBy('id DESC')->all();
$this->assertEquals(2, count($articles));
$this->assertTrue($articles['1-1'] instanceof ArticleIndex);
$this->assertTrue($articles['2-2'] instanceof ArticleIndex);
}
public function testFindBySql()
{
// find one
$article = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index ORDER BY id DESC')->one();
$this->assertTrue($article instanceof ArticleIndex);
$this->assertEquals(2, $article->author_id);
// find all
$articles = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index')->all();
$this->assertEquals(2, count($articles));
// find with parameter binding
$article = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index WHERE id=:id', [':id' => 2])->one();
$this->assertTrue($article instanceof ArticleIndex);
$this->assertEquals(2, $article->author_id);
}
public function testInsert()
{
$record = new RuntimeIndex;
$record->id = 15;
$record->title = 'test title';
$record->content = 'test content';
$record->type_id = 7;
$record->category = [1, 2];
$this->assertTrue($record->isNewRecord);
$record->save();
$this->assertEquals(15, $record->id);
$this->assertFalse($record->isNewRecord);
}
/**
* @depends testInsert
*/
public function testUpdate()
{
$record = new RuntimeIndex;
$record->id = 2;
$record->title = 'test title';
$record->content = 'test content';
$record->type_id = 7;
$record->category = [1, 2];
$record->save();
// save
$record = RuntimeIndex::find(['id' => 2]);
$this->assertTrue($record instanceof RuntimeIndex);
$this->assertEquals(7, $record->type_id);
$this->assertFalse($record->isNewRecord);
$record->type_id = 9;
$record->save();
$this->assertEquals(9, $record->type_id);
$this->assertFalse($record->isNewRecord);
$record2 = RuntimeIndex::find(['id' => 2]);
$this->assertEquals(9, $record2->type_id);
// replace
$query = 'replace';
$rows = RuntimeIndex::find($query);
$this->assertEmpty($rows);
$record = RuntimeIndex::find(['id' => 2]);
$record->content = 'Test content with ' . $query;
$record->save();
$rows = RuntimeIndex::find($query);
$this->assertNotEmpty($rows);
// updateAll
$pk = ['id' => 2];
$ret = RuntimeIndex::updateAll(['type_id' => 55], $pk);
$this->assertEquals(1, $ret);
$record = RuntimeIndex::find($pk);
$this->assertEquals(55, $record->type_id);
}
/**
* @depends testInsert
*/
public function testDelete()
{
// delete
$record = new RuntimeIndex;
$record->id = 2;
$record->title = 'test title';
$record->content = 'test content';
$record->type_id = 7;
$record->category = [1, 2];
$record->save();
$record = RuntimeIndex::find(['id' => 2]);
$record->delete();
$record = RuntimeIndex::find(['id' => 2]);
$this->assertNull($record);
// deleteAll
$record = new RuntimeIndex;
$record->id = 2;
$record->title = 'test title';
$record->content = 'test content';
$record->type_id = 7;
$record->category = [1, 2];
$record->save();
$ret = RuntimeIndex::deleteAll('id = 2');
$this->assertEquals(1, $ret);
$records = RuntimeIndex::find()->all();
$this->assertEquals(0, count($records));
}
public function testCallSnippets()
{
$query = 'pencil';
$source = 'Some data sentence about ' . $query;
$snippet = ArticleIndex::callSnippets($source, $query);
$this->assertNotEmpty($snippet, 'Unable to call snippets!');
$this->assertContains('<b>' . $query . '</b>', $snippet, 'Query not present in the snippet!');
$rows = ArticleIndex::callSnippets([$source], $query);
$this->assertNotEmpty($rows, 'Unable to call snippets!');
$this->assertContains('<b>' . $query . '</b>', $rows[0], 'Query not present in the snippet!');
}
public function testCallKeywords()
{
$text = 'table pencil';
$rows = ArticleIndex::callKeywords($text);
$this->assertNotEmpty($rows, 'Unable to call keywords!');
$this->assertArrayHasKey('tokenized', $rows[0], 'No tokenized keyword!');
$this->assertArrayHasKey('normalized', $rows[0], 'No normalized keyword!');
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
use yiiunit\data\sphinx\ar\ArticleIndex;
use yiiunit\data\sphinx\ar\ArticleDb;
/**
* @group sphinx
*/
class ActiveRelationTest extends SphinxTestCase
{
protected function setUp()
{
parent::setUp();
ActiveRecord::$db = $this->getConnection();
ActiveRecordDb::$db = $this->getDbConnection();
}
// Tests :
public function testFindLazy()
{
/** @var ArticleDb $article */
$article = ArticleDb::find(['id' => 2]);
$this->assertFalse($article->isRelationPopulated('index'));
$index = $article->index;
$this->assertTrue($article->isRelationPopulated('index'));
$this->assertTrue($index instanceof ArticleIndex);
$this->assertEquals(1, count($article->populatedRelations));
}
public function testFindEager()
{
$articles = ArticleDb::find()->with('index')->all();
$this->assertEquals(2, count($articles));
$this->assertTrue($articles[0]->isRelationPopulated('index'));
$this->assertTrue($articles[1]->isRelationPopulated('index'));
$this->assertTrue($articles[0]->index instanceof ArticleIndex);
$this->assertTrue($articles[1]->index instanceof ArticleIndex);
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yii\sphinx\ColumnSchema;
/**
* @group sphinx
*/
class ColumnSchemaTest extends SphinxTestCase
{
/**
* Data provider for [[testTypeCast]]
* @return array test data.
*/
public function dataProviderTypeCast()
{
return [
[
'integer',
'integer',
5,
5
],
[
'integer',
'integer',
'5',
5
],
[
'string',
'string',
5,
'5'
],
];
}
/**
* @dataProvider dataProviderTypeCast
*
* @param $type
* @param $phpType
* @param $value
* @param $expectedResult
*/
public function testTypeCast($type, $phpType, $value, $expectedResult)
{
$columnSchema = new ColumnSchema();
$columnSchema->type = $type;
$columnSchema->phpType = $phpType;
$this->assertEquals($expectedResult, $columnSchema->typecast($value));
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yii\sphinx\Connection;
/**
* @group sphinx
*/
class ConnectionTest extends SphinxTestCase
{
public function testConstruct()
{
$connection = $this->getConnection(false);
$params = $this->sphinxConfig;
$this->assertEquals($params['dsn'], $connection->dsn);
$this->assertEquals($params['username'], $connection->username);
$this->assertEquals($params['password'], $connection->password);
}
public function testOpenClose()
{
$connection = $this->getConnection(false, false);
$this->assertFalse($connection->isActive);
$this->assertEquals(null, $connection->pdo);
$connection->open();
$this->assertTrue($connection->isActive);
$this->assertTrue($connection->pdo instanceof \PDO);
$connection->close();
$this->assertFalse($connection->isActive);
$this->assertEquals(null, $connection->pdo);
$connection = new Connection;
$connection->dsn = 'unknown::memory:';
$this->setExpectedException('yii\db\Exception');
$connection->open();
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
use yiiunit\data\sphinx\ar\ArticleIndex;
use yiiunit\data\sphinx\ar\ArticleDb;
use yiiunit\data\sphinx\ar\TagDb;
/**
* @group sphinx
*/
class ExternalActiveRelationTest extends SphinxTestCase
{
protected function setUp()
{
parent::setUp();
ActiveRecord::$db = $this->getConnection();
ActiveRecordDb::$db = $this->getDbConnection();
}
// Tests :
public function testFindLazy()
{
/** @var ArticleIndex $article */
$article = ArticleIndex::find(['id' => 2]);
// has one :
$this->assertFalse($article->isRelationPopulated('source'));
$source = $article->source;
$this->assertTrue($article->isRelationPopulated('source'));
$this->assertTrue($source instanceof ArticleDb);
$this->assertEquals(1, count($article->populatedRelations));
// has many :
/*$this->assertFalse($article->isRelationPopulated('tags'));
$tags = $article->tags;
$this->assertTrue($article->isRelationPopulated('tags'));
$this->assertEquals(3, count($tags));
$this->assertTrue($tags[0] instanceof TagDb);*/
}
public function testFindEager()
{
// has one :
$articles = ArticleIndex::find()->with('source')->all();
$this->assertEquals(2, count($articles));
$this->assertTrue($articles[0]->isRelationPopulated('source'));
$this->assertTrue($articles[1]->isRelationPopulated('source'));
$this->assertTrue($articles[0]->source instanceof ArticleDb);
$this->assertTrue($articles[1]->source instanceof ArticleDb);
// has many :
/*$articles = ArticleIndex::find()->with('tags')->all();
$this->assertEquals(2, count($articles));
$this->assertTrue($articles[0]->isRelationPopulated('tags'));
$this->assertTrue($articles[1]->isRelationPopulated('tags'));*/
}
/**
* @depends testFindEager
*/
public function testFindWithSnippets()
{
$articles = ArticleIndex::find()
->match('about')
->with('source')
->snippetByModel()
->all();
$this->assertEquals(2, count($articles));
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yii\sphinx\Query;
/**
* @group sphinx
*/
class QueryTest extends SphinxTestCase
{
public function testSelect()
{
// default
$query = new Query;
$query->select('*');
$this->assertEquals(['*'], $query->select);
$this->assertNull($query->distinct);
$this->assertEquals(null, $query->selectOption);
$query = new Query;
$query->select('id, name', 'something')->distinct(true);
$this->assertEquals(['id', 'name'], $query->select);
$this->assertTrue($query->distinct);
$this->assertEquals('something', $query->selectOption);
}
public function testFrom()
{
$query = new Query;
$query->from('tbl_user');
$this->assertEquals(['tbl_user'], $query->from);
}
public function testMatch()
{
$query = new Query;
$match = 'test match';
$query->match($match);
$this->assertEquals($match, $query->match);
$command = $query->createCommand($this->getConnection(false));
$this->assertContains('MATCH(', $command->getSql(), 'No MATCH operator present!');
$this->assertContains($match, $command->params, 'No match query among params!');
}
public function testWhere()
{
$query = new Query;
$query->where('id = :id', [':id' => 1]);
$this->assertEquals('id = :id', $query->where);
$this->assertEquals([':id' => 1], $query->params);
$query->andWhere('name = :name', [':name' => 'something']);
$this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where);
$this->assertEquals([':id' => 1, ':name' => 'something'], $query->params);
$query->orWhere('age = :age', [':age' => '30']);
$this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where);
$this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params);
}
public function testGroup()
{
$query = new Query;
$query->groupBy('team');
$this->assertEquals(['team'], $query->groupBy);
$query->addGroupBy('company');
$this->assertEquals(['team', 'company'], $query->groupBy);
$query->addGroupBy('age');
$this->assertEquals(['team', 'company', 'age'], $query->groupBy);
}
public function testOrder()
{
$query = new Query;
$query->orderBy('team');
$this->assertEquals(['team' => SORT_ASC], $query->orderBy);
$query->addOrderBy('company');
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy);
$query->addOrderBy('age');
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy);
$query->addOrderBy(['age' => SORT_DESC]);
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy);
$query->addOrderBy('age ASC, company DESC');
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy);
}
public function testLimitOffset()
{
$query = new Query;
$query->limit(10)->offset(5);
$this->assertEquals(10, $query->limit);
$this->assertEquals(5, $query->offset);
}
public function testWithin()
{
$query = new Query;
$query->within('team');
$this->assertEquals(['team' => SORT_ASC], $query->within);
$query->addWithin('company');
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->within);
$query->addWithin('age');
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->within);
$query->addWithin(['age' => SORT_DESC]);
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->within);
$query->addWithin('age ASC, company DESC');
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->within);
}
public function testOptions()
{
$query = new Query;
$options = [
'cutoff' => 50,
'max_matches' => 50,
];
$query->options($options);
$this->assertEquals($options, $query->options);
$newMaxMatches = $options['max_matches'] + 10;
$query->addOptions(['max_matches' => $newMaxMatches]);
$this->assertEquals($newMaxMatches, $query->options['max_matches']);
}
public function testRun()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('yii2_test_article_index')
->match('about')
->options([
'cutoff' => 50,
'field_weights' => [
'title' => 10,
'content' => 3,
],
])
->all($connection);
$this->assertNotEmpty($rows);
}
/**
* @depends testRun
*/
public function testSnippet()
{
$connection = $this->getConnection();
$match = 'about';
$snippetPrefix = 'snippet#';
$snippetCallback = function() use ($match, $snippetPrefix) {
return [
$snippetPrefix . '1: ' . $match,
$snippetPrefix . '2: ' . $match,
];
};
$snippetOptions = [
'before_match' => '[',
'after_match' => ']',
];
$query = new Query;
$rows = $query->from('yii2_test_article_index')
->match($match)
->snippetCallback($snippetCallback)
->snippetOptions($snippetOptions)
->all($connection);
$this->assertNotEmpty($rows);
foreach ($rows as $row) {
$this->assertContains($snippetPrefix, $row['snippet'], 'Snippet source not present!');
$this->assertContains($snippetOptions['before_match'] . $match, $row['snippet'] . $snippetOptions['after_match'], 'Options not applied!');
}
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yii\caching\FileCache;
use yii\sphinx\Schema;
/**
* @group sphinx
*/
class SchemaTest extends SphinxTestCase
{
public function testFindIndexNames()
{
$schema = $this->getConnection()->schema;
$indexes = $schema->getIndexNames();
$this->assertContains('yii2_test_article_index', $indexes);
$this->assertContains('yii2_test_item_index', $indexes);
$this->assertContains('yii2_test_rt_index', $indexes);
}
public function testGetIndexSchemas()
{
$schema = $this->getConnection()->schema;
$indexes = $schema->getIndexSchemas();
$this->assertEquals(count($schema->getIndexNames()), count($indexes));
foreach($indexes as $index) {
$this->assertInstanceOf('yii\sphinx\IndexSchema', $index);
}
}
public function testGetNonExistingIndexSchema()
{
$this->assertNull($this->getConnection()->schema->getIndexSchema('non_existing_index'));
}
public function testSchemaRefresh()
{
$schema = $this->getConnection()->schema;
$schema->db->enableSchemaCache = true;
$schema->db->schemaCache = new FileCache();
$noCacheIndex = $schema->getIndexSchema('yii2_test_rt_index', true);
$cachedIndex = $schema->getIndexSchema('yii2_test_rt_index', true);
$this->assertEquals($noCacheIndex, $cachedIndex);
}
public function testGetPDOType()
{
$values = [
[null, \PDO::PARAM_NULL],
['', \PDO::PARAM_STR],
['hello', \PDO::PARAM_STR],
[0, \PDO::PARAM_INT],
[1, \PDO::PARAM_INT],
[1337, \PDO::PARAM_INT],
[true, \PDO::PARAM_BOOL],
[false, \PDO::PARAM_BOOL],
[$fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB],
];
$schema = $this->getConnection()->schema;
foreach($values as $value) {
$this->assertEquals($value[1], $schema->getPdoType($value[0]));
}
fclose($fp);
}
public function testIndexType()
{
$schema = $this->getConnection()->schema;
$index = $schema->getIndexSchema('yii2_test_article_index');
$this->assertEquals('local', $index->type);
$this->assertFalse($index->isRuntime);
$index = $schema->getIndexSchema('yii2_test_rt_index');
$this->assertEquals('rt', $index->type);
$this->assertTrue($index->isRuntime);
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\sphinx;
use yii\helpers\FileHelper;
use yii\sphinx\Connection;
use Yii;
use yiiunit\TestCase as TestCase;
/**
* Base class for the Sphinx test cases.
*/
class SphinxTestCase extends TestCase
{
/**
* @var array Sphinx connection configuration.
*/
protected $sphinxConfig = [
'dsn' => 'mysql:host=127.0.0.1;port=9306;',
'username' => '',
'password' => '',
];
/**
* @var Connection Sphinx connection instance.
*/
protected $sphinx;
/**
* @var array Database connection configuration.
*/
protected $dbConfig = [
'dsn' => 'mysql:host=127.0.0.1;',
'username' => '',
'password' => '',
];
/**
* @var \yii\db\Connection database connection instance.
*/
protected $db;
public static function setUpBeforeClass()
{
static::loadClassMap();
}
protected function setUp()
{
parent::setUp();
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) {
$this->markTestSkipped('pdo and pdo_mysql extension are required.');
}
$config = $this->getParam('sphinx');
if (!empty($config)) {
$this->sphinxConfig = $config['sphinx'];
$this->dbConfig = $config['db'];
}
$this->mockApplication();
static::loadClassMap();
}
protected function tearDown()
{
if ($this->sphinx) {
$this->sphinx->close();
}
$this->destroyApplication();
}
/**
* Adds sphinx extension files to [[Yii::$classPath]],
* avoiding the necessity of usage Composer autoloader.
*/
protected static function loadClassMap()
{
$baseNameSpace = 'yii/sphinx';
$basePath = realpath(__DIR__. '/../../../../extensions/sphinx');
$files = FileHelper::findFiles($basePath);
foreach ($files as $file) {
$classRelativePath = str_replace($basePath, '', $file);
$classFullName = str_replace(['/', '.php'], ['\\', ''], $baseNameSpace . $classRelativePath);
Yii::$classMap[$classFullName] = $file;
}
}
/**
* @param bool $reset whether to clean up the test database
* @param bool $open whether to open test database
* @return \yii\sphinx\Connection
*/
public function getConnection($reset = false, $open = true)
{
if (!$reset && $this->sphinx) {
return $this->sphinx;
}
$db = new Connection;
$db->dsn = $this->sphinxConfig['dsn'];
if (isset($this->sphinxConfig['username'])) {
$db->username = $this->sphinxConfig['username'];
$db->password = $this->sphinxConfig['password'];
}
if (isset($this->sphinxConfig['attributes'])) {
$db->attributes = $this->sphinxConfig['attributes'];
}
if ($open) {
$db->open();
}
$this->sphinx = $db;
return $db;
}
/**
* Truncates the runtime index.
* @param string $indexName index name.
*/
protected function truncateRuntimeIndex($indexName)
{
if ($this->sphinx) {
$this->sphinx->createCommand('TRUNCATE RTINDEX ' . $indexName)->execute();
}
}
/**
* @param bool $reset whether to clean up the test database
* @param bool $open whether to open and populate test database
* @return \yii\db\Connection
*/
public function getDbConnection($reset = true, $open = true)
{
if (!$reset && $this->db) {
return $this->db;
}
$db = new \yii\db\Connection;
$db->dsn = $this->dbConfig['dsn'];
if (isset($this->dbConfig['username'])) {
$db->username = $this->dbConfig['username'];
$db->password = $this->dbConfig['password'];
}
if (isset($this->dbConfig['attributes'])) {
$db->attributes = $this->dbConfig['attributes'];
}
if ($open) {
$db->open();
if (!empty($this->dbConfig['fixture'])) {
$lines = explode(';', file_get_contents($this->dbConfig['fixture']));
foreach ($lines as $line) {
if (trim($line) !== '') {
$db->pdo->exec($line);
}
}
}
}
$this->db = $db;
return $db;
}
}
\ No newline at end of file
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