Commit bd589620 by Carsten Brandt

Merge branch 'master' into elasticsearch

* master: (59 commits) Refactored hasMany and hasOne so that they support cross-DBMS relationship. removed unused asset.php files fixed composer.json autoload pathes fixed c&p error allow installing yii2-dev and get the Yii.php file in the same place updated dev composer.json dependencies added composer.json for yii2-dev package fixed broken UniqueValidator removed call to nonexistsend property cleanup redis AR refactored Model and redis AR to allow drop of RecordSchema refactored redis AR to relect the latest changes "yii\swiftmailer\Mailer::createSwiftObject()" simplified. fixed empty result in findByPk list fixed problem with not closed transaction in deleteAll() fixed broken test apply changes to db\AR -> redis\AR added dependency in db\AR -> redis\AR needs to be refactored later redis AR cleanup ensure atomicity of operations ... Conflicts: tests/unit/data/config.php
parents e0fcecf2 a07facf4
{
"name": "yiisoft/yii2-dev",
"description": "Yii2 Web Programming Framework - Development Package",
"keywords": ["yii", "framework"],
"homepage": "http://www.yiiframework.com/",
"type": "yii2-extension",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/",
"role": "Founder and project lead"
},
{
"name": "Alexander Makarov",
"email": "sam@rmcreative.ru",
"homepage": "http://rmcreative.ru/",
"role": "Core framework development"
},
{
"name": "Maurizio Domba",
"homepage": "http://mdomba.info/",
"role": "Core framework development"
},
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc",
"homepage": "http://cebe.cc/",
"role": "Core framework development"
},
{
"name": "Timur Ruziev",
"email": "resurtm@gmail.com",
"homepage": "http://resurtm.com/",
"role": "Core framework development"
},
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com",
"role": "Core framework development"
},
{
"name": "Wei Zhuo",
"email": "weizhuo@gmail.com",
"role": "Project site maintenance and development"
},
{
"name": "Sebastián Thierer",
"email": "sebas@artfos.com",
"role": "Component development"
},
{
"name": "Jeffrey Winesett",
"email": "jefftulsa@gmail.com",
"role": "Documentation and marketing"
}
],
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"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"
},
"require": {
"php": ">=5.4.0",
"ext-mbstring": "*",
"lib-pcre": "*",
"yiisoft/jquery": "1.10.*",
"yiisoft/yii2-composer": "self.version",
"phpspec/php-diff": ">=1.0.2",
"ezyang/htmlpurifier": "4.5.*",
"michelf/php-markdown": "1.3.*",
"twbs/bootstrap": "3.0.*",
"smarty/smarty": "*",
"swiftmailer/swiftmailer": "*",
"twig/twig": "*"
},
"autoload": {
"psr-0": {
"yii\\bootstrap\\": "extensions/bootstrap/",
"yii\\debug\\": "extensions/debug/",
"yii\\gii\\": "extensions/gii/",
"yii\\jui\\": "extensions/jui/",
"yii\\smarty\\": "extensions/smarty/",
"yii\\swiftmailer\\": "extensions/swiftmailer/",
"yii\\twig\\": "extensions/twig/",
"yii\\": "framework/yii/"
}
}
}
...@@ -38,8 +38,14 @@ class Installer extends LibraryInstaller ...@@ -38,8 +38,14 @@ class Installer extends LibraryInstaller
*/ */
public function install(InstalledRepositoryInterface $repo, PackageInterface $package) public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
{ {
// install the package the normal composer way
parent::install($repo, $package); parent::install($repo, $package);
// add the package to yiisoft/extensions.php
$this->addPackage($package); $this->addPackage($package);
// ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
if ($package->getName() == 'yiisoft/yii2-dev') {
$this->linkYiiBaseFiles();
}
} }
/** /**
...@@ -50,6 +56,10 @@ class Installer extends LibraryInstaller ...@@ -50,6 +56,10 @@ class Installer extends LibraryInstaller
parent::update($repo, $initial, $target); parent::update($repo, $initial, $target);
$this->removePackage($initial); $this->removePackage($initial);
$this->addPackage($target); $this->addPackage($target);
// ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
if ($initial->getName() == 'yiisoft/yii2-dev') {
$this->linkYiiBaseFiles();
}
} }
/** /**
...@@ -57,8 +67,14 @@ class Installer extends LibraryInstaller ...@@ -57,8 +67,14 @@ class Installer extends LibraryInstaller
*/ */
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
{ {
// uninstall the package the normal composer way
parent::uninstall($repo, $package); parent::uninstall($repo, $package);
// remove the package from yiisoft/extensions.php
$this->removePackage($package); $this->removePackage($package);
// remove links for Yii.php
if ($package->getName() == 'yiisoft/yii2-dev') {
$this->removeYiiBaseFiles();
}
} }
protected function addPackage(PackageInterface $package) protected function addPackage(PackageInterface $package)
...@@ -145,6 +161,42 @@ class Installer extends LibraryInstaller ...@@ -145,6 +161,42 @@ class Installer extends LibraryInstaller
file_put_contents($file, "<?php\n\n\$vendorDir = dirname(__DIR__);\n\nreturn $array;\n"); file_put_contents($file, "<?php\n\n\$vendorDir = dirname(__DIR__);\n\nreturn $array;\n");
} }
protected function linkYiiBaseFiles()
{
$yiiDir = $this->vendorDir . '/yiisoft/yii2/yii';
if (!file_exists($yiiDir)) {
mkdir($yiiDir, 0777, true);
}
foreach(['Yii.php', 'YiiBase.php', 'classes.php'] as $file) {
file_put_contents($yiiDir . '/' . $file, <<<EOF
<?php
/**
* This is a link provided by the yiisoft/yii2-dev package via yii2-composer plugin.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
return require(__DIR__ . '/../../yii2-dev/framework/yii/$file');
EOF
);
}
}
protected function removeYiiBaseFiles()
{
$yiiDir = $this->vendorDir . '/yiisoft/yii2/yii';
foreach(['Yii.php', 'YiiBase.php', 'classes.php'] as $file) {
if (file_exists($yiiDir . '/' . $file)) {
unlink($yiiDir . '/' . $file);
}
}
if (file_exists($yiiDir)) {
rmdir($yiiDir);
}
}
/** /**
* Sets the correct permission for the files and directories listed in the extra section. * Sets the correct permission for the files and directories listed in the extra section.
......
<?php
return [
yii\jui\CoreAsset::className(),
yii\jui\EffectAsset::className(),
yii\jui\AccordionAsset::className(),
yii\jui\AutoCompleteAsset::className(),
yii\jui\ButtonAsset::className(),
yii\jui\DatePickerAsset::className(),
yii\jui\DatePickerRegionalAsset::className(),
yii\jui\ProgressBarAsset::className(),
yii\jui\ResizableAsset::className(),
yii\jui\SelectableAsset::className(),
yii\jui\SliderAsset::className(),
yii\jui\SortableAsset::className(),
yii\jui\SpinnerAsset::className(),
yii\jui\TabsAsset::className(),
yii\jui\TooltipAsset::className(),
yii\jui\DialogAsset::className(),
yii\jui\DraggableAsset::className(),
yii\jui\DroppableAsset::className(),
yii\jui\MenuAsset::className(),
];
...@@ -192,7 +192,7 @@ class Mailer extends BaseMailer ...@@ -192,7 +192,7 @@ class Mailer extends BaseMailer
} }
} }
unset($config['constructArgs']); unset($config['constructArgs']);
array_unshift($args, ['class' => $className]); array_unshift($args, $className);
$object = call_user_func_array(['Yii', 'createObject'], $args); $object = call_user_func_array(['Yii', 'createObject'], $args);
} else { } else {
$object = new $className; $object = new $className;
......
...@@ -74,6 +74,6 @@ ...@@ -74,6 +74,6 @@
"michelf/php-markdown": "1.3.*" "michelf/php-markdown": "1.3.*"
}, },
"autoload": { "autoload": {
"psr-0": { "yii\\": "/" } "psr-0": { "yii\\": "" }
} }
} }
<?php
return [
yii\web\YiiAsset::className(),
yii\web\JqueryAsset::className(),
yii\validators\PunycodeAsset::className(),
yii\validators\ValidationAsset::className(),
yii\widgets\ActiveFormAsset::className(),
yii\captcha\CaptchaAsset::className(),
yii\widgets\MaskedInputAsset::className(),
];
...@@ -229,14 +229,13 @@ class Model extends Component implements IteratorAggregate, ArrayAccess ...@@ -229,14 +229,13 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* You may override this method to change the default behavior. * You may override this method to change the default behavior.
* @return array list of attribute names. * @return array list of attribute names.
*/ */
public function attributes() public static function attributes()
{ {
$class = new ReflectionClass($this); $class = new ReflectionClass(get_called_class());
$names = []; $names = [];
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$name = $property->getName();
if (!$property->isStatic()) { if (!$property->isStatic()) {
$names[] = $name; $names[] = $property->getName();
} }
} }
return $names; return $names;
......
...@@ -475,7 +475,8 @@ class ActiveRecord extends Model ...@@ -475,7 +475,8 @@ class ActiveRecord extends Model
*/ */
public function hasOne($class, $link) public function hasOne($class, $link)
{ {
return $this->createActiveRelation([ /** @var ActiveRecord $class */
return $class::createActiveRelation([
'modelClass' => $class, 'modelClass' => $class,
'primaryModel' => $this, 'primaryModel' => $this,
'link' => $link, 'link' => $link,
...@@ -513,7 +514,8 @@ class ActiveRecord extends Model ...@@ -513,7 +514,8 @@ class ActiveRecord extends Model
*/ */
public function hasMany($class, $link) public function hasMany($class, $link)
{ {
return $this->createActiveRelation([ /** @var ActiveRecord $class */
return $class::createActiveRelation([
'modelClass' => $class, 'modelClass' => $class,
'primaryModel' => $this, 'primaryModel' => $this,
'link' => $link, 'link' => $link,
...@@ -528,7 +530,7 @@ class ActiveRecord extends Model ...@@ -528,7 +530,7 @@ class ActiveRecord extends Model
* @param array $config the configuration passed to the ActiveRelation class. * @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelation the newly created [[ActiveRelation]] instance. * @return ActiveRelation the newly created [[ActiveRelation]] instance.
*/ */
protected function createActiveRelation($config = []) public static function createActiveRelation($config = [])
{ {
return new ActiveRelation($config); return new ActiveRelation($config);
} }
...@@ -568,9 +570,9 @@ class ActiveRecord extends Model ...@@ -568,9 +570,9 @@ class ActiveRecord extends Model
* The default implementation will return all column names of the table associated with this AR class. * The default implementation will return all column names of the table associated with this AR class.
* @return array list of attribute names. * @return array list of attribute names.
*/ */
public function attributes() public static function attributes()
{ {
return array_keys($this->getTableSchema()->columns); return array_keys(static::getTableSchema()->columns);
} }
/** /**
...@@ -580,7 +582,7 @@ class ActiveRecord extends Model ...@@ -580,7 +582,7 @@ class ActiveRecord extends Model
*/ */
public function hasAttribute($name) public function hasAttribute($name)
{ {
return isset($this->_attributes[$name]) || isset($this->getTableSchema()->columns[$name]); return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
} }
/** /**
...@@ -1244,7 +1246,7 @@ class ActiveRecord extends Model ...@@ -1244,7 +1246,7 @@ class ActiveRecord extends Model
public static function create($row) public static function create($row)
{ {
$record = static::instantiate($row); $record = static::instantiate($row);
$columns = static::getTableSchema()->columns; $columns = array_flip(static::attributes());
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($columns[$name])) { if (isset($columns[$name])) {
$record->_attributes[$name] = $value; $record->_attributes[$name] = $value;
...@@ -1299,7 +1301,7 @@ class ActiveRecord extends Model ...@@ -1299,7 +1301,7 @@ class ActiveRecord extends Model
if ($relation instanceof ActiveRelationInterface) { if ($relation instanceof ActiveRelationInterface) {
return $relation; return $relation;
} else { } else {
return null; throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
} }
} catch (UnknownMethodException $e) { } catch (UnknownMethodException $e) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
...@@ -1336,9 +1338,7 @@ class ActiveRecord extends Model ...@@ -1336,9 +1338,7 @@ class ActiveRecord extends Model
if (is_array($relation->via)) { if (is_array($relation->via)) {
/** @var ActiveRelation $viaRelation */ /** @var ActiveRelation $viaRelation */
list($viaName, $viaRelation) = $relation->via; list($viaName, $viaRelation) = $relation->via;
/** @var ActiveRecord $viaClass */
$viaClass = $viaRelation->modelClass; $viaClass = $viaRelation->modelClass;
$viaTable = $viaClass::tableName();
// unset $viaName so that it can be reloaded to reflect the change // unset $viaName so that it can be reloaded to reflect the change
unset($this->_related[$viaName]); unset($this->_related[$viaName]);
} else { } else {
...@@ -1355,8 +1355,19 @@ class ActiveRecord extends Model ...@@ -1355,8 +1355,19 @@ class ActiveRecord extends Model
foreach ($extraColumns as $k => $v) { foreach ($extraColumns as $k => $v) {
$columns[$k] = $v; $columns[$k] = $v;
} }
static::getDb()->createCommand() if (is_array($relation->via)) {
->insert($viaTable, $columns)->execute(); /** @var $viaClass ActiveRecord */
/** @var $record ActiveRecord */
$record = new $viaClass();
foreach($columns as $column => $value) {
$record->$column = $value;
}
$record->insert(false);
} else {
/** @var $viaTable string */
static::getDb()->createCommand()
->insert($viaTable, $columns)->execute();
}
} else { } else {
$p1 = $model->isPrimaryKey(array_keys($relation->link)); $p1 = $model->isPrimaryKey(array_keys($relation->link));
$p2 = $this->isPrimaryKey(array_values($relation->link)); $p2 = $this->isPrimaryKey(array_values($relation->link));
...@@ -1411,9 +1422,7 @@ class ActiveRecord extends Model ...@@ -1411,9 +1422,7 @@ class ActiveRecord extends Model
if (is_array($relation->via)) { if (is_array($relation->via)) {
/** @var ActiveRelation $viaRelation */ /** @var ActiveRelation $viaRelation */
list($viaName, $viaRelation) = $relation->via; list($viaName, $viaRelation) = $relation->via;
/** @var ActiveRecord $viaClass */
$viaClass = $viaRelation->modelClass; $viaClass = $viaRelation->modelClass;
$viaTable = $viaClass::tableName();
unset($this->_related[$viaName]); unset($this->_related[$viaName]);
} else { } else {
$viaRelation = $relation->via; $viaRelation = $relation->via;
...@@ -1426,15 +1435,29 @@ class ActiveRecord extends Model ...@@ -1426,15 +1435,29 @@ class ActiveRecord extends Model
foreach ($relation->link as $a => $b) { foreach ($relation->link as $a => $b) {
$columns[$b] = $model->$a; $columns[$b] = $model->$a;
} }
$command = static::getDb()->createCommand(); if (is_array($relation->via)) {
if ($delete) { /** @var $viaClass ActiveRecord */
$command->delete($viaTable, $columns)->execute(); if ($delete) {
$viaClass::deleteAll($columns);
} else {
$nulls = [];
foreach (array_keys($columns) as $a) {
$nulls[$a] = null;
}
$viaClass::updateAll($nulls, $columns);
}
} else { } else {
$nulls = []; /** @var $viaTable string */
foreach (array_keys($columns) as $a) { $command = static::getDb()->createCommand();
$nulls[$a] = null; if ($delete) {
$command->delete($viaTable, $columns)->execute();
} else {
$nulls = [];
foreach (array_keys($columns) as $a) {
$nulls[$a] = null;
}
$command->update($viaTable, $nulls, $columns)->execute();
} }
$command->update($viaTable, $nulls, $columns)->execute();
} }
} else { } else {
$p1 = $model->isPrimaryKey(array_keys($relation->link)); $p1 = $model->isPrimaryKey(array_keys($relation->link));
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\helpers\StringHelper;
/**
* ActiveRecord is the base class for classes representing relational data in terms of objects.
*
* This class implements the ActiveRecord pattern for the [redis](http://redis.io/) key-value store.
*
* For defining a record a subclass should at least implement the [[attributes()]] method to define
* attributes. A primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified.
*
* The following is an example model called `Customer`:
*
* ```php
* class Customer extends \yii\redis\ActiveRecord
* {
* public function attributes()
* {
* return ['id', 'name', 'address', 'registration_date'];
* }
* }
* ```
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRecord extends \yii\db\ActiveRecord
{
/**
* Returns the database connection used by this AR class.
* By default, the "redis" application component is used as the database connection.
* You may override this method if you want to use a different database connection.
* @return Connection the database connection used by this AR class.
*/
public static function getDb()
{
return \Yii::$app->getComponent('redis');
}
/**
* @inheritDoc
*/
public static function createQuery()
{
return new ActiveQuery(['modelClass' => get_called_class()]);
}
/**
* @inheritDoc
*/
public static function createActiveRelation($config = [])
{
return new ActiveRelation($config);
}
/**
* Returns the primary key name(s) for this AR class.
* This method should be overridden by child classes to define the primary key.
*
* Note that an array should be returned even when it is a single primary key.
*
* @return string[] the primary keys of this record.
*/
public static function primaryKey()
{
return ['id'];
}
/**
* Returns the list of all attribute names of the model.
* This method must be overridden by child classes to define available attributes.
* @return array list of attribute names.
*/
public static function attributes()
{
throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.');
}
/**
* @inheritDocs
*/
public function insert($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
return false;
}
if ($this->beforeSave(true)) {
$db = static::getDb();
$values = $this->getDirtyAttributes($attributes);
$pk = [];
// if ($values === []) {
foreach ($this->primaryKey() as $key) {
$pk[$key] = $values[$key] = $this->getAttribute($key);
if ($pk[$key] === null) {
$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::tableName() . ':s:' . $key]);
$this->setAttribute($key, $values[$key]);
}
}
// }
// save pk in a findall pool
$db->executeCommand('RPUSH', [static::tableName(), static::buildKey($pk)]);
$key = static::tableName() . ':a:' . static::buildKey($pk);
// save attributes
$args = [$key];
foreach($values as $attribute => $value) {
$args[] = $attribute;
$args[] = $value;
}
$db->executeCommand('HMSET', $args);
$this->setOldAttributes($values);
$this->afterSave(true);
return true;
}
return false;
}
/**
* Updates the whole table using the provided attribute values and conditions.
* For example, to change the status to be 1 for all customers whose status is 2:
*
* ~~~
* Customer::updateAll(['status' => 1], ['id' => 2]);
* ~~~
*
* @param array $attributes attribute values (name-value pairs) to be saved into the table
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @param array $params this parameter is ignored in redis implementation.
* @return integer the number of rows updated
*/
public static function updateAll($attributes, $condition = null, $params = [])
{
if (empty($attributes)) {
return 0;
}
$db = static::getDb();
$n=0;
foreach(static::fetchPks($condition) as $pk) {
$newPk = $pk;
$pk = static::buildKey($pk);
$key = static::tableName() . ':a:' . $pk;
// save attributes
$args = [$key];
foreach($attributes as $attribute => $value) {
if (isset($newPk[$attribute])) {
$newPk[$attribute] = $value;
}
$args[] = $attribute;
$args[] = $value;
}
$newPk = static::buildKey($newPk);
$newKey = static::tableName() . ':a:' . $newPk;
// rename index if pk changed
if ($newPk != $pk) {
$db->executeCommand('MULTI');
$db->executeCommand('HMSET', $args);
$db->executeCommand('LINSERT', [static::tableName(), 'AFTER', $pk, $newPk]);
$db->executeCommand('LREM', [static::tableName(), 0, $pk]);
$db->executeCommand('RENAME', [$key, $newKey]);
$db->executeCommand('EXEC');
} else {
$db->executeCommand('HMSET', $args);
}
$n++;
}
return $n;
}
/**
* Updates the whole table using the provided counter changes and conditions.
* For example, to increment all customers' age by 1,
*
* ~~~
* Customer::updateAllCounters(['age' => 1]);
* ~~~
*
* @param array $counters the counters to be updated (attribute name => increment value).
* Use negative values if you want to decrement the counters.
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @param array $params this parameter is ignored in redis implementation.
* @return integer the number of rows updated
*/
public static function updateAllCounters($counters, $condition = null, $params = [])
{
if (empty($counters)) {
return 0;
}
$db = static::getDb();
$n=0;
foreach(static::fetchPks($condition) as $pk) {
$key = static::tableName() . ':a:' . static::buildKey($pk);
foreach($counters as $attribute => $value) {
$db->executeCommand('HINCRBY', [$key, $attribute, $value]);
}
$n++;
}
return $n;
}
/**
* Deletes rows in the table using the provided conditions.
* WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
*
* For example, to delete all customers whose status is 3:
*
* ~~~
* Customer::deleteAll(['status' => 3]);
* ~~~
*
* @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @param array $params this parameter is ignored in redis implementation.
* @return integer the number of rows deleted
*/
public static function deleteAll($condition = null, $params = [])
{
$db = static::getDb();
$attributeKeys = [];
$pks = static::fetchPks($condition);
$db->executeCommand('MULTI');
foreach($pks as $pk) {
$pk = static::buildKey($pk);
$db->executeCommand('LREM', [static::tableName(), 0, $pk]);
$attributeKeys[] = static::tableName() . ':a:' . $pk;
}
if (empty($attributeKeys)) {
$db->executeCommand('EXEC');
return 0;
}
$db->executeCommand('DEL', $attributeKeys);
$result = $db->executeCommand('EXEC');
return end($result);
}
private static function fetchPks($condition)
{
$query = static::createQuery();
$query->where($condition);
$records = $query->asArray()->all(); // TODO limit fetched columns to pk
$primaryKey = static::primaryKey();
$pks = [];
foreach($records as $record) {
$pk = [];
foreach($primaryKey as $key) {
$pk[$key] = $record[$key];
}
$pks[] = $pk;
}
return $pks;
}
/**
* Builds a normalized key from a given primary key value.
*
* @param mixed $key the key to be normalized
* @return string the generated key
*/
public static function buildKey($key)
{
if (is_numeric($key)) {
return $key;
} elseif (is_string($key)) {
return ctype_alnum($key) && StringHelper::strlen($key) <= 32 ? $key : md5($key);
} elseif (is_array($key)) {
if (count($key) == 1) {
return self::buildKey(reset($key));
}
ksort($key); // ensure order is always the same
$isNumeric = true;
foreach($key as $value) {
if (!is_numeric($value)) {
$isNumeric = false;
}
}
if ($isNumeric) {
return implode('-', $key);
}
}
return md5(json_encode($key));
}
/**
* @inheritdoc
*/
public static function getTableSchema()
{
throw new NotSupportedException('getTableSchema() is not supported by redis ActiveRecord');
}
/**
* @inheritdoc
*/
public static function findBySql($sql, $params = [])
{
throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord');
}
/**
* Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
* This method will always return false as transactional operations are not supported by redis.
* @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
* @return boolean whether the specified operation is transactional in the current [[scenario]].
*/
public function isTransactional($operation)
{
return false;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation between two Active Record classes.
*
* ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveRelation object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
/**
* Executes a script created by [[LuaScriptBuilder]]
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @param string $type the type of the script to generate
* @param null $column
* @return array|bool|null|string
*/
protected function executeScript($db, $type, $column=null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot table
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
return parent::executeScript($db, $type, $column);
}
}
<?php <?php
/** /**
* Connection class file
*
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC * @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
...@@ -22,8 +20,7 @@ use yii\helpers\Inflector; ...@@ -22,8 +20,7 @@ use yii\helpers\Inflector;
* *
* @property string $driverName Name of the DB driver. This property is read-only. * @property string $driverName Name of the DB driver. This property is read-only.
* @property boolean $isActive Whether the DB connection is established. This property is read-only. * @property boolean $isActive Whether the DB connection is established. This property is read-only.
* @property Transaction $transaction The currently active transaction. Null if no active transaction. This * @property LuaScriptBuilder $luaScriptBuilder This property is read-only.
* property is read-only.
* *
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
...@@ -203,10 +200,6 @@ class Connection extends Component ...@@ -203,10 +200,6 @@ class Connection extends Component
'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key
]; ];
/** /**
* @var Transaction the currently active transaction
*/
private $_transaction;
/**
* @var resource redis socket connection * @var resource redis socket connection
*/ */
private $_socket; private $_socket;
...@@ -283,7 +276,6 @@ class Connection extends Component ...@@ -283,7 +276,6 @@ class Connection extends Component
$this->executeCommand('QUIT'); $this->executeCommand('QUIT');
stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR);
$this->_socket = null; $this->_socket = null;
$this->_transaction = null;
} }
} }
...@@ -298,27 +290,6 @@ class Connection extends Component ...@@ -298,27 +290,6 @@ class Connection extends Component
} }
/** /**
* Returns the currently active transaction.
* @return Transaction the currently active transaction. Null if no active transaction.
*/
public function getTransaction()
{
return $this->_transaction && $this->_transaction->isActive ? $this->_transaction : null;
}
/**
* Starts a transaction.
* @return Transaction the transaction initiated
*/
public function beginTransaction()
{
$this->open();
$this->_transaction = new Transaction(['db' => $this]);
$this->_transaction->begin();
return $this->_transaction;
}
/**
* Returns the name of the DB driver for the current [[dsn]]. * Returns the name of the DB driver for the current [[dsn]].
* @return string name of the DB driver * @return string name of the DB driver
*/ */
...@@ -332,6 +303,14 @@ class Connection extends Component ...@@ -332,6 +303,14 @@ class Connection extends Component
} }
/** /**
* @return LuaScriptBuilder
*/
public function getLuaScriptBuilder()
{
return new LuaScriptBuilder();
}
/**
* *
* @param string $name * @param string $name
* @param array $params * @param array $params
......
<?php
/**
* Transaction class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\base\InvalidConfigException;
use yii\db\Exception;
/**
* Transaction represents a DB transaction.
*
* @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]]
* or [[rollBack()]]. This property is read-only.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Transaction extends \yii\base\Object
{
/**
* @var Connection the database connection that this transaction is associated with.
*/
public $db;
/**
* @var boolean whether this transaction is active. Only an active transaction
* can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started.
*/
private $_active = false;
/**
* Returns a value indicating whether this transaction is active.
* @return boolean whether this transaction is active. Only an active transaction
* can [[commit()]] or [[rollBack()]].
*/
public function getIsActive()
{
return $this->_active;
}
/**
* Begins a transaction.
* @throws InvalidConfigException if [[connection]] is null
*/
public function begin()
{
if (!$this->_active) {
if ($this->db === null) {
throw new InvalidConfigException('Transaction::db must be set.');
}
\Yii::trace('Starting transaction', __CLASS__);
$this->db->open();
$this->db->createCommand('MULTI')->execute();
$this->_active = true;
}
}
/**
* Commits a transaction.
* @throws Exception if the transaction or the DB connection is not active.
*/
public function commit()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Committing transaction', __CLASS__);
$this->db->createCommand('EXEC')->execute();
// TODO handle result of EXEC
$this->_active = false;
} else {
throw new Exception('Failed to commit transaction: transaction was inactive.');
}
}
/**
* Rolls back a transaction.
* @throws Exception if the transaction or the DB connection is not active.
*/
public function rollback()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Rolling back transaction', __CLASS__);
$this->db->pdo->commit();
$this->_active = false;
} else {
throw new Exception('Failed to roll back transaction: transaction was inactive.');
}
}
}
...@@ -64,13 +64,13 @@ class UniqueValidator extends Validator ...@@ -64,13 +64,13 @@ class UniqueValidator extends Validator
$className = $this->className === null ? get_class($object) : $this->className; $className = $this->className === null ? get_class($object) : $this->className;
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName; $attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
$table = $className::getTableSchema(); $attributes = $className::attributes();
if (($column = $table->getColumn($attributeName)) === null) { if (!in_array($attributeName, $attributes)) {
throw new InvalidConfigException("Table '{$table->name}' does not have a column named '$attributeName'."); throw new InvalidConfigException("'$className' does not have an attribute named '$attributeName'.");
} }
$query = $className::find(); $query = $className::find();
$query->where([$column->name => $value]); $query->where([$attributeName => $value]);
if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) { if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) {
// if current $object isn't in the database yet then it's OK just to call exists() // if current $object isn't in the database yet then it's OK just to call exists()
...@@ -82,7 +82,7 @@ class UniqueValidator extends Validator ...@@ -82,7 +82,7 @@ class UniqueValidator extends Validator
$n = count($objects); $n = count($objects);
if ($n === 1) { if ($n === 1) {
if ($column->isPrimaryKey) { if (in_array($attributeName, $className::primaryKey())) {
// primary key is modified and not unique // primary key is modified and not unique
$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey(); $exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
} else { } else {
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\data\ar\redis;
use yii\redis\Connection;
/**
* ActiveRecord is ...
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveRecord extends \yii\redis\ActiveRecord
{
public static $db;
public static function getDb()
{
return self::$db;
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\ar\redis;
class Customer extends ActiveRecord
{
const STATUS_ACTIVE = 1;
const STATUS_INACTIVE = 2;
public $status2;
public static function attributes()
{
return ['id', 'email', 'name', 'address', 'status'];
}
/**
* @return \yii\redis\ActiveRelation
*/
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
}
public static function active($query)
{
$query->andWhere(['status' => 1]);
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\ar\redis;
class Item extends ActiveRecord
{
public static function attributes()
{
return ['id', 'name', 'category_id'];
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\ar\redis;
class Order extends ActiveRecord
{
public static function attributes()
{
return ['id', 'customer_id', 'create_time', 'total'];
}
public function getCustomer()
{
return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
}
public function getOrderItems()
{
return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
}
public function getItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItems', function($q) {
// additional query configuration
});
}
public function getBooks()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItems', ['order_id' => 'id']);
//->where(['category_id' => 1]);
}
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
$this->create_time = time();
return true;
} else {
return false;
}
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\ar\redis;
use yii\redis\RecordSchema;
class OrderItem extends ActiveRecord
{
public static function primaryKey()
{
return ['order_id', 'item_id'];
}
public static function attributes()
{
return ['order_id', 'item_id', 'quantity', 'subtotal'];
}
public function getOrder()
{
return $this->hasOne(Order::className(), ['id' => 'order_id']);
}
public function getItem()
{
return $this->hasOne(Item::className(), ['id' => 'item_id']);
}
}
\ No newline at end of file
...@@ -32,5 +32,9 @@ return [ ...@@ -32,5 +32,9 @@ return [
'elasticsearch' => [ 'elasticsearch' => [
'dsn' => 'elasticsearch://localhost:9200' 'dsn' => 'elasticsearch://localhost:9200'
], ],
'redis' => [
'dsn' => 'redis://localhost:6379/0',
'password' => null,
],
], ],
]; ];
...@@ -70,10 +70,11 @@ class MailerTest extends VendorTestCase ...@@ -70,10 +70,11 @@ class MailerTest extends VendorTestCase
{ {
$mailer = new Mailer(); $mailer = new Mailer();
$class = 'Swift_SmtpTransport';
$host = 'some.test.host'; $host = 'some.test.host';
$port = 999; $port = 999;
$transportConfig = [ $transportConfig = [
'class' => 'Swift_SmtpTransport', 'class' => $class,
'constructArgs' => [ 'constructArgs' => [
$host, $host,
$port, $port,
...@@ -82,6 +83,7 @@ class MailerTest extends VendorTestCase ...@@ -82,6 +83,7 @@ class MailerTest extends VendorTestCase
$mailer->setTransport($transportConfig); $mailer->setTransport($transportConfig);
$transport = $mailer->getTransport(); $transport = $mailer->getTransport();
$this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); $this->assertTrue(is_object($transport), 'Unable to setup transport via config!');
$this->assertEquals($class, get_class($transport), 'Invalid transport class!');
$this->assertEquals($host, $transport->getHost(), 'Invalid transport host!'); $this->assertEquals($host, $transport->getHost(), 'Invalid transport host!');
$this->assertEquals($port, $transport->getPort(), 'Invalid transport host!'); $this->assertEquals($port, $transport->getPort(), 'Invalid transport host!');
} }
......
...@@ -40,6 +40,12 @@ class ActiveRecordTest extends DatabaseTestCase ...@@ -40,6 +40,12 @@ class ActiveRecordTest extends DatabaseTestCase
$customer = Customer::find(2); $customer = Customer::find(2);
$this->assertTrue($customer instanceof Customer); $this->assertTrue($customer instanceof Customer);
$this->assertEquals('user2', $customer->name); $this->assertEquals('user2', $customer->name);
$customer = Customer::find(5);
$this->assertNull($customer);
// query scalar
$customerName = Customer::find()->where(array('id' => 2))->select('name')->scalar();
$this->assertEquals('user2', $customerName);
// find by column values // find by column values
$customer = Customer::find(['id' => 2, 'name' => 'user2']); $customer = Customer::find(['id' => 2, 'name' => 'user2']);
......
<?php
namespace yiiunit\framework\redis;
use yii\redis\Connection;
/**
* @group redis
*/
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->open();
$this->assertTrue($db->ping());
$db->set('YIITESTKEY', 'YIITESTVALUE');
$db->close();
$db = new Connection();
$db->dsn = 'redis://localhost:6379/0';
$db->open();
$this->assertEquals('YIITESTVALUE', $db->get('YIITESTKEY'));
$db->close();
$db = new Connection();
$db->dsn = 'redis://localhost:6379/1';
$db->open();
$this->assertNull($db->get('YIITESTKEY'));
$db->close();
}
public function keyValueData()
{
return array(
array(123),
array(-123),
array(0),
array('test'),
array("test\r\ntest"),
array(''),
);
}
/**
* @dataProvider keyValueData
*/
public function testStoreGet($data)
{
$db = $this->getConnection(true);
$db->set('hi', $data);
$this->assertEquals($data, $db->get('hi'));
}
}
\ No newline at end of file
<?php
namespace yiiunit\framework\redis;
use yii\redis\Connection;
use yiiunit\TestCase;
/**
* RedisTestCase is the base class for all redis related test cases
*/
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'])) {
$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);
}
parent::setUp();
}
/**
* @param bool $reset whether to clean up the test database
* @return Connection
*/
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'];
if ($reset) {
$db->open();
$db->flushall();
}
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