Skip to content

Commit

Permalink
Merge pull request #552 from sergeyklay/2.0.x
Browse files Browse the repository at this point in the history
Fixed NestedSet::moveAsFirst, appendTo, prependTo
  • Loading branch information
sergeyklay committed Mar 2, 2016
2 parents 0bbf7b5 + 98320d6 commit 5517545
Show file tree
Hide file tree
Showing 8 changed files with 991 additions and 142 deletions.
340 changes: 203 additions & 137 deletions Library/Phalcon/Mvc/Model/Behavior/NestedSet.php

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions Library/Phalcon/Mvc/Model/Behavior/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ There are two ways this behavior can work: one tree per table and multiple trees
The mode is selected based on the value of `hasManyRoots` option that is `false` by default meaning single tree mode.
In multiple trees mode you can set `rootAttribute` option to match existing field in the table storing the tree.

### Example schema

```sql
CREATE TABLE `categories` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128) NOT NULL,
`description` TEXT DEFAULT NULL,
`root` INT UNSIGNED DEFAULT NULL,
`lft` INT UNSIGNED NOT NULL,
`rgt` INT UNSIGNED NOT NULL,
`level` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_coordinates` (`lft`,`rgt`,`root`),
KEY `category_root` (`root`),
KEY `category_lft` (`lft`),
KEY `category_lft_root` (`lft`, `root`),
KEY `category_rgt` (`rgt`),
KEY `category_rgt_root` (`rgt`, `root`),
KEY `category_level` (`level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```

### Selecting from a tree

In the following we'll use an example model Category with the following in its DB:
Expand Down Expand Up @@ -358,6 +380,20 @@ for ($i = $level; $i; $i--) {
}
```

Or just:

```php
$order = 'lft'; // or 'root, lft' for multiple trees
$categories = Categories::find(['order' => $order]);

$result = [];
foreach ($categories as $category) {
$result[] = str_repeat(' ', ($category->level - 1) * 5) . $category->name;
}

echo print_r($result, true), PHP_EOL;
```

## Blameable

```php
Expand Down
10 changes: 6 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
"swiftmailer/swiftmailer": "~5.2"
},
"require-dev": {
"squizlabs/php_codesniffer": "^2.5",
"codeception/codeception": "^2.1",
"codeception/mockery-module": "^0.2",
"codeception/aerospike-module": "^0.1"
"squizlabs/php_codesniffer": "~2.5",
"codeception/codeception": "~2.1",
"codeception/mockery-module": "~0.2",
"codeception/aerospike-module": "~0.1",
"codeception/specify": "~0.4",
"codeception/verify": "~0.3"
},
"suggest": {
"ext-aerospike": "*",
Expand Down
2 changes: 1 addition & 1 deletion docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ We use the following settings of these services:
+ Username: `root`
+ Password: `''` _(empty string)_
+ DB Name: `incubator_tests`
+ Charset: `urf8`
+ Charset: `utf8`

You can change the connection settings of these services **before** running tests
by using [environment variables][4]:
Expand Down
19 changes: 19 additions & 0 deletions tests/_data/dump.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
DROP TABLE IF EXISTS `categories`;
CREATE TABLE `categories` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128) NOT NULL,
`description` TEXT DEFAULT NULL,
`root` INT UNSIGNED DEFAULT NULL,
`lft` INT UNSIGNED NOT NULL,
`rgt` INT UNSIGNED NOT NULL,
`level` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_coordinates` (`lft`,`rgt`,`root`),
KEY `category_root` (`root`),
KEY `category_lft` (`lft`),
KEY `category_lft_root` (`lft`, `root`),
KEY `category_rgt` (`rgt`),
KEY `category_rgt_root` (`rgt`, `root`),
KEY `category_level` (`level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `bug`;
CREATE TABLE `bug` (
`id` serial,
Expand Down
232 changes: 232 additions & 0 deletions tests/unit/Mvc/Model/Behavior/Helper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
<?php

namespace Phalcon\Test\Mvc\Model\Behavior;

use Mockery;
use Phalcon\Di;
use Phalcon\Mvc\ModelInterface;
use UnitTester;
use ReflectionProperty;
use CategoriesManyRoots;
use Phalcon\DiInterface;
use Codeception\Specify;
use Phalcon\Mvc\Model\Manager;
use Codeception\TestCase\Test;
use Phalcon\Mvc\Model\Metadata;
use Phalcon\Db\Adapter\Pdo\Mysql;
use Codeception\Specify\Config as SpecifyConfig;
use Phalcon\Mvc\Model\Behavior\NestedSet as NestedSetBehavior;

/**
* \Phalcon\Test\Mvc\Model\Behavior\Helper
* Helper class for Phalcon\Test\Mvc\Model\Behavior tests
*
* @copyright (c) 2011-2016 Phalcon Team
* @link http://www.phalconphp.com
* @author Serghei Iakovlev <[email protected]>
* @package Phalcon\Test\Mvc\Model\Behavior
*
* The contents of this file are subject to the New BSD License that is
* bundled with this package in the file docs/LICENSE.txt
*
* If you did not receive a copy of the license and are unable to obtain it
* through the world-wide-web, please send an email to [email protected]
* so that we can send you a copy immediately.
*/
class Helper extends Test
{
use Specify;

/**
* UnitTester Object
* @var UnitTester
*/
protected $tester;

/**
* @var DiInterface
*/
protected $previousDependencyInjector;

/**
* executed before each test
*/
protected function _before()
{
require_once 'Stubs/Categories.php';

$this->previousDependencyInjector = Di::getDefault();

$di = new Di();

$di->setShared('modelsMetadata', new Metadata\Memory());
$di->setShared('modelsManager', new Manager());
$di->setShared('db', function () {
return new Mysql([
'host' => TEST_DB_HOST,
'port' => TEST_DB_PORT,
'username' => TEST_DB_USER,
'password' => TEST_DB_PASSWD,
'dbname' => TEST_DB_NAME,
'charset' => TEST_DB_CHARSET,
]);
});

if ($this->previousDependencyInjector instanceof DiInterface) {
Di::setDefault($di);
}

SpecifyConfig::setDeepClone(false);

$this->truncateTable(CategoriesManyRoots::$table);
}

/**
* executed after each test
*/
protected function _after()
{
if ($this->previousDependencyInjector instanceof DiInterface) {
Di::setDefault($this->previousDependencyInjector);
} else {
Di::reset();
}
}

protected function getProperty($propertyName, NestedSetBehavior $behavior)
{
$property = new ReflectionProperty(get_class($behavior), $propertyName);
$property->setAccessible(true);

return $property->getValue($behavior);
}

/**
* @return \Phalcon\Db\AdapterInterface
*/
protected function getConnection()
{
return Di::getDefault()->getShared('db');
}

/**
* @return \Pdo
*/
protected function getDbPdo()
{
return $this->getModule('Db')->dbh;
}

protected function truncateTable($table)
{
$this->getDbPdo()->query("TRUNCATE TABLE `{$table}`")->execute();
$this->getDbPdo()->query("ALTER TABLE `{$table}` AUTO_INCREMENT = 1")->execute();

$this->tester->seeNumRecords(0, $table);
}

protected function prettifyRoots($multipleTrees = true)
{
if ($multipleTrees) {
$order = 'root, lft';
} else {
$order = 'lft';
}

$categories = CategoriesManyRoots::find(['order' => $order]);

$result = [];
foreach ($categories as $category) {
$result[] = str_repeat(' ', ($category->level - 1) * 5) . $category->name;
}

return $result;
}

/**
* Checking the integrity of keys
*
* @param int|null $rootId
*/
protected function checkIntegrity($rootId = null)
{
$connection = $this->getConnection();

$sql = "SELECT COUNT(*) cnt FROM categories WHERE lft >= rgt";
if ($rootId) {
$sql .= " AND root = {$rootId}";
}

/** @var \Phalcon\Db\Result\Pdo $check1 */
$check1 = $connection->query($sql);
$this->assertEquals(['cnt' => '0'], $check1->fetch(\PDO::FETCH_ASSOC));


$sql = "SELECT COUNT(*) cnt, MIN(lft) min, MAX(rgt) max FROM categories";
if ($rootId) {
$sql .= " WHERE root = {$rootId}";
}

/** @var \Phalcon\Db\Result\Pdo $check2 */
$check2 = $connection->query($sql);
$result = $check2->fetch(\PDO::FETCH_ASSOC);

$this->assertEquals(1, $result['min']);
$this->assertEquals($result['cnt'] * 2, $result['max']);

$sql = "SELECT COUNT(*) cnt FROM categories WHERE MOD((rgt - lft), 2) = 0";
if ($rootId) {
$sql .= " AND root = {$rootId}";
}

/** @var \Phalcon\Db\Result\Pdo $check3 */
$check3 = $connection->query($sql);
$this->assertEquals(['cnt' => '0'], $check3->fetch(\PDO::FETCH_ASSOC));

$sql = "SELECT COUNT(*) cnt FROM categories WHERE MOD((lft - level + 2), 2) = 1";
if ($rootId) {
$sql .= " AND root = {$rootId}";
}

/** @var \Phalcon\Db\Result\Pdo $check4 */
$check4 = $connection->query($sql);
$this->assertEquals(['cnt' => '0'], $check4->fetch(\PDO::FETCH_ASSOC));
}

protected function createTree()
{
$cars = new CategoriesManyRoots();
$cars->name = 'Cars';
$cars->saveNode();

$ford = new CategoriesManyRoots();
$ford->name = 'Ford';

$audi = new CategoriesManyRoots();
$audi->name = 'Audi';

$mercedes = new CategoriesManyRoots();
$mercedes->name = 'Mercedes';

$ford->appendTo($cars);
$mercedes->insertAfter($ford);
$audi->insertBefore($ford);

$phones = new CategoriesManyRoots();
$phones->name = 'Mobile Phones';
$phones->saveNode();

$samsung = new CategoriesManyRoots();
$samsung->name = 'Samsung';

$motorola = new CategoriesManyRoots();
$motorola->name = 'Motorola';

$iphone = new CategoriesManyRoots();
$iphone->name = 'iPhone';

$samsung->appendTo($phones);
$motorola->insertAfter($samsung);
$iphone->prependTo($phones);
}
}
Loading

0 comments on commit 5517545

Please sign in to comment.