From 9b966d3c8d47f3338705fd8ebe4a95d96ab542fa Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Mon, 1 Nov 2021 10:31:10 +0100 Subject: [PATCH 01/19] Adjusted composer.json --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 62079e9..551bcaa 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "kalnoy/nestedset", - "description": "Nested Set Model for Laravel 5.7 and up", + "name": "lychee-org/nestedset", + "description": "Nested Set Model for Laravel 5.7 and up (fork with patches for Lychee)", "keywords": ["laravel", "nested sets", "nsm", "database", "hierarchy"], "license": "MIT", From ad464b7fb24b5b45b87e5c06090c1aad2e8afec6 Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Mon, 1 Nov 2021 10:44:05 +0100 Subject: [PATCH 02/19] Adjusted `readme.md` --- README.markdown | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.markdown b/README.markdown index 40d6adb..a37832f 100644 --- a/README.markdown +++ b/README.markdown @@ -1,21 +1,18 @@ -[![Build Status](https://travis-ci.org/lazychaser/laravel-nestedset.svg?branch=master)](https://travis-ci.org/lazychaser/laravel-nestedset) -[![Total Downloads](https://poser.pugx.org/kalnoy/nestedset/downloads.svg)](https://packagist.org/packages/kalnoy/nestedset) -[![Latest Stable Version](https://poser.pugx.org/kalnoy/nestedset/v/stable.svg)](https://packagist.org/packages/kalnoy/nestedset) -[![Latest Unstable Version](https://poser.pugx.org/kalnoy/nestedset/v/unstable.svg)](https://packagist.org/packages/kalnoy/nestedset) -[![License](https://poser.pugx.org/kalnoy/nestedset/license.svg)](https://packagist.org/packages/kalnoy/nestedset) +[![Total Downloads](https://poser.pugx.org/lychee-org/nestedset/downloads.svg)](https://packagist.org/packages/lychee-org/nestedset) +[![Latest Stable Version](https://poser.pugx.org/lychee-org/nestedset/v/stable.svg)](https://packagist.org/packages/lychee-org/nestedset) +[![Latest Unstable Version](https://poser.pugx.org/lychee-org/nestedset/v/unstable.svg)](https://packagist.org/packages/lychee-org/nestedset) +[![License](https://poser.pugx.org/lychee-org/nestedset/license.svg)](https://packagist.org/packages/lychee-org/nestedset) This is a Laravel 4-8 package for working with trees in relational databases. +It is a fork of [lazychaser/laravel-nestedset](https://github.com/lazychaser/laravel-nestedset) and contains patches which are required for using the library with [Lychee](https://github.com/LycheeOrg/Lychee). + * **Laravel 5.7, 5.8, 6.0, 7.0, 8.0** is supported since v5 * **Laravel 5.5, 5.6** is supported since v4.3 * **Laravel 5.2, 5.3, 5.4** is supported since v4 * **Laravel 5.1** is supported in v3 * **Laravel 4** is supported in v2 -Although this project is completely free for use, I appreciate any support! - -- __[Donate via PayPal](https://www.paypal.me/lazychaser)__ - __Contents:__ - [Theory](#what-are-nested-sets) From e7e9fa0e6a606931c30990a3e7512ae62fee0d91 Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Mon, 1 Nov 2021 10:55:14 +0100 Subject: [PATCH 03/19] Fixes upstream issue #538 --- src/NestedSet.php | 2 +- src/Node.php | 363 +++++++++++++++++++++++++++++ tests/models/Category.php | 2 +- tests/models/DuplicateCategory.php | 2 +- tests/models/MenuItem.php | 2 +- 5 files changed, 367 insertions(+), 4 deletions(-) create mode 100644 src/Node.php diff --git a/src/NestedSet.php b/src/NestedSet.php index 8ec8e02..7045471 100644 --- a/src/NestedSet.php +++ b/src/NestedSet.php @@ -77,7 +77,7 @@ public static function getDefaultColumns() */ public static function isNode($node) { - return is_object($node) && in_array(NodeTrait::class, (array)$node); + return $node instanceof Node; } } \ No newline at end of file diff --git a/src/Node.php b/src/Node.php new file mode 100644 index 0000000..dfcb12f --- /dev/null +++ b/src/Node.php @@ -0,0 +1,363 @@ + + */ + public function getNextSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection + */ + public function getPrevSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Node + */ + public function getNextSibling(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Node + */ + public function getPrevSibling(array $columns = ['*']); + + /** + * @return array + */ + public function getBounds(); + + /** + * @param $value + * + * @return $this + */ + public function setLft($value); + + /** + * @param $value + * + * @return $this + */ + public function setRgt($value); + + /** + * @param $value + * + * @return $this + */ + public function setParentId($value); + + /** + * @param array|null $except + * + * @return $this + */ + public function replicate(array $except = null); +} diff --git a/tests/models/Category.php b/tests/models/Category.php index bcce8e9..0d336f3 100644 --- a/tests/models/Category.php +++ b/tests/models/Category.php @@ -2,7 +2,7 @@ use \Illuminate\Database\Eloquent\Model; -class Category extends Model { +class Category extends Model implements \Kalnoy\Nestedset\Node { use \Illuminate\Database\Eloquent\SoftDeletes, \Kalnoy\Nestedset\NodeTrait; diff --git a/tests/models/DuplicateCategory.php b/tests/models/DuplicateCategory.php index a6f619a..34311a9 100644 --- a/tests/models/DuplicateCategory.php +++ b/tests/models/DuplicateCategory.php @@ -1,6 +1,6 @@ Date: Mon, 1 Nov 2021 12:05:05 +0100 Subject: [PATCH 04/19] migrate to github actions (#2) Migrate to github actions --- .github/workflows/CI.yaml | 98 +++++ .gitignore | 2 + .travis.yml | 15 - composer.json | 21 +- phpunit.xml | 35 +- src/AncestorsRelation.php | 4 +- src/BaseRelation.php | 14 +- src/Collection.php | 17 +- src/DescendantsRelation.php | 7 +- src/NestedSet.php | 13 +- src/NestedSetServiceProvider.php | 2 +- src/Node.php | 680 +++++++++++++++---------------- src/NodeTrait.php | 61 ++- src/QueryBuilder.php | 83 ++-- tests/NodeTest.php | 29 +- tests/ScopedNodeTest.php | 8 +- 16 files changed, 618 insertions(+), 471 deletions(-) create mode 100644 .github/workflows/CI.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml new file mode 100644 index 0000000..a1008fb --- /dev/null +++ b/.github/workflows/CI.yaml @@ -0,0 +1,98 @@ +name: Tests + +# Run this workflow every time a new commit pushed to your repository +on: + push: + paths-ignore: + - '**/*.md' + pull_request: + paths-ignore: + - '**/*.md' + +jobs: + tests: + runs-on: ${{ matrix.operating-system }} + if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) + + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-20.04] + php-versions: ['7.4', '8.0', '8.1'] + dependencies: ['no', 'low', 'beta'] + include: + - operating-system: ubuntu-20.04 + php-versions: '8.0' + continue-on-error: true + + name: PHP ${{ matrix.php-versions }} - ${{ matrix.dependencies }} + + env: + extensions: curl json libxml dom + key: cache-v1 # can be any string, change to clear the extension cache. + + steps: + + # Checks out a copy of your repository on the ubuntu machine + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php-versions }} + extensions: ${{ env.extensions }} + key: ${{ env.key }} + + - name: Cache PHP Extensions + uses: actions/cache@v2 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: Cache Composer Dependencies + uses: actions/cache@v1 + with: + path: ~/.composer/cache/files + key: dependencies-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP Action + uses: shivammathur/setup-php@2.8.0 + with: + php-version: ${{ matrix.php-versions }} + extensions: ${{ env.extensions }} + coverage: xdebug + tools: pecl, composer + + - name: PHP Show modules + run: php -m + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + if: ${{ matrix.dependencies != 'low' }} + run: composer update --no-interaction + + - name: Install Composer dependencies + if: ${{ matrix.dependencies == 'low' }} + run: composer update -vvv --prefer-lowest --prefer-stable --no-interaction + + - name: Validate files + run: composer validate-files + + - name: Check Style + run: composer check-code-style + + # - name: Run tests + # run: composer run-tests diff --git a/.gitignore b/.gitignore index 8879189..24a2013 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ composer.phar composer.lock .DS_Store +.php-cs-fixer.cache +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d0ba93c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: php - -php: - - 5.5 - - 5.6 - - 7.1 - - 7.2 - - 7.3 - - 7.4 - -before_script: - - curl -s http://getcomposer.org/installer | php - - php composer.phar install --dev - -script: phpunit \ No newline at end of file diff --git a/composer.json b/composer.json index 551bcaa..568552b 100644 --- a/composer.json +++ b/composer.json @@ -25,9 +25,26 @@ }, "require-dev": { - "phpunit/phpunit": "7.*" - }, + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpmd/phpmd": "^2.9", + "phpunit/phpunit": "^9.3", + "sebastian/phpcpd": ">=4.1", + "friendsofphp/php-cs-fixer": ">=2.17", + "squizlabs/php_codesniffer": "^3.5" + }, + "scripts": { + "check-code-style": [ + "vendor/bin/phpcs --standard=PSR2 ./src/" + ], + "run-tests": [ + "vendor/bin/phpunit -c phpunit.xml", + "vendor/bin/phpunit --coverage-clover=coverage.xml" + ], + "validate-files": [ + "vendor/bin/parallel-lint --exclude vendor ." + ] + }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/phpunit.xml b/phpunit.xml index 1e71a58..0ea8ac9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,22 +1,13 @@ - - - - - ./tests/ - - - - - - ./src - - - \ No newline at end of file + + + + + ./src + + + + + ./tests/ + + + diff --git a/src/AncestorsRelation.php b/src/AncestorsRelation.php index b59fba2..38736bd 100644 --- a/src/AncestorsRelation.php +++ b/src/AncestorsRelation.php @@ -13,7 +13,9 @@ class AncestorsRelation extends BaseRelation */ public function addConstraints() { - if ( ! static::$constraints) return; + if (! static::$constraints) { + return; + } $this->query->whereAncestorOf($this->parent) ->applyNestedSetScope(); diff --git a/src/BaseRelation.php b/src/BaseRelation.php index 3b3b592..21fa1f6 100644 --- a/src/BaseRelation.php +++ b/src/BaseRelation.php @@ -36,7 +36,7 @@ abstract class BaseRelation extends Relation */ public function __construct(QueryBuilder $builder, Model $model) { - if ( ! NestedSet::isNode($model)) { + if (! NestedSet::isNode($model)) { throw new InvalidArgumentException('Model must be node.'); } @@ -76,8 +76,10 @@ abstract protected function relationExistenceCondition($hash, $table, $lft, $rgt * * @return mixed */ - public function getRelationExistenceQuery(EloquentBuilder $query, EloquentBuilder $parent, - $columns = [ '*' ] + public function getRelationExistenceQuery( + EloquentBuilder $query, + EloquentBuilder $parent, + $columns = [ '*' ] ) { $query = $this->getParent()->replicate()->newScopedQuery()->select($columns); @@ -93,7 +95,8 @@ public function getRelationExistenceQuery(EloquentBuilder $query, EloquentBuilde $grammar->wrapTable($hash), $grammar->wrapTable($table), $grammar->wrap($this->parent->getLftName()), - $grammar->wrap($this->parent->getRgtName())); + $grammar->wrap($this->parent->getRgtName()) + ); return $query->whereRaw($condition); } @@ -119,7 +122,8 @@ public function initRelation(array $models, $relation) * @return mixed */ public function getRelationQuery( - EloquentBuilder $query, EloquentBuilder $parent, + EloquentBuilder $query, + EloquentBuilder $parent, $columns = [ '*' ] ) { return $this->getRelationExistenceQuery($query, $parent, $columns); diff --git a/src/Collection.php b/src/Collection.php index 2dd26df..0664add 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -16,13 +16,15 @@ class Collection extends BaseCollection */ public function linkNodes() { - if ($this->isEmpty()) return $this; + if ($this->isEmpty()) { + return $this; + } $groupedNodes = $this->groupBy($this->first()->getParentIdName()); /** @var NodeTrait|Model $node */ foreach ($this->items as $node) { - if ( ! $node->getParentId()) { + if (! $node->getParentId()) { $node->setRelation('parent', null); } @@ -53,7 +55,7 @@ public function linkNodes() public function toTree($root = false) { if ($this->isEmpty()) { - return new static; + return new static(); } $this->linkNodes(); @@ -112,9 +114,11 @@ protected function getRootNodeId($root = false) */ public function toFlatTree($root = false) { - $result = new static; + $result = new static(); - if ($this->isEmpty()) return $result; + if ($this->isEmpty()) { + return $result; + } $groupedNodes = $this->groupBy($this->first()->getParentIdName()); @@ -139,5 +143,4 @@ protected function flattenTree(self $groupedNodes, $parentId) return $this; } - -} \ No newline at end of file +} diff --git a/src/DescendantsRelation.php b/src/DescendantsRelation.php index 4c6457d..fd0bbb1 100644 --- a/src/DescendantsRelation.php +++ b/src/DescendantsRelation.php @@ -7,7 +7,6 @@ class DescendantsRelation extends BaseRelation { - /** * Set the base constraints on the relation query. * @@ -15,7 +14,9 @@ class DescendantsRelation extends BaseRelation */ public function addConstraints() { - if ( ! static::$constraints) return; + if (! static::$constraints) { + return; + } $this->query->whereDescendantOf($this->parent) ->applyNestedSetScope(); @@ -53,4 +54,4 @@ protected function relationExistenceCondition($hash, $table, $lft, $rgt) { return "{$hash}.{$lft} between {$table}.{$lft} + 1 and {$table}.{$rgt}"; } -} \ No newline at end of file +} diff --git a/src/NestedSet.php b/src/NestedSet.php index 7045471..df8bc1c 100644 --- a/src/NestedSet.php +++ b/src/NestedSet.php @@ -9,27 +9,27 @@ class NestedSet /** * The name of default lft column. */ - const LFT = '_lft'; + public const LFT = '_lft'; /** * The name of default rgt column. */ - const RGT = '_rgt'; + public const RGT = '_rgt'; /** * The name of default parent id column. */ - const PARENT_ID = 'parent_id'; + public const PARENT_ID = 'parent_id'; /** * Insert direction. */ - const BEFORE = 1; + public const BEFORE = 1; /** * Insert direction. */ - const AFTER = 2; + public const AFTER = 2; /** * Add default nested set columns to the table. Also create an index. @@ -79,5 +79,4 @@ public static function isNode($node) { return $node instanceof Node; } - -} \ No newline at end of file +} diff --git a/src/NestedSetServiceProvider.php b/src/NestedSetServiceProvider.php index b4516f7..16466b4 100644 --- a/src/NestedSetServiceProvider.php +++ b/src/NestedSetServiceProvider.php @@ -17,4 +17,4 @@ public function register() NestedSet::dropColumns($this); }); } -} \ No newline at end of file +} diff --git a/src/Node.php b/src/Node.php index dfcb12f..9539447 100644 --- a/src/Node.php +++ b/src/Node.php @@ -20,344 +20,344 @@ */ interface Node { - /** - * Relation to the parent. - * - * @return BelongsTo - */ - public function parent(); - - /** - * Relation to children. - * - * @return HasMany - */ - public function children(); - - /** - * Get query for descendants of the node. - * - * @return DescendantsRelation - */ - public function descendants(); - - /** - * Get query for siblings of the node. - * - * @return QueryBuilder - */ - public function siblings(); - - /** - * Get the node siblings and the node itself. - * - * @return QueryBuilder - */ - public function siblingsAndSelf(); - - /** - * Get query for the node siblings and the node itself. - * - * @param array $columns - * - * @return EloquentCollection - */ - public function getSiblingsAndSelf(array $columns = ['*']); - - /** - * Get query for siblings after the node. - * - * @return QueryBuilder - */ - public function nextSiblings(); - - /** - * Get query for siblings before the node. - * - * @return QueryBuilder - */ - public function prevSiblings(); - - /** - * Get query for nodes after current node. - * - * @return QueryBuilder - */ - public function nextNodes(); - - /** - * Get query for nodes before current node in reversed order. - * - * @return QueryBuilder - */ - public function prevNodes(); - - /** - * Get query ancestors of the node. - * - * @return AncestorsRelation - */ - public function ancestors(); - - /** - * Make this node a root node. - * - * @return $this - */ - public function makeRoot(); - - /** - * Save node as root. - * - * @return bool - */ - public function saveAsRoot(); - - /** - * @param $lft - * @param $rgt - * @param $parentId - * - * @return $this - */ - public function rawNode($lft, $rgt, $parentId); - - /** - * Move node up given amount of positions. - * - * @param int $amount - * - * @return bool - */ - public function up($amount = 1); - - /** - * Move node down given amount of positions. - * - * @param int $amount - * - * @return bool - */ - public function down($amount = 1); - - /** - * @since 2.0 - */ - public function newEloquentBuilder($query); - - /** - * Get a new base query that includes deleted nodes. - * - * @since 1.1 - * - * @return QueryBuilder - */ - public function newNestedSetQuery($table = null); - - /** - * @param ?string $table - * - * @return QueryBuilder - */ - public function newScopedQuery($table = null); - - /** - * @param mixed $query - * @param ?string $table - * - * @return mixed - */ - public function applyNestedSetScope($query, $table = null); - - /** - * @param array $attributes - * - * @return self - */ - public static function scoped(array $attributes); - - public function newCollection(array $models = []); - - /** - * Get node height (rgt - lft + 1). - * - * @return int - */ - public function getNodeHeight(); - - /** - * Get number of descendant nodes. - * - * @return int - */ - public function getDescendantCount(); - - /** - * Set the value of model's parent id key. - * - * Behind the scenes node is appended to found parent node. - * - * @param int $value - * - * @throws \Exception If parent node doesn't exists - */ - public function setParentIdAttribute($value); - - /** - * Get whether node is root. - * - * @return bool - */ - public function isRoot(); - - /** - * @return bool - */ - public function isLeaf(); - - /** - * Get the lft key name. - * - * @return string - */ - public function getLftName(); - - /** - * Get the rgt key name. - * - * @return string - */ - public function getRgtName(); - - /** - * Get the parent id key name. - * - * @return string - */ - public function getParentIdName(); - - /** - * Get the value of the model's lft key. - * - * @return int - */ - public function getLft(); - - /** - * Get the value of the model's rgt key. - * - * @return int - */ - public function getRgt(); - - /** - * Get the value of the model's parent id key. - * - * @return int - */ - public function getParentId(); - - /** - * Returns node that is next to current node without constraining to siblings. - * - * This can be either a next sibling or a next sibling of the parent node. - * - * @param array $columns - * - * @return self - */ - public function getNextNode(array $columns = ['*']); - - /** - * Returns node that is before current node without constraining to siblings. - * - * This can be either a prev sibling or parent node. - * - * @param array $columns - * - * @return self - */ - public function getPrevNode(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection - */ - public function getAncestors(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection|self[] - */ - public function getDescendants(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection|self[] - */ - public function getSiblings(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection - */ - public function getNextSiblings(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection - */ - public function getPrevSiblings(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Node - */ - public function getNextSibling(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Node - */ - public function getPrevSibling(array $columns = ['*']); - - /** - * @return array - */ - public function getBounds(); - - /** - * @param $value - * - * @return $this - */ - public function setLft($value); - - /** - * @param $value - * - * @return $this - */ - public function setRgt($value); - - /** - * @param $value - * - * @return $this - */ - public function setParentId($value); - - /** - * @param array|null $except - * - * @return $this - */ - public function replicate(array $except = null); + /** + * Relation to the parent. + * + * @return BelongsTo + */ + public function parent(); + + /** + * Relation to children. + * + * @return HasMany + */ + public function children(); + + /** + * Get query for descendants of the node. + * + * @return DescendantsRelation + */ + public function descendants(); + + /** + * Get query for siblings of the node. + * + * @return QueryBuilder + */ + public function siblings(); + + /** + * Get the node siblings and the node itself. + * + * @return QueryBuilder + */ + public function siblingsAndSelf(); + + /** + * Get query for the node siblings and the node itself. + * + * @param array $columns + * + * @return EloquentCollection + */ + public function getSiblingsAndSelf(array $columns = ['*']); + + /** + * Get query for siblings after the node. + * + * @return QueryBuilder + */ + public function nextSiblings(); + + /** + * Get query for siblings before the node. + * + * @return QueryBuilder + */ + public function prevSiblings(); + + /** + * Get query for nodes after current node. + * + * @return QueryBuilder + */ + public function nextNodes(); + + /** + * Get query for nodes before current node in reversed order. + * + * @return QueryBuilder + */ + public function prevNodes(); + + /** + * Get query ancestors of the node. + * + * @return AncestorsRelation + */ + public function ancestors(); + + /** + * Make this node a root node. + * + * @return $this + */ + public function makeRoot(); + + /** + * Save node as root. + * + * @return bool + */ + public function saveAsRoot(); + + /** + * @param $lft + * @param $rgt + * @param $parentId + * + * @return $this + */ + public function rawNode($lft, $rgt, $parentId); + + /** + * Move node up given amount of positions. + * + * @param int $amount + * + * @return bool + */ + public function up($amount = 1); + + /** + * Move node down given amount of positions. + * + * @param int $amount + * + * @return bool + */ + public function down($amount = 1); + + /** + * @since 2.0 + */ + public function newEloquentBuilder($query); + + /** + * Get a new base query that includes deleted nodes. + * + * @since 1.1 + * + * @return QueryBuilder + */ + public function newNestedSetQuery($table = null); + + /** + * @param ?string $table + * + * @return QueryBuilder + */ + public function newScopedQuery($table = null); + + /** + * @param mixed $query + * @param ?string $table + * + * @return mixed + */ + public function applyNestedSetScope($query, $table = null); + + /** + * @param array $attributes + * + * @return self + */ + public static function scoped(array $attributes); + + public function newCollection(array $models = []); + + /** + * Get node height (rgt - lft + 1). + * + * @return int + */ + public function getNodeHeight(); + + /** + * Get number of descendant nodes. + * + * @return int + */ + public function getDescendantCount(); + + /** + * Set the value of model's parent id key. + * + * Behind the scenes node is appended to found parent node. + * + * @param int $value + * + * @throws \Exception If parent node doesn't exists + */ + public function setParentIdAttribute($value); + + /** + * Get whether node is root. + * + * @return bool + */ + public function isRoot(); + + /** + * @return bool + */ + public function isLeaf(); + + /** + * Get the lft key name. + * + * @return string + */ + public function getLftName(); + + /** + * Get the rgt key name. + * + * @return string + */ + public function getRgtName(); + + /** + * Get the parent id key name. + * + * @return string + */ + public function getParentIdName(); + + /** + * Get the value of the model's lft key. + * + * @return int + */ + public function getLft(); + + /** + * Get the value of the model's rgt key. + * + * @return int + */ + public function getRgt(); + + /** + * Get the value of the model's parent id key. + * + * @return int + */ + public function getParentId(); + + /** + * Returns node that is next to current node without constraining to siblings. + * + * This can be either a next sibling or a next sibling of the parent node. + * + * @param array $columns + * + * @return self + */ + public function getNextNode(array $columns = ['*']); + + /** + * Returns node that is before current node without constraining to siblings. + * + * This can be either a prev sibling or parent node. + * + * @param array $columns + * + * @return self + */ + public function getPrevNode(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection + */ + public function getAncestors(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection|self[] + */ + public function getDescendants(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection|self[] + */ + public function getSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection + */ + public function getNextSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection + */ + public function getPrevSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Node + */ + public function getNextSibling(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Node + */ + public function getPrevSibling(array $columns = ['*']); + + /** + * @return array + */ + public function getBounds(); + + /** + * @param $value + * + * @return $this + */ + public function setLft($value); + + /** + * @param $value + * + * @return $this + */ + public function setRgt($value); + + /** + * @param $value + * + * @return $this + */ + public function setParentId($value); + + /** + * @param array|null $except + * + * @return $this + */ + public function replicate(array $except = null); } diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 0b985ab..e7f0366 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -88,11 +88,13 @@ protected function callPendingAction() { $this->moved = false; - if ( ! $this->pending && ! $this->exists) { + if (! $this->pending && ! $this->exists) { $this->makeRoot(); } - if ( ! $this->pending) return; + if (! $this->pending) { + return; + } $method = 'action'.ucfirst(array_shift($this->pending)); $parameters = $this->pending; @@ -110,7 +112,7 @@ public static function usesSoftDelete() static $softDelete; if (is_null($softDelete)) { - $instance = new static; + $instance = new static(); return $softDelete = method_exists($instance, 'bootSoftDeletes'); } @@ -132,7 +134,7 @@ protected function actionRaw() protected function actionRoot() { // Simplest case that do not affect other nodes. - if ( ! $this->exists) { + if (! $this->exists) { $cut = $this->getLowerBound() + 1; $this->setLft($cut); @@ -168,7 +170,7 @@ protected function actionAppendOrPrepend(self $parent, $prepend = false) $cut = $prepend ? $parent->getLft() + 1 : $parent->getRgt(); - if ( ! $this->insertAt($cut)) { + if (! $this->insertAt($cut)) { return false; } @@ -212,7 +214,9 @@ protected function actionBeforeOrAfter(self $node, $after = false) */ public function refreshNode() { - if ( ! $this->exists || static::$actionsPerformed === 0) return; + if (! $this->exists || static::$actionsPerformed === 0) { + return; + } $attributes = $this->newNestedSetQuery()->getNodeData($this->getKey()); @@ -468,7 +472,7 @@ public function beforeOrAfterNode(self $node, $after = false) ->assertNotDescendant($node) ->assertSameScope($node); - if ( ! $this->isSiblingOf($node)) { + if (! $this->isSiblingOf($node)) { $this->setParent($node->getRelationValue('parent')); } @@ -498,7 +502,9 @@ public function insertAfterNode(self $node) */ public function insertBeforeNode(self $node) { - if ( ! $this->beforeNode($node)->save()) return false; + if (! $this->beforeNode($node)->save()) { + return false; + } // We'll update the target node since it will be moved $node->refreshNode(); @@ -534,7 +540,9 @@ public function up($amount = 1) ->skip($amount - 1) ->first(); - if ( ! $sibling) return false; + if (! $sibling) { + return false; + } return $this->insertBeforeNode($sibling); } @@ -553,7 +561,9 @@ public function down($amount = 1) ->skip($amount - 1) ->first(); - if ( ! $sibling) return false; + if (! $sibling) { + return false; + } return $this->insertAfterNode($sibling); } @@ -590,7 +600,9 @@ protected function moveNode($position) $updated = $this->newNestedSetQuery() ->moveNode($this->getKey(), $position) > 0; - if ($updated) $this->refreshNode(); + if ($updated) { + $this->refreshNode(); + } return $updated; } @@ -698,17 +710,20 @@ public function newScopedQuery($table = null) */ public function applyNestedSetScope($query, $table = null) { - if ( ! $scoped = $this->getScopeAttributes()) { + if (! $scoped = $this->getScopeAttributes()) { return $query; } - if ( ! $table) { + if (! $table) { $table = $this->getTable(); } foreach ($scoped as $attribute) { - $query->where($table.'.'.$attribute, '=', - $this->getAttributeValue($attribute)); + $query->where( + $table.'.'.$attribute, + '=', + $this->getAttributeValue($attribute) + ); } return $query; @@ -729,7 +744,7 @@ protected function getScopeAttributes() */ public static function scoped(array $attributes) { - $instance = new static; + $instance = new static(); $instance->setRawAttributes($attributes); @@ -764,7 +779,7 @@ public static function create(array $attributes = [], self $parent = null) $instance->save(); // Now create children - $relation = new EloquentCollection; + $relation = new EloquentCollection(); foreach ((array)$children as $child) { $relation->add($child = static::create($child, $instance)); @@ -784,7 +799,9 @@ public static function create(array $attributes = [], self $parent = null) */ public function getNodeHeight() { - if ( ! $this->exists) return 2; + if (! $this->exists) { + return 2; + } return $this->getRgt() - $this->getLft() + 1; } @@ -810,7 +827,9 @@ public function getDescendantCount() */ public function setParentIdAttribute($value) { - if ($this->getParentId() == $value) return; + if ($this->getParentId() == $value) { + return; + } if ($value) { $this->appendToNode($this->newScopedQuery()->findOrFail($value)); @@ -1178,7 +1197,7 @@ protected function assertNotDescendant(self $node) */ protected function assertNodeExists(self $node) { - if ( ! $node->getLft() || ! $node->getRgt()) { + if (! $node->getLft() || ! $node->getRgt()) { throw new LogicException('Node must exists.'); } @@ -1190,7 +1209,7 @@ protected function assertNodeExists(self $node) */ protected function assertSameScope(self $node) { - if ( ! $scoped = $this->getScopeAttributes()) { + if (! $scoped = $this->getScopeAttributes()) { return; } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 61aba6a..9b794e1 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -38,8 +38,8 @@ public function getNodeData($id, $required = false) $data = $query->first([ $this->model->getLftName(), $this->model->getRgtName() ]); - if ( ! $data && $required) { - throw new ModelNotFoundException; + if (! $data && $required) { + throw new ModelNotFoundException(); } return (array)$data; @@ -114,7 +114,7 @@ public function whereAncestorOf($id, $andSelf = false, $boolean = 'and') $inner->whereRaw("{$value} between {$wrappedTable}.{$lft} and {$wrappedTable}.{$rgt}"); - if ( ! $andSelf) { + if (! $andSelf) { $inner->where($keyName, '<>', $id); } }, $boolean); @@ -183,7 +183,12 @@ public function ancestorsAndSelf($id, array $columns = [ '*' ]) */ public function whereNodeBetween($values, $boolean = 'and', $not = false) { - $this->query->whereBetween($this->model->getTable() . '.' . $this->model->getLftName(), $values, $boolean, $not); + $this->query->whereBetween( + $this->model->getTable() . '.' . $this->model->getLftName(), + $values, + $boolean, + $not + ); return $this; } @@ -214,8 +219,11 @@ public function orWhereNodeBetween($values) * * @return $this */ - public function whereDescendantOf($id, $boolean = 'and', $not = false, - $andSelf = false + public function whereDescendantOf( + $id, + $boolean = 'and', + $not = false, + $andSelf = false ) { if (NestedSet::isNode($id)) { $data = $id->getBounds(); @@ -225,7 +233,7 @@ public function whereDescendantOf($id, $boolean = 'and', $not = false, } // Don't include the node - if ( ! $andSelf) { + if (! $andSelf) { ++$data[0]; } @@ -289,9 +297,7 @@ public function descendantsOf($id, array $columns = [ '*' ], $andSelf = false) { try { return $this->whereDescendantOf($id, 'and', false, $andSelf)->get($columns); - } - - catch (ModelNotFoundException $e) { + } catch (ModelNotFoundException $e) { return $this->model->newCollection(); } } @@ -333,7 +339,7 @@ protected function whereIsBeforeOrAfter($id, $operator, $boolean) $value = '('.$valueQuery->toSql().')'; } - list($lft,) = $this->wrappedColumns(); + list($lft, ) = $this->wrappedColumns(); $this->query->whereRaw("{$lft} {$operator} {$value}", [ ], $boolean); @@ -399,7 +405,9 @@ public function leaves(array $columns = [ '*']) */ public function withDepth($as = 'depth') { - if ($this->query->columns === null) $this->query->columns = [ '*' ]; + if ($this->query->columns === null) { + $this->query->columns = [ '*' ]; + } $table = $this->wrappedTable(); @@ -640,7 +648,9 @@ protected function columnPatch($col, array $params) extract($params); /** @var int $height */ - if ($height > 0) $height = '+'.$height; + if ($height > 0) { + $height = '+'.$height; + } if (isset($cut)) { return new Expression("case when {$col} >= {$cut} then {$col}{$height} else {$col} end"); @@ -651,9 +661,12 @@ protected function columnPatch($col, array $params) /** @var int $rgt */ /** @var int $from */ /** @var int $to */ - if ($distance > 0) $distance = '+'.$distance; + if ($distance > 0) { + $distance = '+'.$distance; + } - return new Expression("case ". + return new Expression( + "case ". "when {$col} between {$lft} and {$rgt} then {$col}{$distance} ". // Move the node "when {$col} between {$from} and {$to} then {$col}{$height} ". // Move other nodes "else {$col} end" @@ -896,7 +909,7 @@ protected function fixNodes(array &$dictionary, $parent = null) $cut = self::reorderNodes($dictionary, $updated, $parentId, $cut); // Save nodes that have invalid parent as roots - while ( ! empty($dictionary)) { + while (! empty($dictionary)) { $dictionary[null] = reset($dictionary); unset($dictionary[key($dictionary)]); @@ -927,9 +940,12 @@ protected function fixNodes(array &$dictionary, $parent = null) * @internal param int $fixed */ protected static function reorderNodes( - array &$dictionary, array &$updated, $parentId = null, $cut = 1 + array &$dictionary, + array &$updated, + $parentId = null, + $cut = 1 ) { - if ( ! isset($dictionary[$parentId])) { + if (! isset($dictionary[$parentId])) { return $cut; } @@ -982,7 +998,7 @@ public function rebuildTree(array $data, $delete = false, $root = null) $this->buildRebuildDictionary($dictionary, $data, $existing, $parentId); /** @var Model|NodeTrait $model */ - if ( ! empty($existing)) { + if (! empty($existing)) { if ($delete && ! $this->model->usesSoftDelete()) { $this->model ->newScopedQuery() @@ -1024,24 +1040,25 @@ public function rebuildSubtree($root, array $data, $delete = false) * @param array $existing * @param mixed $parentId */ - protected function buildRebuildDictionary(array &$dictionary, - array $data, - array &$existing, - $parentId = null + protected function buildRebuildDictionary( + array &$dictionary, + array $data, + array &$existing, + $parentId = null ) { $keyName = $this->model->getKeyName(); foreach ($data as $itemData) { /** @var NodeTrait|Model $model */ - if ( ! isset($itemData[$keyName])) { + if (! isset($itemData[$keyName])) { $model = $this->model->newInstance($this->model->getAttributes()); // Set some values that will be fixed later $model->rawNode(0, 0, $parentId); } else { - if ( ! isset($existing[$key = $itemData[$keyName]])) { - throw new ModelNotFoundException; + if (! isset($existing[$key = $itemData[$keyName]])) { + throw new ModelNotFoundException(); } $model = $existing[$key]; @@ -1056,12 +1073,16 @@ protected function buildRebuildDictionary(array &$dictionary, $dictionary[$parentId][] = $model; - if ( ! isset($itemData['children'])) continue; + if (! isset($itemData['children'])) { + continue; + } - $this->buildRebuildDictionary($dictionary, - $itemData['children'], - $existing, - $model->getKey()); + $this->buildRebuildDictionary( + $dictionary, + $itemData['children'], + $existing, + $model->getKey() + ); } } diff --git a/tests/NodeTest.php b/tests/NodeTest.php index 7c2fbae..d565ca2 100644 --- a/tests/NodeTest.php +++ b/tests/NodeTest.php @@ -5,7 +5,7 @@ class NodeTest extends PHPUnit\Framework\TestCase { - public static function setUpBeforeClass() + public static function setUpBeforeClass() : void { $schema = Capsule::schema(); @@ -23,7 +23,7 @@ public static function setUpBeforeClass() Capsule::enableQueryLog(); } - public function setUp() + public function setUp() : void { $data = include __DIR__.'/data/categories.php'; @@ -36,7 +36,7 @@ public function setUp() date_default_timezone_set('America/Denver'); } - public function tearDown() + public function tearDown() : void { Capsule::table('categories')->truncate(); } @@ -84,7 +84,9 @@ public function assertTreeNotBroken($table = 'categories') public function dumpTree($items = null) { - if ( ! $items) $items = Category::withTrashed()->defaultOrder()->get(); + if (! $items) { + $items = Category::withTrashed()->defaultOrder()->get(); + } foreach ($items as $item) { echo PHP_EOL.($item->trashed() ? '-' : '+').' '.$item->name." ".$item->getKey().' '.$item->getLft()." ".$item->getRgt().' '.$item->getParentId(); @@ -615,14 +617,15 @@ public function testCreatesViaRelationship() public function testCreatesTree() { $node = Category::create( - [ + [ 'name' => 'test', 'children' => [ [ 'name' => 'test2' ], [ 'name' => 'test3' ], ], - ]); + ] + ); $this->assertTreeNotBroken(); @@ -669,8 +672,7 @@ public function testMultipleDeletionsDoNotBrakeTree() { $category = $this->findCategory('mobile'); - foreach ($category->children()->take(2)->get() as $child) - { + foreach ($category->children()->take(2)->get() as $child) { $child->forceDelete(); } @@ -925,7 +927,9 @@ public function testEagerLoadAncestors() foreach ($categories as $category) { $output["{$category->name} ({$category->id})}"] = $category->ancestors->count() - ? implode(' > ', $category->ancestors->map(function ($cat) { return "{$cat->name} ({$cat->id})"; })->toArray()) + ? implode(' > ', $category->ancestors->map(function ($cat) { + return "{$cat->name} ({$cat->id})"; + })->toArray()) : ''; } @@ -957,7 +961,9 @@ public function testLazyLoadAncestors() foreach ($categories as $category) { $output["{$category->name} ({$category->id})}"] = $category->ancestors->count() - ? implode(' > ', $category->ancestors->map(function ($cat) { return "{$cat->name} ({$cat->id})"; })->toArray()) + ? implode(' > ', $category->ancestors->map(function ($cat) { + return "{$cat->name} ({$cat->id})"; + })->toArray()) : ''; } @@ -998,10 +1004,9 @@ public function testReplication() $this->assertEquals(1, $category->getParentId()); } - } function all($items) { return is_array($items) ? $items : $items->all(); -} \ No newline at end of file +} diff --git a/tests/ScopedNodeTest.php b/tests/ScopedNodeTest.php index f1c6921..7bcfdd2 100644 --- a/tests/ScopedNodeTest.php +++ b/tests/ScopedNodeTest.php @@ -5,7 +5,7 @@ class ScopedNodeTest extends PHPUnit\Framework\TestCase { - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { $schema = Capsule::schema(); @@ -23,7 +23,7 @@ public static function setUpBeforeClass() Capsule::enableQueryLog(); } - public function setUp() + public function setUp(): void { $data = include __DIR__.'/data/menu_items.php'; @@ -36,7 +36,7 @@ public function setUp() date_default_timezone_set('America/Denver'); } - public function tearDown() + public function tearDown(): void { Capsule::table('menu_items')->truncate(); } @@ -222,4 +222,4 @@ public function testInsertingBeforeAnotherScopeFails() $a->insertAfterNode($b); } -} \ No newline at end of file +} From 63cd550b1bfe0db2b14abeb6a41f8f89638d142d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Viguier?= Date: Mon, 1 Nov 2021 13:13:15 +0100 Subject: [PATCH 05/19] Update repo (#3) * Revert "migrate to github actions (#2)" This reverts commit f9ec3d0030ad756f29cf31c6582419ba9e8e27db. * fix repo * fix * cleaning deps * exclude 8.1 low * bye travis --- .github/workflows/CI.yaml | 17 +- .gitignore | 2 +- composer.json | 11 +- phpunit.xml | 26 +- src/AncestorsRelation.php | 4 +- src/BaseRelation.php | 14 +- src/Collection.php | 17 +- src/DescendantsRelation.php | 7 +- src/NestedSet.php | 13 +- src/NestedSetServiceProvider.php | 2 +- src/Node.php | 680 +++++++++++++++---------------- src/NodeTrait.php | 61 +-- src/QueryBuilder.php | 83 ++-- tests/NodeTest.php | 51 +-- tests/ScopedNodeTest.php | 20 +- 15 files changed, 464 insertions(+), 544 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index a1008fb..5d48eb7 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -20,10 +20,10 @@ jobs: operating-system: [ubuntu-20.04] php-versions: ['7.4', '8.0', '8.1'] dependencies: ['no', 'low', 'beta'] - include: + exclude: - operating-system: ubuntu-20.04 - php-versions: '8.0' - continue-on-error: true + php-versions: '8.1' + dependencies: 'low' name: PHP ${{ matrix.php-versions }} - ${{ matrix.dependencies }} @@ -58,6 +58,10 @@ jobs: path: ~/.composer/cache/files key: dependencies-composer-${{ hashFiles('composer.json') }} + - name: Fix beta + if: ${{ matrix.dependencies == 'beta' }} + run: perl -pi -e 's/^}$/,"minimum-stability":"beta"}/' composer.json + - name: Setup PHP Action uses: shivammathur/setup-php@2.8.0 with: @@ -91,8 +95,5 @@ jobs: - name: Validate files run: composer validate-files - - name: Check Style - run: composer check-code-style - - # - name: Run tests - # run: composer run-tests + - name: Run tests + run: composer run-tests \ No newline at end of file diff --git a/.gitignore b/.gitignore index 24a2013..429c256 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ composer.phar composer.lock .DS_Store .php-cs-fixer.cache -.phpunit.result.cache +.phpunit.result.cache \ No newline at end of file diff --git a/composer.json b/composer.json index 568552b..98604b9 100644 --- a/composer.json +++ b/composer.json @@ -26,17 +26,10 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpmd/phpmd": "^2.9", - "phpunit/phpunit": "^9.3", - "sebastian/phpcpd": ">=4.1", - "friendsofphp/php-cs-fixer": ">=2.17", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "^9.3" }, "scripts": { - "check-code-style": [ - "vendor/bin/phpcs --standard=PSR2 ./src/" - ], "run-tests": [ "vendor/bin/phpunit -c phpunit.xml", "vendor/bin/phpunit --coverage-clover=coverage.xml" @@ -59,4 +52,4 @@ ] } } -} +} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index 0ea8ac9..ab23f7f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,13 @@ - - - - - ./src - - - - - ./tests/ - - - + + + + + ./src + + + + + ./tests/ + + + \ No newline at end of file diff --git a/src/AncestorsRelation.php b/src/AncestorsRelation.php index 38736bd..b59fba2 100644 --- a/src/AncestorsRelation.php +++ b/src/AncestorsRelation.php @@ -13,9 +13,7 @@ class AncestorsRelation extends BaseRelation */ public function addConstraints() { - if (! static::$constraints) { - return; - } + if ( ! static::$constraints) return; $this->query->whereAncestorOf($this->parent) ->applyNestedSetScope(); diff --git a/src/BaseRelation.php b/src/BaseRelation.php index 21fa1f6..3b3b592 100644 --- a/src/BaseRelation.php +++ b/src/BaseRelation.php @@ -36,7 +36,7 @@ abstract class BaseRelation extends Relation */ public function __construct(QueryBuilder $builder, Model $model) { - if (! NestedSet::isNode($model)) { + if ( ! NestedSet::isNode($model)) { throw new InvalidArgumentException('Model must be node.'); } @@ -76,10 +76,8 @@ abstract protected function relationExistenceCondition($hash, $table, $lft, $rgt * * @return mixed */ - public function getRelationExistenceQuery( - EloquentBuilder $query, - EloquentBuilder $parent, - $columns = [ '*' ] + public function getRelationExistenceQuery(EloquentBuilder $query, EloquentBuilder $parent, + $columns = [ '*' ] ) { $query = $this->getParent()->replicate()->newScopedQuery()->select($columns); @@ -95,8 +93,7 @@ public function getRelationExistenceQuery( $grammar->wrapTable($hash), $grammar->wrapTable($table), $grammar->wrap($this->parent->getLftName()), - $grammar->wrap($this->parent->getRgtName()) - ); + $grammar->wrap($this->parent->getRgtName())); return $query->whereRaw($condition); } @@ -122,8 +119,7 @@ public function initRelation(array $models, $relation) * @return mixed */ public function getRelationQuery( - EloquentBuilder $query, - EloquentBuilder $parent, + EloquentBuilder $query, EloquentBuilder $parent, $columns = [ '*' ] ) { return $this->getRelationExistenceQuery($query, $parent, $columns); diff --git a/src/Collection.php b/src/Collection.php index 0664add..2dd26df 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -16,15 +16,13 @@ class Collection extends BaseCollection */ public function linkNodes() { - if ($this->isEmpty()) { - return $this; - } + if ($this->isEmpty()) return $this; $groupedNodes = $this->groupBy($this->first()->getParentIdName()); /** @var NodeTrait|Model $node */ foreach ($this->items as $node) { - if (! $node->getParentId()) { + if ( ! $node->getParentId()) { $node->setRelation('parent', null); } @@ -55,7 +53,7 @@ public function linkNodes() public function toTree($root = false) { if ($this->isEmpty()) { - return new static(); + return new static; } $this->linkNodes(); @@ -114,11 +112,9 @@ protected function getRootNodeId($root = false) */ public function toFlatTree($root = false) { - $result = new static(); + $result = new static; - if ($this->isEmpty()) { - return $result; - } + if ($this->isEmpty()) return $result; $groupedNodes = $this->groupBy($this->first()->getParentIdName()); @@ -143,4 +139,5 @@ protected function flattenTree(self $groupedNodes, $parentId) return $this; } -} + +} \ No newline at end of file diff --git a/src/DescendantsRelation.php b/src/DescendantsRelation.php index fd0bbb1..4c6457d 100644 --- a/src/DescendantsRelation.php +++ b/src/DescendantsRelation.php @@ -7,6 +7,7 @@ class DescendantsRelation extends BaseRelation { + /** * Set the base constraints on the relation query. * @@ -14,9 +15,7 @@ class DescendantsRelation extends BaseRelation */ public function addConstraints() { - if (! static::$constraints) { - return; - } + if ( ! static::$constraints) return; $this->query->whereDescendantOf($this->parent) ->applyNestedSetScope(); @@ -54,4 +53,4 @@ protected function relationExistenceCondition($hash, $table, $lft, $rgt) { return "{$hash}.{$lft} between {$table}.{$lft} + 1 and {$table}.{$rgt}"; } -} +} \ No newline at end of file diff --git a/src/NestedSet.php b/src/NestedSet.php index df8bc1c..7045471 100644 --- a/src/NestedSet.php +++ b/src/NestedSet.php @@ -9,27 +9,27 @@ class NestedSet /** * The name of default lft column. */ - public const LFT = '_lft'; + const LFT = '_lft'; /** * The name of default rgt column. */ - public const RGT = '_rgt'; + const RGT = '_rgt'; /** * The name of default parent id column. */ - public const PARENT_ID = 'parent_id'; + const PARENT_ID = 'parent_id'; /** * Insert direction. */ - public const BEFORE = 1; + const BEFORE = 1; /** * Insert direction. */ - public const AFTER = 2; + const AFTER = 2; /** * Add default nested set columns to the table. Also create an index. @@ -79,4 +79,5 @@ public static function isNode($node) { return $node instanceof Node; } -} + +} \ No newline at end of file diff --git a/src/NestedSetServiceProvider.php b/src/NestedSetServiceProvider.php index 16466b4..b4516f7 100644 --- a/src/NestedSetServiceProvider.php +++ b/src/NestedSetServiceProvider.php @@ -17,4 +17,4 @@ public function register() NestedSet::dropColumns($this); }); } -} +} \ No newline at end of file diff --git a/src/Node.php b/src/Node.php index 9539447..dfcb12f 100644 --- a/src/Node.php +++ b/src/Node.php @@ -20,344 +20,344 @@ */ interface Node { - /** - * Relation to the parent. - * - * @return BelongsTo - */ - public function parent(); - - /** - * Relation to children. - * - * @return HasMany - */ - public function children(); - - /** - * Get query for descendants of the node. - * - * @return DescendantsRelation - */ - public function descendants(); - - /** - * Get query for siblings of the node. - * - * @return QueryBuilder - */ - public function siblings(); - - /** - * Get the node siblings and the node itself. - * - * @return QueryBuilder - */ - public function siblingsAndSelf(); - - /** - * Get query for the node siblings and the node itself. - * - * @param array $columns - * - * @return EloquentCollection - */ - public function getSiblingsAndSelf(array $columns = ['*']); - - /** - * Get query for siblings after the node. - * - * @return QueryBuilder - */ - public function nextSiblings(); - - /** - * Get query for siblings before the node. - * - * @return QueryBuilder - */ - public function prevSiblings(); - - /** - * Get query for nodes after current node. - * - * @return QueryBuilder - */ - public function nextNodes(); - - /** - * Get query for nodes before current node in reversed order. - * - * @return QueryBuilder - */ - public function prevNodes(); - - /** - * Get query ancestors of the node. - * - * @return AncestorsRelation - */ - public function ancestors(); - - /** - * Make this node a root node. - * - * @return $this - */ - public function makeRoot(); - - /** - * Save node as root. - * - * @return bool - */ - public function saveAsRoot(); - - /** - * @param $lft - * @param $rgt - * @param $parentId - * - * @return $this - */ - public function rawNode($lft, $rgt, $parentId); - - /** - * Move node up given amount of positions. - * - * @param int $amount - * - * @return bool - */ - public function up($amount = 1); - - /** - * Move node down given amount of positions. - * - * @param int $amount - * - * @return bool - */ - public function down($amount = 1); - - /** - * @since 2.0 - */ - public function newEloquentBuilder($query); - - /** - * Get a new base query that includes deleted nodes. - * - * @since 1.1 - * - * @return QueryBuilder - */ - public function newNestedSetQuery($table = null); - - /** - * @param ?string $table - * - * @return QueryBuilder - */ - public function newScopedQuery($table = null); - - /** - * @param mixed $query - * @param ?string $table - * - * @return mixed - */ - public function applyNestedSetScope($query, $table = null); - - /** - * @param array $attributes - * - * @return self - */ - public static function scoped(array $attributes); - - public function newCollection(array $models = []); - - /** - * Get node height (rgt - lft + 1). - * - * @return int - */ - public function getNodeHeight(); - - /** - * Get number of descendant nodes. - * - * @return int - */ - public function getDescendantCount(); - - /** - * Set the value of model's parent id key. - * - * Behind the scenes node is appended to found parent node. - * - * @param int $value - * - * @throws \Exception If parent node doesn't exists - */ - public function setParentIdAttribute($value); - - /** - * Get whether node is root. - * - * @return bool - */ - public function isRoot(); - - /** - * @return bool - */ - public function isLeaf(); - - /** - * Get the lft key name. - * - * @return string - */ - public function getLftName(); - - /** - * Get the rgt key name. - * - * @return string - */ - public function getRgtName(); - - /** - * Get the parent id key name. - * - * @return string - */ - public function getParentIdName(); - - /** - * Get the value of the model's lft key. - * - * @return int - */ - public function getLft(); - - /** - * Get the value of the model's rgt key. - * - * @return int - */ - public function getRgt(); - - /** - * Get the value of the model's parent id key. - * - * @return int - */ - public function getParentId(); - - /** - * Returns node that is next to current node without constraining to siblings. - * - * This can be either a next sibling or a next sibling of the parent node. - * - * @param array $columns - * - * @return self - */ - public function getNextNode(array $columns = ['*']); - - /** - * Returns node that is before current node without constraining to siblings. - * - * This can be either a prev sibling or parent node. - * - * @param array $columns - * - * @return self - */ - public function getPrevNode(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection - */ - public function getAncestors(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection|self[] - */ - public function getDescendants(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection|self[] - */ - public function getSiblings(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection - */ - public function getNextSiblings(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Collection - */ - public function getPrevSiblings(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Node - */ - public function getNextSibling(array $columns = ['*']); - - /** - * @param array $columns - * - * @return Node - */ - public function getPrevSibling(array $columns = ['*']); - - /** - * @return array - */ - public function getBounds(); - - /** - * @param $value - * - * @return $this - */ - public function setLft($value); - - /** - * @param $value - * - * @return $this - */ - public function setRgt($value); - - /** - * @param $value - * - * @return $this - */ - public function setParentId($value); - - /** - * @param array|null $except - * - * @return $this - */ - public function replicate(array $except = null); + /** + * Relation to the parent. + * + * @return BelongsTo + */ + public function parent(); + + /** + * Relation to children. + * + * @return HasMany + */ + public function children(); + + /** + * Get query for descendants of the node. + * + * @return DescendantsRelation + */ + public function descendants(); + + /** + * Get query for siblings of the node. + * + * @return QueryBuilder + */ + public function siblings(); + + /** + * Get the node siblings and the node itself. + * + * @return QueryBuilder + */ + public function siblingsAndSelf(); + + /** + * Get query for the node siblings and the node itself. + * + * @param array $columns + * + * @return EloquentCollection + */ + public function getSiblingsAndSelf(array $columns = ['*']); + + /** + * Get query for siblings after the node. + * + * @return QueryBuilder + */ + public function nextSiblings(); + + /** + * Get query for siblings before the node. + * + * @return QueryBuilder + */ + public function prevSiblings(); + + /** + * Get query for nodes after current node. + * + * @return QueryBuilder + */ + public function nextNodes(); + + /** + * Get query for nodes before current node in reversed order. + * + * @return QueryBuilder + */ + public function prevNodes(); + + /** + * Get query ancestors of the node. + * + * @return AncestorsRelation + */ + public function ancestors(); + + /** + * Make this node a root node. + * + * @return $this + */ + public function makeRoot(); + + /** + * Save node as root. + * + * @return bool + */ + public function saveAsRoot(); + + /** + * @param $lft + * @param $rgt + * @param $parentId + * + * @return $this + */ + public function rawNode($lft, $rgt, $parentId); + + /** + * Move node up given amount of positions. + * + * @param int $amount + * + * @return bool + */ + public function up($amount = 1); + + /** + * Move node down given amount of positions. + * + * @param int $amount + * + * @return bool + */ + public function down($amount = 1); + + /** + * @since 2.0 + */ + public function newEloquentBuilder($query); + + /** + * Get a new base query that includes deleted nodes. + * + * @since 1.1 + * + * @return QueryBuilder + */ + public function newNestedSetQuery($table = null); + + /** + * @param ?string $table + * + * @return QueryBuilder + */ + public function newScopedQuery($table = null); + + /** + * @param mixed $query + * @param ?string $table + * + * @return mixed + */ + public function applyNestedSetScope($query, $table = null); + + /** + * @param array $attributes + * + * @return self + */ + public static function scoped(array $attributes); + + public function newCollection(array $models = []); + + /** + * Get node height (rgt - lft + 1). + * + * @return int + */ + public function getNodeHeight(); + + /** + * Get number of descendant nodes. + * + * @return int + */ + public function getDescendantCount(); + + /** + * Set the value of model's parent id key. + * + * Behind the scenes node is appended to found parent node. + * + * @param int $value + * + * @throws \Exception If parent node doesn't exists + */ + public function setParentIdAttribute($value); + + /** + * Get whether node is root. + * + * @return bool + */ + public function isRoot(); + + /** + * @return bool + */ + public function isLeaf(); + + /** + * Get the lft key name. + * + * @return string + */ + public function getLftName(); + + /** + * Get the rgt key name. + * + * @return string + */ + public function getRgtName(); + + /** + * Get the parent id key name. + * + * @return string + */ + public function getParentIdName(); + + /** + * Get the value of the model's lft key. + * + * @return int + */ + public function getLft(); + + /** + * Get the value of the model's rgt key. + * + * @return int + */ + public function getRgt(); + + /** + * Get the value of the model's parent id key. + * + * @return int + */ + public function getParentId(); + + /** + * Returns node that is next to current node without constraining to siblings. + * + * This can be either a next sibling or a next sibling of the parent node. + * + * @param array $columns + * + * @return self + */ + public function getNextNode(array $columns = ['*']); + + /** + * Returns node that is before current node without constraining to siblings. + * + * This can be either a prev sibling or parent node. + * + * @param array $columns + * + * @return self + */ + public function getPrevNode(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection + */ + public function getAncestors(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection|self[] + */ + public function getDescendants(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection|self[] + */ + public function getSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection + */ + public function getNextSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Collection + */ + public function getPrevSiblings(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Node + */ + public function getNextSibling(array $columns = ['*']); + + /** + * @param array $columns + * + * @return Node + */ + public function getPrevSibling(array $columns = ['*']); + + /** + * @return array + */ + public function getBounds(); + + /** + * @param $value + * + * @return $this + */ + public function setLft($value); + + /** + * @param $value + * + * @return $this + */ + public function setRgt($value); + + /** + * @param $value + * + * @return $this + */ + public function setParentId($value); + + /** + * @param array|null $except + * + * @return $this + */ + public function replicate(array $except = null); } diff --git a/src/NodeTrait.php b/src/NodeTrait.php index e7f0366..0b985ab 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -88,13 +88,11 @@ protected function callPendingAction() { $this->moved = false; - if (! $this->pending && ! $this->exists) { + if ( ! $this->pending && ! $this->exists) { $this->makeRoot(); } - if (! $this->pending) { - return; - } + if ( ! $this->pending) return; $method = 'action'.ucfirst(array_shift($this->pending)); $parameters = $this->pending; @@ -112,7 +110,7 @@ public static function usesSoftDelete() static $softDelete; if (is_null($softDelete)) { - $instance = new static(); + $instance = new static; return $softDelete = method_exists($instance, 'bootSoftDeletes'); } @@ -134,7 +132,7 @@ protected function actionRaw() protected function actionRoot() { // Simplest case that do not affect other nodes. - if (! $this->exists) { + if ( ! $this->exists) { $cut = $this->getLowerBound() + 1; $this->setLft($cut); @@ -170,7 +168,7 @@ protected function actionAppendOrPrepend(self $parent, $prepend = false) $cut = $prepend ? $parent->getLft() + 1 : $parent->getRgt(); - if (! $this->insertAt($cut)) { + if ( ! $this->insertAt($cut)) { return false; } @@ -214,9 +212,7 @@ protected function actionBeforeOrAfter(self $node, $after = false) */ public function refreshNode() { - if (! $this->exists || static::$actionsPerformed === 0) { - return; - } + if ( ! $this->exists || static::$actionsPerformed === 0) return; $attributes = $this->newNestedSetQuery()->getNodeData($this->getKey()); @@ -472,7 +468,7 @@ public function beforeOrAfterNode(self $node, $after = false) ->assertNotDescendant($node) ->assertSameScope($node); - if (! $this->isSiblingOf($node)) { + if ( ! $this->isSiblingOf($node)) { $this->setParent($node->getRelationValue('parent')); } @@ -502,9 +498,7 @@ public function insertAfterNode(self $node) */ public function insertBeforeNode(self $node) { - if (! $this->beforeNode($node)->save()) { - return false; - } + if ( ! $this->beforeNode($node)->save()) return false; // We'll update the target node since it will be moved $node->refreshNode(); @@ -540,9 +534,7 @@ public function up($amount = 1) ->skip($amount - 1) ->first(); - if (! $sibling) { - return false; - } + if ( ! $sibling) return false; return $this->insertBeforeNode($sibling); } @@ -561,9 +553,7 @@ public function down($amount = 1) ->skip($amount - 1) ->first(); - if (! $sibling) { - return false; - } + if ( ! $sibling) return false; return $this->insertAfterNode($sibling); } @@ -600,9 +590,7 @@ protected function moveNode($position) $updated = $this->newNestedSetQuery() ->moveNode($this->getKey(), $position) > 0; - if ($updated) { - $this->refreshNode(); - } + if ($updated) $this->refreshNode(); return $updated; } @@ -710,20 +698,17 @@ public function newScopedQuery($table = null) */ public function applyNestedSetScope($query, $table = null) { - if (! $scoped = $this->getScopeAttributes()) { + if ( ! $scoped = $this->getScopeAttributes()) { return $query; } - if (! $table) { + if ( ! $table) { $table = $this->getTable(); } foreach ($scoped as $attribute) { - $query->where( - $table.'.'.$attribute, - '=', - $this->getAttributeValue($attribute) - ); + $query->where($table.'.'.$attribute, '=', + $this->getAttributeValue($attribute)); } return $query; @@ -744,7 +729,7 @@ protected function getScopeAttributes() */ public static function scoped(array $attributes) { - $instance = new static(); + $instance = new static; $instance->setRawAttributes($attributes); @@ -779,7 +764,7 @@ public static function create(array $attributes = [], self $parent = null) $instance->save(); // Now create children - $relation = new EloquentCollection(); + $relation = new EloquentCollection; foreach ((array)$children as $child) { $relation->add($child = static::create($child, $instance)); @@ -799,9 +784,7 @@ public static function create(array $attributes = [], self $parent = null) */ public function getNodeHeight() { - if (! $this->exists) { - return 2; - } + if ( ! $this->exists) return 2; return $this->getRgt() - $this->getLft() + 1; } @@ -827,9 +810,7 @@ public function getDescendantCount() */ public function setParentIdAttribute($value) { - if ($this->getParentId() == $value) { - return; - } + if ($this->getParentId() == $value) return; if ($value) { $this->appendToNode($this->newScopedQuery()->findOrFail($value)); @@ -1197,7 +1178,7 @@ protected function assertNotDescendant(self $node) */ protected function assertNodeExists(self $node) { - if (! $node->getLft() || ! $node->getRgt()) { + if ( ! $node->getLft() || ! $node->getRgt()) { throw new LogicException('Node must exists.'); } @@ -1209,7 +1190,7 @@ protected function assertNodeExists(self $node) */ protected function assertSameScope(self $node) { - if (! $scoped = $this->getScopeAttributes()) { + if ( ! $scoped = $this->getScopeAttributes()) { return; } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 9b794e1..61aba6a 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -38,8 +38,8 @@ public function getNodeData($id, $required = false) $data = $query->first([ $this->model->getLftName(), $this->model->getRgtName() ]); - if (! $data && $required) { - throw new ModelNotFoundException(); + if ( ! $data && $required) { + throw new ModelNotFoundException; } return (array)$data; @@ -114,7 +114,7 @@ public function whereAncestorOf($id, $andSelf = false, $boolean = 'and') $inner->whereRaw("{$value} between {$wrappedTable}.{$lft} and {$wrappedTable}.{$rgt}"); - if (! $andSelf) { + if ( ! $andSelf) { $inner->where($keyName, '<>', $id); } }, $boolean); @@ -183,12 +183,7 @@ public function ancestorsAndSelf($id, array $columns = [ '*' ]) */ public function whereNodeBetween($values, $boolean = 'and', $not = false) { - $this->query->whereBetween( - $this->model->getTable() . '.' . $this->model->getLftName(), - $values, - $boolean, - $not - ); + $this->query->whereBetween($this->model->getTable() . '.' . $this->model->getLftName(), $values, $boolean, $not); return $this; } @@ -219,11 +214,8 @@ public function orWhereNodeBetween($values) * * @return $this */ - public function whereDescendantOf( - $id, - $boolean = 'and', - $not = false, - $andSelf = false + public function whereDescendantOf($id, $boolean = 'and', $not = false, + $andSelf = false ) { if (NestedSet::isNode($id)) { $data = $id->getBounds(); @@ -233,7 +225,7 @@ public function whereDescendantOf( } // Don't include the node - if (! $andSelf) { + if ( ! $andSelf) { ++$data[0]; } @@ -297,7 +289,9 @@ public function descendantsOf($id, array $columns = [ '*' ], $andSelf = false) { try { return $this->whereDescendantOf($id, 'and', false, $andSelf)->get($columns); - } catch (ModelNotFoundException $e) { + } + + catch (ModelNotFoundException $e) { return $this->model->newCollection(); } } @@ -339,7 +333,7 @@ protected function whereIsBeforeOrAfter($id, $operator, $boolean) $value = '('.$valueQuery->toSql().')'; } - list($lft, ) = $this->wrappedColumns(); + list($lft,) = $this->wrappedColumns(); $this->query->whereRaw("{$lft} {$operator} {$value}", [ ], $boolean); @@ -405,9 +399,7 @@ public function leaves(array $columns = [ '*']) */ public function withDepth($as = 'depth') { - if ($this->query->columns === null) { - $this->query->columns = [ '*' ]; - } + if ($this->query->columns === null) $this->query->columns = [ '*' ]; $table = $this->wrappedTable(); @@ -648,9 +640,7 @@ protected function columnPatch($col, array $params) extract($params); /** @var int $height */ - if ($height > 0) { - $height = '+'.$height; - } + if ($height > 0) $height = '+'.$height; if (isset($cut)) { return new Expression("case when {$col} >= {$cut} then {$col}{$height} else {$col} end"); @@ -661,12 +651,9 @@ protected function columnPatch($col, array $params) /** @var int $rgt */ /** @var int $from */ /** @var int $to */ - if ($distance > 0) { - $distance = '+'.$distance; - } + if ($distance > 0) $distance = '+'.$distance; - return new Expression( - "case ". + return new Expression("case ". "when {$col} between {$lft} and {$rgt} then {$col}{$distance} ". // Move the node "when {$col} between {$from} and {$to} then {$col}{$height} ". // Move other nodes "else {$col} end" @@ -909,7 +896,7 @@ protected function fixNodes(array &$dictionary, $parent = null) $cut = self::reorderNodes($dictionary, $updated, $parentId, $cut); // Save nodes that have invalid parent as roots - while (! empty($dictionary)) { + while ( ! empty($dictionary)) { $dictionary[null] = reset($dictionary); unset($dictionary[key($dictionary)]); @@ -940,12 +927,9 @@ protected function fixNodes(array &$dictionary, $parent = null) * @internal param int $fixed */ protected static function reorderNodes( - array &$dictionary, - array &$updated, - $parentId = null, - $cut = 1 + array &$dictionary, array &$updated, $parentId = null, $cut = 1 ) { - if (! isset($dictionary[$parentId])) { + if ( ! isset($dictionary[$parentId])) { return $cut; } @@ -998,7 +982,7 @@ public function rebuildTree(array $data, $delete = false, $root = null) $this->buildRebuildDictionary($dictionary, $data, $existing, $parentId); /** @var Model|NodeTrait $model */ - if (! empty($existing)) { + if ( ! empty($existing)) { if ($delete && ! $this->model->usesSoftDelete()) { $this->model ->newScopedQuery() @@ -1040,25 +1024,24 @@ public function rebuildSubtree($root, array $data, $delete = false) * @param array $existing * @param mixed $parentId */ - protected function buildRebuildDictionary( - array &$dictionary, - array $data, - array &$existing, - $parentId = null + protected function buildRebuildDictionary(array &$dictionary, + array $data, + array &$existing, + $parentId = null ) { $keyName = $this->model->getKeyName(); foreach ($data as $itemData) { /** @var NodeTrait|Model $model */ - if (! isset($itemData[$keyName])) { + if ( ! isset($itemData[$keyName])) { $model = $this->model->newInstance($this->model->getAttributes()); // Set some values that will be fixed later $model->rawNode(0, 0, $parentId); } else { - if (! isset($existing[$key = $itemData[$keyName]])) { - throw new ModelNotFoundException(); + if ( ! isset($existing[$key = $itemData[$keyName]])) { + throw new ModelNotFoundException; } $model = $existing[$key]; @@ -1073,16 +1056,12 @@ protected function buildRebuildDictionary( $dictionary[$parentId][] = $model; - if (! isset($itemData['children'])) { - continue; - } + if ( ! isset($itemData['children'])) continue; - $this->buildRebuildDictionary( - $dictionary, - $itemData['children'], - $existing, - $model->getKey() - ); + $this->buildRebuildDictionary($dictionary, + $itemData['children'], + $existing, + $model->getKey()); } } diff --git a/tests/NodeTest.php b/tests/NodeTest.php index d565ca2..15f6f7b 100644 --- a/tests/NodeTest.php +++ b/tests/NodeTest.php @@ -84,9 +84,7 @@ public function assertTreeNotBroken($table = 'categories') public function dumpTree($items = null) { - if (! $items) { - $items = Category::withTrashed()->defaultOrder()->get(); - } + if ( ! $items) $items = Category::withTrashed()->defaultOrder()->get(); foreach ($items as $item) { echo PHP_EOL.($item->trashed() ? '-' : '+').' '.$item->name." ".$item->getKey().' '.$item->getLft()." ".$item->getRgt().' '.$item->getParentId(); @@ -223,32 +221,26 @@ public function testCategoryMovesUp() $this->assertNodeReceivesValidValues($node); } - /** - * @expectedException Exception - */ public function testFailsToInsertIntoChild() { + $this->expectException(Exception::class); $node = $this->findCategory('notebooks'); $target = $node->children()->first(); $node->afterNode($target)->save(); } - /** - * @expectedException Exception - */ public function testFailsToAppendIntoItself() { + $this->expectException(Exception::class); $node = $this->findCategory('notebooks'); $node->appendToNode($node)->save(); } - /** - * @expectedException Exception - */ public function testFailsToPrependIntoItself() { + $this->expectException(Exception::class); $node = $this->findCategory('notebooks'); $node->prependTo($node)->save(); @@ -340,11 +332,9 @@ public function testParentIdAttributeAccessorAppendsNode() $this->assertTrue($node->isRoot()); } - /** - * @expectedException Exception - */ public function testFailsToSaveNodeUntilNotInserted() { + $this->expectException(Exception::class); $node = new Category; $node->save(); } @@ -407,11 +397,9 @@ public function testSoftDeletedNodeisDeletedWhenParentIsDeleted() $this->assertNull($this->findCategory('sony')); } - /** - * @expectedException Exception - */ public function testFailsToSaveNodeUntilParentIsSaved() { + $this->expectException(Exception::class); $node = new Category(array('title' => 'Node')); $parent = new Category(array('title' => 'Parent')); @@ -617,15 +605,14 @@ public function testCreatesViaRelationship() public function testCreatesTree() { $node = Category::create( - [ + [ 'name' => 'test', 'children' => [ [ 'name' => 'test2' ], [ 'name' => 'test3' ], ], - ] - ); + ]); $this->assertTreeNotBroken(); @@ -644,11 +631,9 @@ public function testDescendantsOfNonExistingNode() $this->assertTrue($node->getDescendants()->isEmpty()); } - /** - * @expectedException \Illuminate\Database\Eloquent\ModelNotFoundException - */ public function testWhereDescendantsOf() { + $this->expectException(\Illuminate\Database\Eloquent\ModelNotFoundException::class); Category::whereDescendantOf(124)->get(); } @@ -672,7 +657,8 @@ public function testMultipleDeletionsDoNotBrakeTree() { $category = $this->findCategory('mobile'); - foreach ($category->children()->take(2)->get() as $child) { + foreach ($category->children()->take(2)->get() as $child) + { $child->forceDelete(); } @@ -854,11 +840,9 @@ public function testRebuildTreeWithDeletion() $this->assertTrue($nodes->count() > 1); } - /** - * @expectedException \Illuminate\Database\Eloquent\ModelNotFoundException - */ public function testRebuildFailsWithInvalidPK() { + $this->expectException(\Illuminate\Database\Eloquent\ModelNotFoundException::class); Category::rebuildTree([ [ 'id' => 24 ] ]); } @@ -927,9 +911,7 @@ public function testEagerLoadAncestors() foreach ($categories as $category) { $output["{$category->name} ({$category->id})}"] = $category->ancestors->count() - ? implode(' > ', $category->ancestors->map(function ($cat) { - return "{$cat->name} ({$cat->id})"; - })->toArray()) + ? implode(' > ', $category->ancestors->map(function ($cat) { return "{$cat->name} ({$cat->id})"; })->toArray()) : ''; } @@ -961,9 +943,7 @@ public function testLazyLoadAncestors() foreach ($categories as $category) { $output["{$category->name} ({$category->id})}"] = $category->ancestors->count() - ? implode(' > ', $category->ancestors->map(function ($cat) { - return "{$cat->name} ({$cat->id})"; - })->toArray()) + ? implode(' > ', $category->ancestors->map(function ($cat) { return "{$cat->name} ({$cat->id})"; })->toArray()) : ''; } @@ -1004,9 +984,10 @@ public function testReplication() $this->assertEquals(1, $category->getParentId()); } + } function all($items) { return is_array($items) ? $items : $items->all(); -} +} \ No newline at end of file diff --git a/tests/ScopedNodeTest.php b/tests/ScopedNodeTest.php index 7bcfdd2..3a4beca 100644 --- a/tests/ScopedNodeTest.php +++ b/tests/ScopedNodeTest.php @@ -5,7 +5,7 @@ class ScopedNodeTest extends PHPUnit\Framework\TestCase { - public static function setUpBeforeClass(): void + public static function setUpBeforeClass() : void { $schema = Capsule::schema(); @@ -23,7 +23,7 @@ public static function setUpBeforeClass(): void Capsule::enableQueryLog(); } - public function setUp(): void + public function setUp() : void { $data = include __DIR__.'/data/menu_items.php'; @@ -36,7 +36,7 @@ public function setUp(): void date_default_timezone_set('America/Denver'); } - public function tearDown(): void + public function tearDown() : void { Capsule::table('menu_items')->truncate(); } @@ -159,11 +159,9 @@ public function testInsertion() $this->assertOtherScopeNotAffected(); } - /** - * @expectedException \Illuminate\Database\Eloquent\ModelNotFoundException - */ public function testInsertionToParentFromOtherScope() { + $this->expectException(\Illuminate\Database\Eloquent\ModelNotFoundException::class); $node = MenuItem::create([ 'menu_id' => 2, 'parent_id' => 5 ]); } @@ -201,25 +199,21 @@ protected function assertOtherScopeNotAffected() MenuItem::scoped([ 'menu_id' => 2 ])->rebuildTree($data); }*/ - /** - * @expectedException LogicException - */ public function testAppendingToAnotherScopeFails() { + $this->expectException(LogicException::class); $a = MenuItem::find(1); $b = MenuItem::find(3); $a->appendToNode($b)->save(); } - /** - * @expectedException LogicException - */ public function testInsertingBeforeAnotherScopeFails() { + $this->expectException(LogicException::class); $a = MenuItem::find(1); $b = MenuItem::find(3); $a->insertAfterNode($b); } -} +} \ No newline at end of file From 21d2c701a86ad63eb41cc353da810892d1a45e8e Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Mon, 1 Nov 2021 13:32:53 +0100 Subject: [PATCH 06/19] Upgraded Laravel --- composer.json | 6 +++--- src/BaseRelation.php | 14 -------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 98604b9..ea51515 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,9 @@ "require": { "php": ">=7.1.3", - "illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0|>=8.0 <8.43.0", - "illuminate/database": "~5.7.0|~5.8.0|^6.0|^7.0|>=8.0 <8.43.0", - "illuminate/events": "~5.7.0|~5.8.0|^6.0|^7.0|>=8.0 <8.43.0" + "illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0|^8.0", + "illuminate/database": "~5.7.0|~5.8.0|^6.0|^7.0|^8.0", + "illuminate/events": "~5.7.0|~5.8.0|^6.0|^7.0|^8.0" }, "autoload": { diff --git a/src/BaseRelation.php b/src/BaseRelation.php index 3b3b592..f4acd1e 100644 --- a/src/BaseRelation.php +++ b/src/BaseRelation.php @@ -111,20 +111,6 @@ public function initRelation(array $models, $relation) return $models; } - /** - * @param EloquentBuilder $query - * @param EloquentBuilder $parent - * @param array $columns - * - * @return mixed - */ - public function getRelationQuery( - EloquentBuilder $query, EloquentBuilder $parent, - $columns = [ '*' ] - ) { - return $this->getRelationExistenceQuery($query, $parent, $columns); - } - /** * Get a relationship join table hash. * From 678e24c545cbcaea60cdda762aa52e73714e4566 Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Thu, 6 Jan 2022 18:19:43 +0100 Subject: [PATCH 07/19] Fixed order of deletion. (#5) --- src/NodeTrait.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 0b985ab..13eb0c6 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -49,10 +49,9 @@ public static function bootNodeTrait() static::deleting(function ($model) { // We will need fresh data to delete node safely + // We must delete the descendants BEFORE we delete the actual + // album to avoid failing FOREIGN key constraints. $model->refreshNode(); - }); - - static::deleted(function ($model) { $model->deleteDescendants(); }); From fbc505049800e3e827de28060edeb8ef15bb96d2 Mon Sep 17 00:00:00 2001 From: Matthias Nagel Date: Thu, 7 Apr 2022 21:30:44 +0200 Subject: [PATCH 08/19] Added ORDER BY to delete operation. --- src/NodeTrait.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/NodeTrait.php b/src/NodeTrait.php index 13eb0c6..5e9ee78 100644 --- a/src/NodeTrait.php +++ b/src/NodeTrait.php @@ -627,7 +627,31 @@ protected function deleteDescendants() ? 'forceDelete' : 'delete'; - $this->descendants()->{$method}(); + // We must delete the nodes in correct order to avoid failing + // foreign key constraints when we delete an entire subtree. + // For MySQL we must avoid that a parent is deleted before its + // children although the complete subtree will be deleted eventually. + // Hence, deletion must start with the deepest node, i.e. with the + // highest _lft value first. + // Note: `DELETE ... ORDER BY` is non-standard SQL but required by + // MySQL (see https://dev.mysql.com/doc/refman/8.0/en/delete.html), + // because MySQL only supports "row consistency". + // This means the DB must be consistent before and after every single + // operation on a row. + // This is contrasted by statement and transaction consistency which + // means that the DB must be consistent before and after every + // completed statement/transaction. + // (See https://dev.mysql.com/doc/refman/8.0/en/ansi-diff-foreign-keys.html) + // ANSI Standard SQL requires support for statement/transaction + // consistency, but only PostgreSQL supports it. + // (Good PosgreSQL :-) ) + // PostgreSQL does not support `DELETE ... ORDER BY` but also has no + // need for it. + // The grammar compiler removes the superfluous "ORDER BY" for + // PostgreSQL. + $this->descendants() + ->orderBy($this->getLftName(), 'desc') + ->{$method}(); if ($this->hardDeleting()) { $height = $rgt - $lft + 1; From ec8e1d706187eb672e111b36e4cca8c28229e202 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 14:09:31 +0200 Subject: [PATCH 09/19] https://github.com/lazychaser/laravel-nestedset/pull/552 --- src/BaseRelation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BaseRelation.php b/src/BaseRelation.php index 031eecf..e2bd7db 100644 --- a/src/BaseRelation.php +++ b/src/BaseRelation.php @@ -144,7 +144,7 @@ public function addEagerConstraints(array $models) // The first model in the array is always the parent, so add the scope constraints based on that model. // @link https://github.com/laravel/framework/pull/25240 // @link https://github.com/lazychaser/laravel-nestedset/issues/351 - optional($models[0])->applyNestedSetScope($this->query); + optional(reset($models))->applyNestedSetScope($this->query); $this->query->whereNested(function (Builder $inner) use ($models) { // We will use this query in order to apply constraints to the From 62c7528003f328bf0606ad7f0808d6827026b37a Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 14:15:14 +0200 Subject: [PATCH 10/19] removed useless actions --- .github/workflows/CI.yaml | 1 + .github/workflows/run-tests.yml | 43 --------------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 5d48eb7..0bd18f9 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -28,6 +28,7 @@ jobs: name: PHP ${{ matrix.php-versions }} - ${{ matrix.dependencies }} env: + COMPOSER_NO_INTERACTION: 1 extensions: curl json libxml dom key: cache-v1 # can be any string, change to clear the extension cache. diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index 438e5dd..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Unit Tests - -on: - push: - branches: - - master - pull_request: - branches: - - "*" - schedule: - - cron: '0 0 * * *' - -jobs: - php-tests: - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - COMPOSER_NO_INTERACTION: 1 - - strategy: - fail-fast: false - matrix: - php: [8.1, 8.0, 7.4, 7.3, 7.2] - - name: P${{ matrix.php }} - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - coverage: none - tools: composer:v2 - - - name: Install dependencies - run: | - composer install -o --quiet - - - name: Execute Unit Tests - run: composer test From e1bf338ca522f56555f2eb271284008047c64cfd Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 14:17:20 +0200 Subject: [PATCH 11/19] fix Globals error --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8764cc2..3eaae61 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "7.*|8.*|9.*" + "phpunit/phpunit": "^9.3" }, "scripts": { From a780d14bbd8dc2dac14974299874dc76362678d5 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 14:20:31 +0200 Subject: [PATCH 12/19] bumping version of phpunit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3eaae61..9b0bd6a 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "scripts": { From 48e4a2bf0d808c4395afd4483111988bc20ef7e3 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 14:37:56 +0200 Subject: [PATCH 13/19] testing with -c config --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9b0bd6a..201d890 100644 --- a/composer.json +++ b/composer.json @@ -26,13 +26,13 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5.20" }, "scripts": { "run-tests": [ "vendor/bin/phpunit -c phpunit.xml", - "vendor/bin/phpunit --coverage-clover=coverage.xml" + "vendor/bin/phpunit -c phpunit.xml --coverage-clover=coverage.xml" ], "validate-files": [ "vendor/bin/parallel-lint --exclude vendor ." From fabb0c11da40a07ca8eca58969dad8da03954272 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 14:42:15 +0200 Subject: [PATCH 14/19] lower restriction for phpunit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 201d890..1629be2 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^9.5.20" + "phpunit/phpunit": "^9.5" }, "scripts": { From ac32ea4ac35422579a89e76547eaafcbc56e193e Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 14:43:56 +0200 Subject: [PATCH 15/19] up phpunit to 9.5.20 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1629be2..201d890 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5.20" }, "scripts": { From d8778b45ad19a74b8f467c05410aa5f32c752c4e Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 16:12:46 +0200 Subject: [PATCH 16/19] revert some changes as per requested --- tests/NodeTest.php | 12 +++++++++--- tests/ScopedNodeTest.php | 9 ++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/NodeTest.php b/tests/NodeTest.php index 15f6f7b..642b8be 100644 --- a/tests/NodeTest.php +++ b/tests/NodeTest.php @@ -5,7 +5,7 @@ class NodeTest extends PHPUnit\Framework\TestCase { - public static function setUpBeforeClass() : void + public static function setUpBeforeClass(): void { $schema = Capsule::schema(); @@ -23,7 +23,7 @@ public static function setUpBeforeClass() : void Capsule::enableQueryLog(); } - public function setUp() : void + public function setUp(): void { $data = include __DIR__.'/data/categories.php'; @@ -36,7 +36,7 @@ public function setUp() : void date_default_timezone_set('America/Denver'); } - public function tearDown() : void + public function tearDown(): void { Capsule::table('categories')->truncate(); } @@ -233,6 +233,7 @@ public function testFailsToInsertIntoChild() public function testFailsToAppendIntoItself() { $this->expectException(Exception::class); + $node = $this->findCategory('notebooks'); $node->appendToNode($node)->save(); @@ -241,6 +242,7 @@ public function testFailsToAppendIntoItself() public function testFailsToPrependIntoItself() { $this->expectException(Exception::class); + $node = $this->findCategory('notebooks'); $node->prependTo($node)->save(); @@ -335,6 +337,7 @@ public function testParentIdAttributeAccessorAppendsNode() public function testFailsToSaveNodeUntilNotInserted() { $this->expectException(Exception::class); + $node = new Category; $node->save(); } @@ -400,6 +403,7 @@ public function testSoftDeletedNodeisDeletedWhenParentIsDeleted() public function testFailsToSaveNodeUntilParentIsSaved() { $this->expectException(Exception::class); + $node = new Category(array('title' => 'Node')); $parent = new Category(array('title' => 'Parent')); @@ -634,6 +638,7 @@ public function testDescendantsOfNonExistingNode() public function testWhereDescendantsOf() { $this->expectException(\Illuminate\Database\Eloquent\ModelNotFoundException::class); + Category::whereDescendantOf(124)->get(); } @@ -843,6 +848,7 @@ public function testRebuildTreeWithDeletion() public function testRebuildFailsWithInvalidPK() { $this->expectException(\Illuminate\Database\Eloquent\ModelNotFoundException::class); + Category::rebuildTree([ [ 'id' => 24 ] ]); } diff --git a/tests/ScopedNodeTest.php b/tests/ScopedNodeTest.php index 3a4beca..c0eac24 100644 --- a/tests/ScopedNodeTest.php +++ b/tests/ScopedNodeTest.php @@ -5,7 +5,7 @@ class ScopedNodeTest extends PHPUnit\Framework\TestCase { - public static function setUpBeforeClass() : void + public static function setUpBeforeClass(): void { $schema = Capsule::schema(); @@ -23,7 +23,7 @@ public static function setUpBeforeClass() : void Capsule::enableQueryLog(); } - public function setUp() : void + public function setUp(): void { $data = include __DIR__.'/data/menu_items.php'; @@ -36,7 +36,7 @@ public function setUp() : void date_default_timezone_set('America/Denver'); } - public function tearDown() : void + public function tearDown(): void { Capsule::table('menu_items')->truncate(); } @@ -162,6 +162,7 @@ public function testInsertion() public function testInsertionToParentFromOtherScope() { $this->expectException(\Illuminate\Database\Eloquent\ModelNotFoundException::class); + $node = MenuItem::create([ 'menu_id' => 2, 'parent_id' => 5 ]); } @@ -202,6 +203,7 @@ protected function assertOtherScopeNotAffected() public function testAppendingToAnotherScopeFails() { $this->expectException(LogicException::class); + $a = MenuItem::find(1); $b = MenuItem::find(3); @@ -211,6 +213,7 @@ public function testAppendingToAnotherScopeFails() public function testInsertingBeforeAnotherScopeFails() { $this->expectException(LogicException::class); + $a = MenuItem::find(1); $b = MenuItem::find(3); From f7fd03438e67e93d5f33e694d688b90f0069739e Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 10 Apr 2022 16:14:40 +0200 Subject: [PATCH 17/19] missed one --- tests/NodeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/NodeTest.php b/tests/NodeTest.php index 642b8be..3b0831a 100644 --- a/tests/NodeTest.php +++ b/tests/NodeTest.php @@ -224,6 +224,7 @@ public function testCategoryMovesUp() public function testFailsToInsertIntoChild() { $this->expectException(Exception::class); + $node = $this->findCategory('notebooks'); $target = $node->children()->first(); From d69b414094c51f017807b28836a5f57a18705824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Viguier?= Date: Sun, 10 Apr 2022 16:34:08 +0200 Subject: [PATCH 18/19] Update composer.json Co-authored-by: Matthias Nagel --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 201d890..fb15894 100644 --- a/composer.json +++ b/composer.json @@ -52,4 +52,4 @@ ] } } -} \ No newline at end of file +} From 6d0a869efe035b4be904281c4814907da074f317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Viguier?= Date: Sun, 10 Apr 2022 16:34:45 +0200 Subject: [PATCH 19/19] Update README.markdown Co-authored-by: Matthias Nagel --- README.markdown | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index dad2772..67cd0bd 100644 --- a/README.markdown +++ b/README.markdown @@ -5,7 +5,10 @@ This is a Laravel 4-8 package for working with trees in relational databases. -It is a fork of [lazychaser/laravel-nestedset](https://github.com/lazychaser/laravel-nestedset) and contains patches which are required for using the library with [Lychee](https://github.com/LycheeOrg/Lychee). +It is a fork of [lazychaser/laravel-nestedset](https://github.com/lazychaser/laravel-nestedset) and contains general patches which are required for using the library with [Lychee](https://github.com/LycheeOrg/Lychee). Note that the patches are **not** specific for Lychee, but a generally useful. Inter alia: + + * Routines respect a foreign key constraint on the parent-child-relation by taking care that changes to the tree are applied in the correct order. + * The code does not fail if the model which uses `NoteTrait` does not directly extend `Model` but indirectly inherits `Model` via another parent class. * **Laravel 8.0** is supported since v6 * **Laravel 5.7, 5.8, 6.0, 7.0** is supported since v5