From d0e3ee3accdcbd80b5fbe04274740ce607d223d0 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 1 Apr 2020 18:43:03 +0700 Subject: [PATCH 001/113] refactored setup of the testing environment --- .gitignore | 8 +- composer.json | 12 +- composer.lock | 3536 ++++++++++++++++++++++++++++++++++ tests/BaseTestCase.php | 96 +- tests/CollectionTestCase.php | 1 - tests/EntityTestCase.php | 8 - 6 files changed, 3578 insertions(+), 83 deletions(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 5826402..1785821 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ +/.idea/ +/docker /vendor +/.env.testing +/docker-compose.yaml +/_ide_helper.php composer.phar -composer.lock -.DS_Store +.DS_Store \ No newline at end of file diff --git a/composer.json b/composer.json index 340f399..5155b08 100644 --- a/composer.json +++ b/composer.json @@ -12,15 +12,11 @@ } ], "require": { - "php": ">=5.4.0" + "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "dev-master", - "way/phpunit-wrappers": "dev-master", - "way/laravel-test-helpers": "dev-master", - "mockery/mockery": "dev-master", - "orchestra/testbench": "dev-master", - "doctrine/dbal": "dev-master" + "phpunit/phpunit": "^5", + "orchestra/testbench": "3.4.12" }, "autoload": { "classmap": ["tests"], @@ -31,5 +27,5 @@ "config": { "preferred-install": "dist" }, - "minimum-stability": "dev" + "minimum-stability": "stable" } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..5e3d200 --- /dev/null +++ b/composer.lock @@ -0,0 +1,3536 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b1b66425a209df97c7f91a3b6c4907d6", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/inflector", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2019-10-30T19:59:35+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-10-21T16:45:58+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "time": "2019-12-30T22:54:17+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.9.1", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^2.9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2019-12-12T13:22:17+00:00" + }, + { + "name": "kylekatarnls/update-helper", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/kylekatarnls/update-helper.git", + "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kylekatarnls/update-helper/zipball/5786fa188e0361b9adf9e8199d7280d1b2db165e", + "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0.0", + "php": ">=5.3.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "composer/composer": "2.0.x-dev || ^2.0.0-dev", + "phpunit/phpunit": ">=4.8.35 <6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "UpdateHelper\\ComposerPlugin" + }, + "autoload": { + "psr-0": { + "UpdateHelper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Update helper", + "time": "2019-07-29T11:03:54+00:00" + }, + { + "name": "laravel/framework", + "version": "v5.4.36", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "1062a22232071c3e8636487c86ec1ae75681bbf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/1062a22232071c3e8636487c86ec1ae75681bbf9", + "reference": "1062a22232071c3e8636487c86ec1ae75681bbf9", + "shasum": "" + }, + "require": { + "doctrine/inflector": "~1.1", + "erusev/parsedown": "~1.6", + "ext-mbstring": "*", + "ext-openssl": "*", + "league/flysystem": "~1.0", + "monolog/monolog": "~1.11", + "mtdowling/cron-expression": "~1.0", + "nesbot/carbon": "~1.20", + "paragonie/random_compat": "~1.4|~2.0", + "php": ">=5.6.4", + "ramsey/uuid": "~3.0", + "swiftmailer/swiftmailer": "~5.4", + "symfony/console": "~3.2", + "symfony/debug": "~3.2", + "symfony/finder": "~3.2", + "symfony/http-foundation": "~3.2", + "symfony/http-kernel": "~3.2", + "symfony/process": "~3.2", + "symfony/routing": "~3.2", + "symfony/var-dumper": "~3.2", + "tijsverkoyen/css-to-inline-styles": "~2.2", + "vlucas/phpdotenv": "~2.2" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "tightenco/collect": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "~3.0", + "doctrine/dbal": "~2.5", + "mockery/mockery": "~0.9.4", + "pda/pheanstalk": "~3.0", + "phpunit/phpunit": "~5.7", + "predis/predis": "~1.0", + "symfony/css-selector": "~3.2", + "symfony/dom-crawler": "~3.2" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).", + "laravel/tinker": "Required to use the tinker console command (~1.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", + "nexmo/client": "Required to use the Nexmo transport (~1.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", + "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.2).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.2).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "time": "2017-08-30T09:26:16+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.66", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/021569195e15f8209b1c4bebb78bd66aa4f08c21", + "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.26" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2020-03-17T18:58:12+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.25.3", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1", + "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2019-12-20T14:15:16+00:00" + }, + { + "name": "mtdowling/cron-expression", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/cron-expression.git", + "reference": "9be552eebcc1ceec9776378f7dcc085246cacca6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9be552eebcc1ceec9776378f7dcc085246cacca6", + "reference": "9be552eebcc1ceec9776378f7dcc085246cacca6", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "abandoned": "dragonmantank/cron-expression", + "time": "2019-12-28T04:23:06+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2020-01-17T21:11:47+00:00" + }, + { + "name": "nesbot/carbon", + "version": "1.39.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "4be0c005164249208ce1b5ca633cd57bdd42ff33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4be0c005164249208ce1b5ca633cd57bdd42ff33", + "reference": "4be0c005164249208ce1b5ca633cd57bdd42ff33", + "shasum": "" + }, + "require": { + "kylekatarnls/update-helper": "^1.1", + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" + }, + "require-dev": { + "composer/composer": "^1.2", + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "bin": [ + "bin/upgrade-carbon" + ], + "type": "library", + "extra": { + "update-helper": "Carbon\\Upgrade", + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2019-10-14T05:51:36+00:00" + }, + { + "name": "orchestra/testbench", + "version": "v3.4.12", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench.git", + "reference": "1a040537b09fa3e5a6c6a703a1180cf6b29e1f0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/1a040537b09fa3e5a6c6a703a1180cf6b29e1f0e", + "reference": "1a040537b09fa3e5a6c6a703a1180cf6b29e1f0e", + "shasum": "" + }, + "require": { + "fzaninotto/faker": "~1.4", + "laravel/framework": "~5.4.36", + "orchestra/testbench-core": "~3.4.6", + "php": ">=5.6.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4 || ~1.0", + "orchestra/database": "~3.4.0", + "phpunit/phpunit": "~5.7" + }, + "suggest": { + "mockery/mockery": "Allow to use Mockery for testing (^0.9.4).", + "orchestra/database": "Allow to use --realpath migration for testing (~3.4).", + "orchestra/testbench-browser-kit": "Allow to use legacy BrowserKit for testing (~3.4).", + "phpunit/phpunit": "Allow to use PHPUnit for testing (~5.7)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Laravel Testing Helper for Packages Development", + "homepage": "http://orchestraplatform.com/docs/latest/components/testbench/", + "keywords": [ + "BDD", + "TDD", + "laravel", + "orchestra-platform", + "orchestral", + "testing" + ], + "time": "2018-02-20T05:27:50+00:00" + }, + { + "name": "orchestra/testbench-core", + "version": "v3.4.7", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench-core.git", + "reference": "6a7ed6b65942c9b1bffc0bf8f0eab442ccb30e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/6a7ed6b65942c9b1bffc0bf8f0eab442ccb30e5b", + "reference": "6a7ed6b65942c9b1bffc0bf8f0eab442ccb30e5b", + "shasum": "" + }, + "require": { + "fzaninotto/faker": "~1.4", + "php": ">=5.6.0" + }, + "require-dev": { + "laravel/framework": "~5.4.17", + "mockery/mockery": "^0.9.4", + "orchestra/database": "~3.4.0", + "phpunit/phpunit": "~5.7 || ~6.0" + }, + "suggest": { + "laravel/framework": "Required for testing (~5.4.0).", + "mockery/mockery": "Allow to use Mockery for testing (^0.9.4).", + "orchestra/database": "Allow to use --realpath migration for testing (~3.4).", + "orchestra/testbench-browser-kit": "Allow to use legacy BrowserKit for testing (~3.4).", + "orchestra/testbench-dusk": "Allow to use Laravel Dusk for testing (~3.4).", + "phpunit/phpunit": "Allow to use PHPUnit for testing (~6.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Orchestra\\Testbench\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Testing Helper for Laravel Development", + "homepage": "http://orchestraplatform.com/docs/latest/components/testbench/", + "keywords": [ + "BDD", + "TDD", + "laravel", + "orchestra-platform", + "orchestral", + "testing" + ], + "time": "2019-12-10T00:56:53+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.18", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2019-01-03T20:59:08+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "shasum": "" + }, + "require": { + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" + }, + "require-dev": { + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-02-22T12:28:44+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", + "shasum": "" + }, + "require": { + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-02-18T18:59:58+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.9.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92", + "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92", + "shasum": "" + }, + "require": { + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | 9.99.99", + "php": "^5.4 | ^7 | ^8", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9.11 | ^1", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1", + "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2020-02-21T04:36:14+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.12", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "181b89f18a90f8925ef805f950d47a7190e9b950" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950", + "reference": "181b89f18a90f8925ef805f950d47a7190e9b950", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2018-07-31T09:26:32+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "bf60d5e606cd595391c5f82bf6b570d9573fa120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/bf60d5e606cd595391c5f82bf6b570d9573fa120", + "reference": "bf60d5e606cd595391c5f82bf6b570d9573fa120", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T17:07:22+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "5f8d5271303dad260692ba73dfa21777d38e124e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/5f8d5271303dad260692ba73dfa21777d38e124e", + "reference": "5f8d5271303dad260692ba73dfa21777d38e124e", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:56:45+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "ce9f3b5e8e1c50f849fded59b3a1b6bc3562ec29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/ce9f3b5e8e1c50f849fded59b3a1b6bc3562ec29", + "reference": "ce9f3b5e8e1c50f849fded59b3a1b6bc3562ec29", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2020-03-23T10:22:40+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abc8e3618bfdb55e44c8c6a00abd333f831bbfed", + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:54:36+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T09:54:03+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "5ec813ccafa8164ef21757e8c725d3a57da59200" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/5ec813ccafa8164ef21757e8c725d3a57da59200", + "reference": "5ec813ccafa8164ef21757e8c725d3a57da59200", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2020-02-14T07:34:21+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "a8833c56f6a4abcf17a319d830d71fdb0ba93675" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a8833c56f6a4abcf17a319d830d71fdb0ba93675", + "reference": "a8833c56f6a4abcf17a319d830d71fdb0ba93675", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2020-03-23T12:14:52+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "c15b5acab571224b1bf792692ff2ad63239081fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c15b5acab571224b1bf792692ff2ad63239081fe", + "reference": "c15b5acab571224b1bf792692ff2ad63239081fe", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0", + "symfony/debug": "^3.3.3|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php56": "~1.8" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0|~4.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/console": "~2.8|~3.0|~4.0", + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "^3.4.10|^4.0.10", + "symfony/dom-crawler": "~2.8|~3.0|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/process": "~2.8|~3.0|~4.0", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0", + "symfony/templating": "~2.8|~3.0|~4.0", + "symfony/translation": "~2.8|~3.0|~4.0", + "symfony/var-dumper": "~3.3|~4.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2020-03-30T06:25:13+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2020-02-27T09:26:54+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2020-03-09T19:04:49+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/d51ec491c8ddceae7dca8dd6c7e30428f543f37d", + "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-03-09T19:04:49+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "2a18e37a489803559284416df58c71ccebe50bf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/2a18e37a489803559284416df58c71ccebe50bf0", + "reference": "2a18e37a489803559284416df58c71ccebe50bf0", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-02-27T09:26:54+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/d8e76c104127675d0ea3df3be0f2ae24a8619027", + "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2020-03-02T11:55:35+00:00" + }, + { + "name": "symfony/process", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "1dbc09f6e14703ae2398efc86b02ae2bcd9a9931" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/1dbc09f6e14703ae2398efc86b02ae2bcd9a9931", + "reference": "1dbc09f6e14703ae2398efc86b02ae2bcd9a9931", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2020-03-20T06:07:50+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "785e4e6b835e9ab4f9412862855d0e1b7a2b4627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/785e4e6b835e9ab4f9412862855d0e1b7a2b4627", + "reference": "785e4e6b835e9ab4f9412862855d0e1b7a2b4627", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "^3.3.1|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2020-03-25T12:02:26+00:00" + }, + { + "name": "symfony/translation", + "version": "v4.3.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "46e462be71935ae15eab531e4d491d801857f24c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/46e462be71935ae15eab531e4d491d801857f24c", + "reference": "46e462be71935ae15eab531e4d491d801857f24c", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^1.1.6" + }, + "conflict": { + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "symfony/translation-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/service-contracts": "^1.1.2", + "symfony/var-dumper": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2020-01-04T12:24:57+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "364518c132c95642e530d9b2d217acbc2ccac3e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/364518c132c95642e530d9b2d217acbc2ccac3e6", + "reference": "364518c132c95642e530d9b2d217acbc2ccac3e6", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T11:12:18+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "13c03169ae485fc7f1a5143256622ce1bd3c77eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/13c03169ae485fc7f1a5143256622ce1bd3c77eb", + "reference": "13c03169ae485fc7f1a5143256622ce1bd3c77eb", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2020-03-17T22:27:36+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "ef166890d821518106da3560086bfcbeb4fadfec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec", + "reference": "ef166890d821518106da3560086bfcbeb4fadfec", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2020-03-30T11:41:10+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.2", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/dda2ee426acd6d801d5b7fd1001cde9b5f790e15", + "reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5 || ^7.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "time": "2019-10-24T08:53:34+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "c4a653ed3f1ff900baa15b4130c8770b57285b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/c4a653ed3f1ff900baa15b4130c8770b57285b62", + "reference": "c4a653ed3f1ff900baa15b4130c8770b57285b62", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "^1.9" + }, + "require-dev": { + "ext-filter": "*", + "ext-pcre": "*", + "phpunit/phpunit": "^4.8.35 || ^5.0" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator.", + "ext-pcre": "Required to use most of the library." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2020-03-27T23:16:19+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", + "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "vimeo/psalm": "<3.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-02-14T12:15:55+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6.0" + }, + "platform-dev": [] +} diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index fd96049..d2bf637 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -1,92 +1,60 @@ app->bind('Franzose\ClosureTable\Contracts\EntityInterface', 'Franzose\ClosureTable\Models\Entity'); - $this->app->bind('Franzose\ClosureTable\Contracts\ClosureTableInterface', 'Franzose\ClosureTable\Models\ClosureTable'); - - if (!static::$sqlite_in_memory) { - DB::statement('DROP TABLE IF EXISTS entities_closure'); - DB::statement('DROP TABLE IF EXISTS entities;'); - DB::statement('DROP TABLE IF EXISTS migrations'); - } + $this->app->setBasePath(__DIR__ . '/../'); + $this->app->bind(EntityInterface::class, Entity::class); + $this->app->bind(ClosureTableInterface::class, ClosureTable::class); - $artisan = $this->app->make('Illuminate\Contracts\Console\Kernel'); - $artisan->call('migrate', [ - '--database' => 'closuretable', - '--path' => '../tests/migrations' - ]); + $artisan = $this->app->make(Kernel::class); - $artisan->call('db:seed', [ - '--class' => 'Franzose\ClosureTable\Tests\Seeds\EntitiesSeeder' + $artisan->call('migrate:refresh', [ + '--database' => static::DATABASE_CONNECTION, + '--path' => 'tests/migrations', + '--seeder' => EntitiesSeeder::class ]); - - if (static::$debug) { - Entity::$debug = true; - Event::listen('illuminate.query', function ($sql, $bindings, $time) { - $sql = str_replace(array('%', '?'), array('%%', '%s'), $sql); - $full_sql = vsprintf($sql, $bindings); - echo PHP_EOL . '- BEGIN QUERY -' . PHP_EOL . $full_sql . PHP_EOL . '- END QUERY -' . PHP_EOL; - }); - } - } - - public function tearDown() - { - Mockery::close(); } /** - * @param \Illuminate\Foundation\Application $app + * @param Application $app */ protected function getEnvironmentSetUp($app) { - // reset base path to point to our package's src directory - $app['path.base'] = __DIR__ . '/../src'; + $envFilePath = __DIR__ . '/..'; - $app['config']->set('database.default', 'closuretable'); - - if (static::$sqlite_in_memory) { - $options = [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '', - ]; - } else { - $options = [ - 'driver' => 'mysql', - 'host' => 'localhost', - 'database' => 'closuretabletest', - 'username' => 'root', - 'password' => '', - 'prefix' => '', - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - ]; + if (file_exists($envFilePath . '/.env.testing')) { + (new Dotenv($envFilePath, '.env.testing'))->load(); } - $app['config']->set('database.connections.closuretable', $options); + $app['config']->set('database.default', static::DATABASE_CONNECTION); + $app['config']->set('database.connections.' . static::DATABASE_CONNECTION, [ + 'driver' => env('DB_DRIVER', 'mysql'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT'), + 'database' => env('DB_NAME', static::DATABASE_CONNECTION . 'test'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD'), + 'prefix' => '', + 'charset' => 'utf8', + 'collation' => env('DB_COLLATION', 'utf8_unicode_ci'), + ]); } /** diff --git a/tests/CollectionTestCase.php b/tests/CollectionTestCase.php index 065a059..def855f 100644 --- a/tests/CollectionTestCase.php +++ b/tests/CollectionTestCase.php @@ -3,7 +3,6 @@ use Franzose\ClosureTable\Extensions\Collection; use Franzose\ClosureTable\Models\Entity; -use Mockery; class CollectionTestCase extends BaseTestCase { diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index f1860e8..aa112d7 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -3,7 +3,6 @@ use DB; use Franzose\ClosureTable\Models\ClosureTable; -use Mockery; use Franzose\ClosureTable\Models\Entity; use Franzose\ClosureTable\Tests\Models\Page; @@ -16,13 +15,6 @@ class EntityTestCase extends BaseTestCase */ protected $entity; - /** - * Mocked closure object. - * - * @var Mockery\MockInterface|\Yay_MockObject - */ - protected $closure; - protected static $force_boot = false; /** From 043878392b071afb638b5bead041649809556115 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 1 Apr 2020 20:15:43 +0700 Subject: [PATCH 002/113] refactored ClosureTable model --- .../ClosureTable/Models/ClosureTable.php | 69 ++++++++++--------- tests/ClosureTableTestCase.php | 26 ------- 2 files changed, 37 insertions(+), 58 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/ClosureTable.php b/src/Franzose/ClosureTable/Models/ClosureTable.php index e097ccc..449fd3f 100644 --- a/src/Franzose/ClosureTable/Models/ClosureTable.php +++ b/src/Franzose/ClosureTable/Models/ClosureTable.php @@ -1,15 +1,14 @@ connection)->statement($query); + $this->getConnection()->statement($query, [ + $descendantId, + $ancestorId, + $descendantId, + $descendantId + ]); } /** * Make a node a descendant of another ancestor or makes it a root node. * - * @param int $ancestorId + * @param mixed $ancestorId * @return void - * @throws \InvalidArgumentException */ public function moveNodeTo($ancestorId = null) { @@ -78,11 +80,8 @@ public function moveNodeTo($ancestorId = null) $descendant = $this->getDescendantColumn(); $depth = $this->getDepthColumn(); - $thisAncestorId = $this->ancestor; - $thisDescendantId = $this->descendant; - // Prevent constraint collision - if (!is_null($ancestorId) && $thisAncestorId === $ancestorId) { + if ($ancestorId !== null && $this->ancestor === $ancestorId) { return; } @@ -91,7 +90,7 @@ public function moveNodeTo($ancestorId = null) // Since we have already unbound the node relationships, // given null ancestor id, we have nothing else to do, // because now the node is already root. - if (is_null($ancestorId)) { + if ($ancestorId === null) { return; } @@ -100,11 +99,14 @@ public function moveNodeTo($ancestorId = null) SELECT supertbl.{$ancestor}, subtbl.{$descendant}, supertbl.{$depth}+subtbl.{$depth}+1 FROM {$table} as supertbl CROSS JOIN {$table} as subtbl - WHERE supertbl.{$descendant} = {$ancestorId} - AND subtbl.{$ancestor} = {$thisDescendantId} + WHERE supertbl.{$descendant} = ? + AND subtbl.{$ancestor} = ? "; - DB::connection($this->connection)->statement($query); + $this->getConnection()->statement($query, [ + $ancestorId, + $this->descendant + ]); } /** @@ -117,26 +119,29 @@ protected function unbindRelationships() $table = $this->getPrefixedTable(); $ancestorColumn = $this->getAncestorColumn(); $descendantColumn = $this->getDescendantColumn(); - $descendant = $this->descendant; $query = " DELETE FROM {$table} WHERE {$descendantColumn} IN ( SELECT d FROM ( - SELECT {$descendantColumn} as d FROM {$table} - WHERE {$ancestorColumn} = {$descendant} - ) as dct + SELECT {$descendantColumn} AS d FROM {$table} + WHERE {$ancestorColumn} = ? + ) AS dct ) AND {$ancestorColumn} IN ( SELECT a FROM ( SELECT {$ancestorColumn} AS a FROM {$table} - WHERE {$descendantColumn} = {$descendant} - AND {$ancestorColumn} <> {$descendant} - ) as ct + WHERE {$descendantColumn} = ? + AND {$ancestorColumn} <> ? + ) AS ct ) "; - DB::connection($this->connection)->delete($query); + $this->getConnection()->delete($query, [ + $this->descendant, + $this->descendant, + $this->descendant + ]); } /** @@ -146,7 +151,7 @@ protected function unbindRelationships() */ public function getPrefixedTable() { - return DB::connection($this->connection)->getTablePrefix() . $this->getTable(); + return $this->getConnection()->getTablePrefix() . $this->getTable(); } /** @@ -166,7 +171,7 @@ public function getAncestorAttribute() */ public function setAncestorAttribute($value) { - $this->attributes[$this->getAncestorColumn()] = intval($value); + $this->attributes[$this->getAncestorColumn()] = $value; } /** @@ -206,7 +211,7 @@ public function getDescendantAttribute() */ public function setDescendantAttribute($value) { - $this->attributes[$this->getDescendantColumn()] = intval($value); + $this->attributes[$this->getDescendantColumn()] = $value; } /** @@ -246,7 +251,7 @@ public function getDepthAttribute() */ public function setDepthAttribute($value) { - $this->attributes[$this->getDepthColumn()] = intval($value); + $this->attributes[$this->getDepthColumn()] = (int) $value; } /** diff --git a/tests/ClosureTableTestCase.php b/tests/ClosureTableTestCase.php index aed22a1..8c5f3d7 100644 --- a/tests/ClosureTableTestCase.php +++ b/tests/ClosureTableTestCase.php @@ -35,32 +35,6 @@ public function setUp() $this->depthColumn = $this->ctable->getDepthColumn(); } - /** - * @expectedException \InvalidArgumentException - * @dataProvider insertNodeProvider - */ - public function testInsertNodeValidatesItsArguments($ancestorId, $descendantId) - { - $this->ctable->insertNode($ancestorId, $descendantId); - } - - public function insertNodeProvider() - { - return [ - ['wrong', 12], - [12, 'wrong'], - ['wrong', 'wrong'], - ]; - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testMoveNodeToValidatesItsArgument() - { - $this->ctable->moveNodeTo('wrong'); - } - public function testAncestorQualifiedKeyName() { $this->assertEquals($this->ctable->getTable() . '.' . $this->ancestorColumn, $this->ctable->getQualifiedAncestorColumn()); From c79a9fe23119cb52cde0a5b88063e7c84252b7e6 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 1 Apr 2020 20:37:41 +0700 Subject: [PATCH 003/113] refactored Collection and its tests --- .../ClosureTable/Extensions/Collection.php | 6 +- tests/CollectionTestCase.php | 73 +++++++++++++------ ...014_01_18_162506_create_entities_table.php | 4 +- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/Franzose/ClosureTable/Extensions/Collection.php b/src/Franzose/ClosureTable/Extensions/Collection.php index a0d52f1..50c8e59 100644 --- a/src/Franzose/ClosureTable/Extensions/Collection.php +++ b/src/Franzose/ClosureTable/Extensions/Collection.php @@ -15,14 +15,15 @@ class Collection extends EloquentCollection * Retrieves children relation. * * @param $position - * @return Collection|null + * @return Collection */ public function getChildrenOf($position) { if (!$this->hasChildren($position)) { - return null; + return new static(); } + /** @var Entity $item */ $item = $this->get($position); $relation = $item->getChildrenRelationIndex(); @@ -37,6 +38,7 @@ public function getChildrenOf($position) */ public function hasChildren($position) { + /** @var Entity $item */ $item = $this->get($position); $relation = $item->getChildrenRelationIndex(); diff --git a/tests/CollectionTestCase.php b/tests/CollectionTestCase.php index def855f..5b9e69d 100644 --- a/tests/CollectionTestCase.php +++ b/tests/CollectionTestCase.php @@ -8,59 +8,90 @@ class CollectionTestCase extends BaseTestCase { public function testToTree() { - $rootEntity = new Entity; + $rootEntity = new Entity(); $rootEntity->save(); - $childEntity = with(new Entity)->moveTo(0, $rootEntity); - $grandEntity = with(new Entity)->moveTo(0, $childEntity); + $childEntity = (new Entity())->moveTo(0, $rootEntity); + $grandEntity = (new Entity())->moveTo(0, $childEntity); $childrenRelationIndex = $rootEntity->getChildrenRelationIndex(); - $tree = with(new Collection([$rootEntity, $childEntity, $grandEntity]))->toTree(); + $tree = (new Collection([$rootEntity, $childEntity, $grandEntity]))->toTree(); + + /** @var Entity $rootItem */ $rootItem = $tree->get(0); - $this->assertArrayHasKey($childrenRelationIndex, $rootItem->getRelations()); + static::assertArrayHasKey($childrenRelationIndex, $rootItem->getRelations()); + /** @var Collection $children */ $children = $rootItem->getRelation($childrenRelationIndex); - $this->assertCount(1, $children); + static::assertCount(1, $children); + /** @var Entity $childItem */ $childItem = $children->get(0); - $this->assertEquals($childEntity->getKey(), $childItem->getKey()); - $this->assertArrayHasKey($childrenRelationIndex, $childItem->getRelations()); + static::assertEquals($childEntity->getKey(), $childItem->getKey()); + static::assertArrayHasKey($childrenRelationIndex, $childItem->getRelations()); + /** @var Collection $grandItems */ $grandItems = $childItem->getRelation($childrenRelationIndex); - $this->assertCount(1, $grandItems); + static::assertCount(1, $grandItems); + /** @var Entity $grandItem */ $grandItem = $grandItems->get(0); - $this->assertEquals($grandEntity->getKey(), $grandItem->getKey()); - $this->assertArrayNotHasKey($childrenRelationIndex, $grandItem->getRelations()); + static::assertEquals($grandEntity->getKey(), $grandItem->getKey()); + static::assertArrayNotHasKey($childrenRelationIndex, $grandItem->getRelations()); } public function testHasChildren() { - $entity = new Entity; + $entity = new Entity(); $childrenRelationIndex = $entity->getChildrenRelationIndex(); - $collection = new Collection([$entity, new Entity, new Entity]); - $collection->get(0)->setRelation($childrenRelationIndex, new Collection([new Entity, new Entity, new Entity])); + $collection = new Collection([ + $entity, + new Entity(), + new Entity() + ]); + + $children = new Collection([ + new Entity(), + new Entity(), + new Entity() + ]); + + /** @var Entity $firstEntity */ + $firstEntity = $collection->get(0); + $firstEntity->setRelation($childrenRelationIndex, $children); - $this->assertTrue($collection->hasChildren(0)); + static::assertTrue($collection->hasChildren(0)); } public function testGetChildrenOf() { - $entity = new Entity; + $entity = new Entity(); $childrenRelationIndex = $entity->getChildrenRelationIndex(); - $collection = new Collection([$entity, new Entity, new Entity]); - $collection->get(0)->setRelation($childrenRelationIndex, new Collection([new Entity, new Entity, new Entity])); + $collection = new Collection([ + $entity, + new Entity(), + new Entity() + ]); + + $expected = new Collection([ + new Entity(), + new Entity(), + new Entity() + ]); + + /** @var Entity $firstEntity */ + $firstEntity = $collection->get(0); + $firstEntity->setRelation($childrenRelationIndex, $expected); - $children = $collection->getChildrenOf(0); + $actual = $collection->getChildrenOf(0); - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $children); - $this->assertCount(3, $children); + static::assertSame($expected, $actual); } } diff --git a/tests/migrations/2014_01_18_162506_create_entities_table.php b/tests/migrations/2014_01_18_162506_create_entities_table.php index 4ba2bd5..c23eee8 100644 --- a/tests/migrations/2014_01_18_162506_create_entities_table.php +++ b/tests/migrations/2014_01_18_162506_create_entities_table.php @@ -16,8 +16,8 @@ public function up() $table->increments('id'); $table->unsignedInteger('parent_id')->nullable(); $table->string('title')->default('The Title'); - $table->text('excerpt'); - $table->longText('body'); + $table->string('excerpt')->default(''); + $table->string('body')->default(''); $table->integer('position', false, true); $table->integer('real_depth', false, true); $table->softDeletes(); From edf87f29324124a2fc6473dced0190432e52d762 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 1 Apr 2020 20:40:42 +0700 Subject: [PATCH 004/113] refactored tests of the ClosureTable model --- tests/ClosureTableTestCase.php | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/ClosureTableTestCase.php b/tests/ClosureTableTestCase.php index 8c5f3d7..cf88e4f 100644 --- a/tests/ClosureTableTestCase.php +++ b/tests/ClosureTableTestCase.php @@ -8,22 +8,22 @@ class ClosureTableTestCase extends BaseTestCase /** * @var ClosureTable; */ - protected $ctable; + private $ctable; /** * @var string */ - protected $ancestorColumn; + private $ancestorColumn; /** * @var string */ - protected $descendantColumn; + private $descendantColumn; /** * @var string */ - protected $depthColumn; + private $depthColumn; public function setUp() { @@ -37,16 +37,25 @@ public function setUp() public function testAncestorQualifiedKeyName() { - $this->assertEquals($this->ctable->getTable() . '.' . $this->ancestorColumn, $this->ctable->getQualifiedAncestorColumn()); + static::assertEquals( + $this->ctable->getTable() . '.' . $this->ancestorColumn, + $this->ctable->getQualifiedAncestorColumn() + ); } public function testDescendantQualifiedKeyName() { - $this->assertEquals($this->ctable->getTable() . '.' . $this->descendantColumn, $this->ctable->getQualifiedDescendantColumn()); + static::assertEquals( + $this->ctable->getTable() . '.' . $this->descendantColumn, + $this->ctable->getQualifiedDescendantColumn() + ); } public function testDepthQualifiedKeyName() { - $this->assertEquals($this->ctable->getTable() . '.' . $this->depthColumn, $this->ctable->getQualifiedDepthColumn()); + static::assertEquals( + $this->ctable->getTable() . '.' . $this->depthColumn, + $this->ctable->getQualifiedDepthColumn() + ); } } From 5f788bc0c01ad7ccbf2e728c4c745196365c8deb Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 1 Apr 2020 23:38:33 +0700 Subject: [PATCH 005/113] implemented PSR-4 for tests --- composer.json | 6 +++++- tests/BaseTestCase.php | 1 - tests/{seeds => }/EntitiesSeeder.php | 2 +- tests/EntityTestCase.php | 1 - tests/{models => }/Page.php | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) rename tests/{seeds => }/EntitiesSeeder.php (97%) rename tests/{models => }/Page.php (81%) diff --git a/composer.json b/composer.json index 5155b08..b34c6bd 100644 --- a/composer.json +++ b/composer.json @@ -19,11 +19,15 @@ "orchestra/testbench": "3.4.12" }, "autoload": { - "classmap": ["tests"], "psr-0": { "Franzose\\ClosureTable": "src/" } }, + "autoload-dev": { + "psr-4": { + "Franzose\\ClosureTable\\Tests\\": "tests/" + } + }, "config": { "preferred-install": "dist" }, diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index d2bf637..4cef077 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -5,7 +5,6 @@ use Franzose\ClosureTable\Contracts\ClosureTableInterface; use Franzose\ClosureTable\Contracts\EntityInterface; use Franzose\ClosureTable\Models\ClosureTable; -use Franzose\ClosureTable\Tests\Seeds\EntitiesSeeder; use Illuminate\Contracts\Console\Kernel; use Illuminate\Foundation\Application; use Orchestra\Testbench\TestCase; diff --git a/tests/seeds/EntitiesSeeder.php b/tests/EntitiesSeeder.php similarity index 97% rename from tests/seeds/EntitiesSeeder.php rename to tests/EntitiesSeeder.php index 3482da6..67ae573 100644 --- a/tests/seeds/EntitiesSeeder.php +++ b/tests/EntitiesSeeder.php @@ -1,5 +1,5 @@ Date: Thu, 2 Apr 2020 13:19:15 +0700 Subject: [PATCH 006/113] wrote tests to check Entity construction --- src/Franzose/ClosureTable/Models/Entity.php | 13 +++-- tests/Entity/ConstructionTests.php | 59 +++++++++++++++++++++ tests/Entity/CustomEntity.php | 19 +++++++ 3 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 tests/Entity/ConstructionTests.php create mode 100644 tests/Entity/CustomEntity.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 7e854c9..c1656e4 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -27,7 +27,7 @@ class Entity extends Eloquent implements EntityInterface * * @var ClosureTable */ - protected $closure = 'Franzose\ClosureTable\Models\ClosureTable'; + protected $closure = ClosureTable::class; /** * Cached "previous" (i.e. before the model is moved) direct ancestor id of this model. @@ -85,7 +85,11 @@ public function __construct(array $attributes = []) $this->fillable(array_merge($this->getFillable(), [$position, $depth])); - if (!isset($attributes[$depth])) { + if (isset($attributes[$position]) && $attributes[$position] < 0) { + $attributes[$position] = 0; + } + + if (!isset($attributes[$depth]) || $attributes[$depth] < 0) { $attributes[$depth] = 0; } @@ -94,7 +98,7 @@ public function __construct(array $attributes = []) // The default class name of the closure table was not changed // so we define and set default closure table name automagically. // This can prevent useless copy paste of closure table models. - if (get_class($this->closure) == 'Franzose\ClosureTable\Models\ClosureTable') { + if (get_class($this->closure) === ClosureTable::class) { $table = $this->getTable() . '_closure'; $this->closure->setTable($table); } @@ -102,11 +106,12 @@ public function __construct(array $attributes = []) parent::__construct($attributes); } - public function newFromBuilder($attributes = array(), $connection = null) + public function newFromBuilder($attributes = [], $connection = null) { $instance = parent::newFromBuilder($attributes); $instance->old_parent_id = $instance->parent_id; $instance->old_position = $instance->position; + $instance->old_real_depth = $instance->real_depth; return $instance; } diff --git a/tests/Entity/ConstructionTests.php b/tests/Entity/ConstructionTests.php new file mode 100644 index 0000000..a7f84af --- /dev/null +++ b/tests/Entity/ConstructionTests.php @@ -0,0 +1,59 @@ +isFillable('position')); + static::assertTrue($entity->isFillable('real_depth')); + } + + public function testPositionShouldBeCorrect() + { + static::assertNull((new Entity())->position); + static::assertEquals(0, (new Entity(['position' => -1]))->position); + } + + public function testRealDepthShouldBeSetToZero() + { + static::assertEquals(0, (new Entity())->real_depth); + static::assertEquals(0, (new Entity(['real_depth' => null]))->real_depth); + static::assertEquals(0, (new Entity(['real_depth' => -1]))->real_depth); + } + + public function testEntityShouldUseDefaultClosureTable() + { + $entity = new CustomEntity(); + $closure = $entity->getClosureTable(); + + static::assertSame(ClosureTable::class, get_class($closure)); + static::assertEquals($entity->getTable() . '_closure', $closure->getTable()); + } + + public function testNewFromBuilder() + { + $entity = new Entity([ + 'parent_id' => 123, + 'position' => 5, + 'real_depth' => 2 + ]); + + $newEntity = $entity->newFromBuilder([ + 'parent_id' => 321, + 'position' => 0, + 'real_depth' => 0 + ]); + + static::assertEquals(321, static::readAttribute($newEntity, 'old_parent_id')); + static::assertEquals(0, static::readAttribute($newEntity, 'old_position')); + static::assertEquals(0, static::readAttribute($newEntity, 'old_real_depth')); + } +} diff --git a/tests/Entity/CustomEntity.php b/tests/Entity/CustomEntity.php new file mode 100644 index 0000000..a974555 --- /dev/null +++ b/tests/Entity/CustomEntity.php @@ -0,0 +1,19 @@ +closure; + } +} From abafc977704869c2ae0b77e2424d94e41afa5874 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 13:27:28 +0700 Subject: [PATCH 007/113] refactored previous value properties to avoid messing them up with the model attributes --- src/Franzose/ClosureTable/Models/Entity.php | 42 ++++++++++----------- tests/EntityTestCase.php | 16 ++++---- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index c1656e4..c46e80e 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -34,21 +34,21 @@ class Entity extends Eloquent implements EntityInterface * * @var int */ - protected $old_parent_id; + private $previousParentId; /** * Cached "previous" (i.e. before the model is moved) model position. * * @var int */ - protected $old_position; + private $previousPosition; /** * Cached "previous" (i.e. before the model is moved) model real depth. * * @var int */ - protected $old_real_depth; + private $previousRealDepth; /** * Indicates if the model should soft delete. @@ -109,9 +109,9 @@ public function __construct(array $attributes = []) public function newFromBuilder($attributes = [], $connection = null) { $instance = parent::newFromBuilder($attributes); - $instance->old_parent_id = $instance->parent_id; - $instance->old_position = $instance->position; - $instance->old_real_depth = $instance->real_depth; + $instance->previousParentId = $instance->parent_id; + $instance->previousPosition = $instance->position; + $instance->previousRealDepth = $instance->real_depth; return $instance; } @@ -135,7 +135,7 @@ public function setParentIdAttribute($value) if ($this->parent_id === $value) { return; } - $this->old_parent_id = $this->parent_id; + $this->previousParentId = $this->parent_id; $this->attributes[$this->getParentIdColumn()] = $value; } @@ -179,7 +179,7 @@ public function setPositionAttribute($value) if ($this->position === $value) { return; } - $this->old_position = $this->position; + $this->previousPosition = $this->position; $this->attributes[$this->getPositionColumn()] = intval($value); } @@ -223,7 +223,7 @@ protected function setRealDepthAttribute($value) if ($this->real_depth === $value) { return; } - $this->old_real_depth = $this->real_depth; + $this->previousRealDepth = $this->real_depth; $this->attributes[$this->getRealDepthColumn()] = intval($value); } @@ -276,8 +276,8 @@ public static function boot() // When entity is created, the appropriate // data will be put into the closure table. static::created(function (Entity $entity) { - $entity->old_parent_id = false; - $entity->old_position = $entity->position; + $entity->previousParentId = false; + $entity->previousPosition = $entity->position; $entity->insertNode(); }); @@ -1266,9 +1266,9 @@ protected function performInsert(EloquentBuilder $query, array $options = []) protected function performUpdate(EloquentBuilder $query, array $options = []) { if (parent::performUpdate($query, $options)) { - if ($this->real_depth != $this->old_real_depth && $this->isMoved === true) { - $action = ($this->real_depth > $this->old_real_depth ? 'increment' : 'decrement'); - $amount = abs($this->real_depth - $this->old_real_depth); + if ($this->real_depth != $this->previousRealDepth && $this->isMoved === true) { + $action = ($this->real_depth > $this->previousRealDepth ? 'increment' : 'decrement'); + $amount = abs($this->real_depth - $this->previousRealDepth); $this->subqueryClosureBy('descendant')->$action($this->getRealDepthColumn(), $amount); } @@ -1321,7 +1321,7 @@ protected function reorderSiblings($parentIdChanged = false) // As the method called twice (before moving and after moving), // first we gather "old" siblings by the old parent id value of the model. if ($parentIdChanged === true) { - $query = $this->siblings(false, $this->old_parent_id); + $query = $this->siblings(false, $this->previousParentId); } else { $query = $this->siblings(); } @@ -1349,18 +1349,18 @@ protected function setupReordering($parentIdChanged) // If the model's parent was changed, firstly we decrement // positions of the 'old' next siblings of the model. if ($parentIdChanged === true) { - $range = $this->old_position; + $range = $this->previousPosition; $action = 'decrement'; } else { // TODO: There's probably a bug here where if you just created an entity and you set it to be // a root (parent_id = null) then it comes in here (while it should have gone in the else) // Reordering within the same ancestor - if ($this->old_parent_id !== false && $this->old_parent_id == $this->parent_id) { - if ($this->position > $this->old_position) { - $range = [$this->old_position, $this->position]; + if ($this->previousParentId !== false && $this->previousParentId == $this->parent_id) { + if ($this->position > $this->previousPosition) { + $range = [$this->previousPosition, $this->position]; $action = 'decrement'; - } else if ($this->position < $this->old_position) { - $range = [$this->position, $this->old_position]; + } else if ($this->position < $this->previousPosition) { + $range = [$this->position, $this->previousPosition]; $action = 'increment'; } } // Ancestor has changed diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 053e6f8..8000486 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -94,16 +94,16 @@ public function testCreateSetsPosition() $entity = new Page(['title' => 'Item 1']); $this->assertEquals(null, $entity->position); - $this->assertEquals(null, $this->readAttribute($entity, 'old_position')); + $this->assertEquals(null, $this->readAttribute($entity, 'previousPosition')); $this->assertEquals(null, $entity->parent_id); - $this->assertEquals(null, $this->readAttribute($entity, 'old_parent_id')); + $this->assertEquals(null, $this->readAttribute($entity, 'previousParentId')); $entity->save(); $this->assertEquals(9, $entity->position); - $this->assertEquals($entity->position, $this->readAttribute($entity, 'old_position')); + $this->assertEquals($entity->position, $this->readAttribute($entity, 'previousPosition')); $this->assertEquals(null, $entity->parent_id); - $this->assertEquals($entity->parent_id, $this->readAttribute($entity, 'old_parent_id')); + $this->assertEquals($entity->parent_id, $this->readAttribute($entity, 'previousParentId')); } /** @@ -167,15 +167,15 @@ public function testSavingLoadedEntityShouldNotTriggerReordering() $this->assertEquals(8, Page::find(9)->position); // Sibling node that shouldn't move - $this->assertEquals($entity1->position, $this->readAttribute($entity1, 'old_position'), 'Position should be the same after a load'); - $this->assertEquals($entity1->parent_id, $this->readAttribute($entity1, 'old_parent_id'), 'Parent should be the same after a load'); + $this->assertEquals($entity1->position, $this->readAttribute($entity1, 'previousPosition'), 'Position should be the same after a load'); + $this->assertEquals($entity1->parent_id, $this->readAttribute($entity1, 'previousParentId'), 'Parent should be the same after a load'); $entity1->title = 'New title'; $entity1->save(); $this->assertEquals(8, Page::find(9)->position, 'Sibling node should not have moved'); - $this->assertEquals($entity1->position, $this->readAttribute($entity1, 'old_position')); - $this->assertEquals($entity1->parent_id, $this->readAttribute($entity1, 'old_parent_id')); + $this->assertEquals($entity1->position, $this->readAttribute($entity1, 'previousPosition')); + $this->assertEquals($entity1->parent_id, $this->readAttribute($entity1, 'previousParentId')); } /** From 3c3c3ee73c0087b6c849e04a1ad9c13c62bdf53c Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 13:27:43 +0700 Subject: [PATCH 008/113] removed redundant tests --- tests/EntityTestCase.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 8000486..7ee5182 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -41,26 +41,6 @@ public function setUp() $this->childrenRelationIndex = $this->entity->getChildrenRelationIndex(); } - public function testPositionIsFillable() - { - $this->assertContains($this->entity->getPositionColumn(), $this->entity->getFillable()); - } - - public function testPositionDefaultValue() - { - $this->assertEquals(0, $this->entity->position); - } - - public function testRealDepthIsFillable() - { - $this->assertContains($this->entity->getRealDepthColumn(), $this->entity->getFillable()); - } - - public function testRealDepthDefaultValue() - { - $this->assertEquals(0, $this->entity->real_depth); - } - public function testIsParent() { $this->assertFalse($this->entity->isParent()); From 3aae756100b351d04566413a300340dd40328413 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 13:28:45 +0700 Subject: [PATCH 009/113] moved isMoved up --- src/Franzose/ClosureTable/Models/Entity.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index c46e80e..8d83263 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -51,18 +51,18 @@ class Entity extends Eloquent implements EntityInterface private $previousRealDepth; /** - * Indicates if the model should soft delete. + * Indicates if the model is being moved to another ancestor. * * @var bool */ - protected $softDelete = true; + private $isMoved = false; /** - * Indicates if the model is being moved to another ancestor. + * Indicates if the model should soft delete. * * @var bool */ - protected $isMoved = false; + protected $softDelete = true; /** * Indicates if the model should be timestamped. From 9276a0a45741f1aa6bedb4f209977e27757c2708 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 13:31:21 +0700 Subject: [PATCH 010/113] refactored intval's to type casts --- src/Franzose/ClosureTable/Models/Entity.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 8d83263..65f3f61 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -180,7 +180,7 @@ public function setPositionAttribute($value) return; } $this->previousPosition = $this->position; - $this->attributes[$this->getPositionColumn()] = intval($value); + $this->attributes[$this->getPositionColumn()] = (int) $value; } /** @@ -224,7 +224,7 @@ protected function setRealDepthAttribute($value) return; } $this->previousRealDepth = $this->real_depth; - $this->attributes[$this->getRealDepthColumn()] = intval($value); + $this->attributes[$this->getRealDepthColumn()] = (int) $value; } /** From 972b9b7ac60b6639bf3c81188e8f7575d30c2141 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 13:37:28 +0700 Subject: [PATCH 011/113] refactored is_null to a simple check for null --- src/Franzose/ClosureTable/Models/Entity.php | 30 +++++++++------------ 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 65f3f61..ba80668 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -306,11 +306,7 @@ public function isParent() */ public function isRoot() { - if (!$this->exists) { - return false; - } - - return is_null($this->parent_id); + return $this->exists === true && $this->parent_id === null; } /** @@ -513,7 +509,7 @@ protected function children($position = null, $order = 'asc') { $query = $this->queryByParentId(); - if (!is_null($position)) { + if ($position !== null) { if (is_array($position)) { $query->buildWherePosition($this->getPositionColumn(), $position); } else { @@ -684,7 +680,7 @@ protected function getLastChildPosition() { $lastChild = $this->getLastChild([$this->getPositionColumn()]); - return (is_null($lastChild) ? 0 : $lastChild->position); + return $lastChild === null ? 0 : $lastChild->position; } /** @@ -698,7 +694,7 @@ protected function getLastChildPosition() public function addChild(EntityInterface $child, $position = null, $returnChild = false) { if ($this->exists) { - if (is_null($position)) { + if ($position === null) { $position = $this->getNextAfterLastPosition($this->getKey()); } @@ -768,7 +764,7 @@ public function removeChild($position = null, $forceDelete = false) */ public function removeChildren($from, $to = null, $forceDelete = false) { - if (!is_numeric($from) || (!is_null($to) && !is_numeric($to))) { + if (!is_numeric($from) || ($to !== null && !is_numeric($to))) { throw new \InvalidArgumentException('`from` and `to` are the position boundaries. They must be of type int.'); } @@ -1023,7 +1019,7 @@ public function getSiblingsRange($from, $to = null, array $columns = ['*']) public function addSibling(EntityInterface $sibling, $position = null, $returnSibling = false) { if ($this->exists) { - if (is_null($position)) { + if ($position === null) { $position = $this->getNextAfterLastPosition(); } @@ -1043,7 +1039,7 @@ public function addSibling(EntityInterface $sibling, $position = null, $returnSi public function addSiblings(array $siblings, $from = null) { if ($this->exists) { - if (is_null($from)) { + if ($from === null) { $from = $this->getNextAfterLastPosition(); } @@ -1173,7 +1169,7 @@ public static function createFromArray(array $tree, EntityInterface $parent = nu $entity->parent_id = $parent ? $parent->getKey() : null; $entity->save(); - if (!is_null($children)) { + if ($children !== null) { $children = static::createFromArray($children, $entity); $entity->setRelation($childrenRelationIndex, $children); $entity->addChildren($children->all()); @@ -1197,11 +1193,11 @@ public function moveTo($position, $ancestor = null) { $parentId = (!$ancestor instanceof EntityInterface ? $ancestor : $ancestor->getKey()); - if ($this->parent_id == $parentId && !is_null($this->parent_id)) { + if ($this->parent_id === $parentId && $this->parent_id !== null) { return $this; } - if ($this->getKey() == $parentId) { + if ($this->getKey() === $parentId) { throw new \InvalidArgumentException('Target entity is equal to the sender.'); } @@ -1227,7 +1223,7 @@ public function moveTo($position, $ancestor = null) protected function getNewRealDepth($ancestor) { if (!$ancestor instanceof EntityInterface) { - if (is_null($ancestor)) { + if ($ancestor === null) { return 0; } else { return static::find($ancestor)->real_depth + 1; @@ -1303,7 +1299,7 @@ public function getLastPosition($parentId = false) ->orderBy($positionColumn, 'desc') ->first(); - return !is_null($entity) ? (int)$entity->position : null; + return $entity !== null ? (int)$entity->position : null; } /** @@ -1398,7 +1394,7 @@ protected function insertNode() protected function moveNode() { if ($this->exists) { - if (is_null($this->closure->ancestor)) { + if ($this->closure->ancestor === null) { $primaryKey = $this->getKey(); $this->closure->ancestor = $primaryKey; $this->closure->descendant = $primaryKey; From e6a6bb9e35ac42a669e8b62adb1de6957b23d262 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 14:39:20 +0700 Subject: [PATCH 012/113] wrote tests for isParent(), isRoot() and getParent() methods --- src/Franzose/ClosureTable/Models/Entity.php | 10 ++++-- tests/Entity/ParentRootTests.php | 36 +++++++++++++++++++++ tests/EntityTestCase.php | 20 ------------ 3 files changed, 43 insertions(+), 23 deletions(-) create mode 100644 tests/Entity/ParentRootTests.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index ba80668..3b1fbae 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -296,7 +296,7 @@ public static function boot() */ public function isParent() { - return $this->hasChildren(); + return $this->exists === true && $this->hasChildren(); } /** @@ -313,11 +313,15 @@ public function isRoot() * Retrieves direct ancestor of a model. * * @param array $columns - * @return Entity + * @return Entity|null */ public function getParent(array $columns = ['*']) { - return static::find($this->parent_id, $columns); + if ($this->exists === false) { + return null; + } + + return $this->find($this->parent_id, $columns); } /** diff --git a/tests/Entity/ParentRootTests.php b/tests/Entity/ParentRootTests.php new file mode 100644 index 0000000..2ed59e4 --- /dev/null +++ b/tests/Entity/ParentRootTests.php @@ -0,0 +1,36 @@ +isParent()); + static::assertFalse((new Entity())->isRoot()); + } + + public function testExistingInstance() + { + static::assertTrue(Entity::find(9)->isParent()); + static::assertFalse(Entity::find(1)->isParent()); + static::assertTrue(Entity::find(1)->isRoot()); + static::assertFalse(Entity::find(10)->isRoot()); + } + + public function testGetParent() + { + $parent = Entity::find(10)->getParent(); + + static::assertInstanceOf(Entity::class, $parent); + static::assertEquals(9, $parent->getKey()); + static::assertNull(Entity::find(1)->getParent()); + static::assertNull((new Entity())->getParent()); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 7ee5182..1dcf709 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -41,17 +41,6 @@ public function setUp() $this->childrenRelationIndex = $this->entity->getChildrenRelationIndex(); } - public function testIsParent() - { - $this->assertFalse($this->entity->isParent()); - } - - public function testIsRoot() - { - $this->assertFalse($this->entity->isRoot()); - $this->assertTrue(Entity::find(1)->isRoot()); - } - public function testCreate() { DB::statement("SET foreign_key_checks=0"); @@ -192,15 +181,6 @@ public function testClampPosition() $this->assertEquals($ancestor->countChildren(), $entity->position); } - public function testGetParent() - { - $entity = Entity::find(10); - $parent = $entity->getParent(); - - $this->assertInstanceOf('Franzose\ClosureTable\Models\Entity', $parent); - $this->assertEquals(9, $parent->getKey()); - } - public function testGetParentAfterMovingToAnAncestor() { $entity = Entity::find(10); From a1176b2c34613c6f665a79db2fba1fe0ef7f120a Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 15:13:48 +0700 Subject: [PATCH 013/113] simplified methods --- src/Franzose/ClosureTable/Models/Entity.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 3b1fbae..2941e98 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -296,7 +296,7 @@ public static function boot() */ public function isParent() { - return $this->exists === true && $this->hasChildren(); + return $this->exists && $this->hasChildren(); } /** @@ -306,7 +306,7 @@ public function isParent() */ public function isRoot() { - return $this->exists === true && $this->parent_id === null; + return $this->exists && $this->parent_id === null; } /** @@ -317,11 +317,7 @@ public function isRoot() */ public function getParent(array $columns = ['*']) { - if ($this->exists === false) { - return null; - } - - return $this->find($this->parent_id, $columns); + return $this->exists ? $this->find($this->parent_id, $columns) : null; } /** From 2503da967c1f9909b5e9c468b9db0e06596c984f Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 15:24:28 +0700 Subject: [PATCH 014/113] moved tests of *ancestor* methods to a separate class --- src/Franzose/ClosureTable/Models/Entity.php | 2 +- tests/Entity/AncestorTests.php | 52 +++++++++++++++++++++ tests/EntityTestCase.php | 39 ---------------- 3 files changed, 53 insertions(+), 40 deletions(-) create mode 100644 tests/Entity/AncestorTests.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 2941e98..06ef171 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -439,7 +439,7 @@ public function countAncestors() */ public function hasAncestors() { - return !!$this->countAncestors(); + return (bool) $this->countAncestors(); } /** diff --git a/tests/Entity/AncestorTests.php b/tests/Entity/AncestorTests.php new file mode 100644 index 0000000..a14a219 --- /dev/null +++ b/tests/Entity/AncestorTests.php @@ -0,0 +1,52 @@ +getAncestors()); + static::assertCount(0, Entity::find(1)->getAncestors()); + } + + public function testGetAncestorsShouldNotBeEmpty() + { + $entity = Entity::find(12); + + $ancestors = $entity->getAncestors(); + + static::assertInstanceOf(Collection::class, $ancestors); + static::assertCount(3, $ancestors); + static::assertContainsOnlyInstancesOf(Entity::class, $ancestors); + $this->assertArrayValuesEquals($ancestors->modelKeys(), [9, 10, 11]); + } + + public function testAncestorsWhere() + { + $entity = Entity::find(12); + + $ancestors = $entity->getAncestorsWhere('real_depth', '<', 2); + + static::assertInstanceOf(Collection::class, $ancestors); + static::assertCount(2, $ancestors); + static::assertContainsOnlyInstancesOf(Entity::class, $ancestors); + $this->assertArrayValuesEquals($ancestors->modelKeys(), [9, 10]); + } + + public function testCountAncestors() + { + static::assertEquals(0, Entity::find(1)->countAncestors()); + static::assertEquals(3, Entity::find(12)->countAncestors()); + } + + public function testHasAncestors() + { + static::assertFalse(Entity::find(1)->hasAncestors()); + static::assertTrue(Entity::find(12)->hasAncestors()); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 1dcf709..fe0aeef 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -191,45 +191,6 @@ public function testGetParentAfterMovingToAnAncestor() $this->assertEquals(15, $parent->getKey()); } - public function testGetAncestors() - { - $entity = Entity::find(12); - $ancestors = $entity->getAncestors(); - - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $ancestors); - $this->assertCount(3, $ancestors); - $this->assertArrayValuesEquals($ancestors->modelKeys(), [9, 10, 11]); - } - - public function testGetAncestorsWhere() - { - $entity = Entity::find(12); - $ancestors = $entity->getAncestorsWhere('excerpt', '=', ''); - - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $ancestors); - $this->assertCount(0, $ancestors); - - $ancestors = $entity->getAncestorsWhere($this->entity->getPositionColumn(), '=', 0); - $this->assertCount(2, $ancestors); - $this->assertArrayValuesEquals($ancestors->modelKeys(), [10, 11]); - } - - public function testCountAncestors() - { - $entity = Entity::find(12); - $ancestors = $entity->countAncestors(); - - $this->assertEquals(3, $ancestors); - } - - public function testHasAncestors() - { - $entity = Entity::find(12); - $hasAncestors = $entity->hasAncestors(); - - $this->assertTrue($hasAncestors); - } - public function testGetDescendants() { $entity = Entity::find(9); From 727e0a47b424da85df4fa04f1263122018926585 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 15:35:20 +0700 Subject: [PATCH 015/113] moved tests of *descendant* methods to a separate class --- src/Franzose/ClosureTable/Models/Entity.php | 2 +- tests/Entity/DescendantTests.php | 46 +++++++++++++++++++++ tests/EntityTestCase.php | 35 ---------------- 3 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 tests/Entity/DescendantTests.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 06ef171..cbdfa17 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -495,7 +495,7 @@ public function countDescendants() */ public function hasDescendants() { - return !!$this->countDescendants(); + return (bool) $this->countDescendants(); } /** diff --git a/tests/Entity/DescendantTests.php b/tests/Entity/DescendantTests.php new file mode 100644 index 0000000..7a07229 --- /dev/null +++ b/tests/Entity/DescendantTests.php @@ -0,0 +1,46 @@ +getDescendants()); + static::assertCount(0, Entity::find(1)->getDescendants()); + } + + public function testGetDescendants() + { + $entity = Entity::find(9); + $descendants = $entity->getDescendants(); + + static::assertInstanceOf(Collection::class, $descendants); + static::assertCount(6, $descendants); + $this->assertArrayValuesEquals($descendants->modelKeys(), [10, 11, 12, 13, 14, 15]); + } + + public function testGetDescendantsWhere() + { + $descendants = Entity::find(9)->getDescendantsWhere('position', '=', 1); + + static::assertCount(1, $descendants); + $this->assertArrayValuesEquals($descendants->modelKeys(), [13]); + } + + public function testCountDescendants() + { + static::assertEquals(6, Entity::find(9)->countDescendants()); + static::assertEquals(0, Entity::find(1)->countDescendants()); + } + + public function testHasDescendants() + { + static::assertTrue(Entity::find(9)->hasDescendants()); + static::assertFalse(Entity::find(1)->hasDescendants()); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index fe0aeef..026a608 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -191,41 +191,6 @@ public function testGetParentAfterMovingToAnAncestor() $this->assertEquals(15, $parent->getKey()); } - public function testGetDescendants() - { - $entity = Entity::find(9); - $descendants = $entity->getDescendants(); - - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $descendants); - $this->assertCount(6, $descendants); - $this->assertArrayValuesEquals($descendants->modelKeys(), [10, 11, 12, 13, 14, 15]); - } - - public function testGetDescendantsWhere() - { - $entity = Entity::find(9); - - $descendants = $entity->getDescendantsWhere($this->entity->getPositionColumn(), '=', 1); - $this->assertCount(1, $descendants); - $this->assertArrayValuesEquals($descendants->modelKeys(), [13]); - } - - public function testCountDescendants() - { - $entity = Entity::find(9); - $descendants = $entity->countDescendants(); - - $this->assertEquals(6, $descendants); - } - - public function testHasDescendants() - { - $entity = Entity::find(9); - $hasDescendants = $entity->hasDescendants(); - - $this->assertTrue($hasDescendants); - } - public function testGetChildren() { $entity = Entity::find(9); From 9c2ff8ffc2ae63387003f3fa215fbf42074d575a Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 2 Apr 2020 15:44:41 +0700 Subject: [PATCH 016/113] moved tests of the getRoot() method --- tests/Entity/ParentRootTests.php | 11 +++++++++++ tests/EntityTestCase.php | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Entity/ParentRootTests.php b/tests/Entity/ParentRootTests.php index 2ed59e4..e2e3734 100644 --- a/tests/Entity/ParentRootTests.php +++ b/tests/Entity/ParentRootTests.php @@ -33,4 +33,15 @@ public function testGetParent() static::assertNull(Entity::find(1)->getParent()); static::assertNull((new Entity())->getParent()); } + + public function testGetRoots() + { + $roots = Entity::getRoots(); + + static::assertCount(9, $roots); + + foreach ($roots as $idx => $root) { + static::assertEquals($idx + 1, $roots->get($idx)->getKey()); + } + } } diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 026a608..bd5589b 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -505,17 +505,6 @@ public function testAddSiblingsFromPosition() $this->assertEquals(19, $siblings[3]->getKey()); } - public function testGetRoots() - { - $roots = Entity::getRoots(); - - $this->assertCount(9, $roots); - - foreach ($roots as $idx => $root) { - $this->assertEquals($idx + 1, $roots->get($idx)->getKey()); - } - } - public function testGetTree() { $tree = Entity::getTree(); From 554fc3cbe7c67b5c29f0f834ea13ab1e1fd087c5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 09:47:20 +0700 Subject: [PATCH 017/113] added convenient methods to Collection --- .../ClosureTable/Extensions/Collection.php | 68 +++++++++++++++--- tests/CollectionTestCase.php | 71 +++++++++++++++---- 2 files changed, 118 insertions(+), 21 deletions(-) diff --git a/src/Franzose/ClosureTable/Extensions/Collection.php b/src/Franzose/ClosureTable/Extensions/Collection.php index 50c8e59..d20a9ff 100644 --- a/src/Franzose/ClosureTable/Extensions/Collection.php +++ b/src/Franzose/ClosureTable/Extensions/Collection.php @@ -7,10 +7,66 @@ /** * Extended Collection class. Provides some useful methods. * + * @method Entity|null get($key, $default = null) * @package Franzose\ClosureTable\Extensions */ class Collection extends EloquentCollection { + /** + * Returns a child node at the given position. + * + * @param int $position + * + * @return Entity|null + */ + public function getChildAt($position) + { + return $this->filter(static function (Entity $entity) use ($position) { + return $entity->position === $position; + })->first(); + } + + /** + * Returns the first child node. + * + * @return Entity|null + */ + public function getFirstChild() + { + return $this->getChildAt(0); + } + + /** + * Returns the last child node. + * + * @return Entity|null + */ + public function getLastChild() + { + return $this->sortByDesc(static function (Entity $entity) { + return $entity->position; + })->first(); + } + + /** + * Filters the collection by the given positions. + * + * @param int $from + * @param int|null $to + * + * @return Collection + */ + public function getRange($from, $to = null) + { + return $this->filter(static function (Entity $entity) use ($from, $to) { + if ($to === null) { + return $entity->position >= $from; + } + + return $entity->position >= $from && $entity->position <= $to; + }); + } + /** * Retrieves children relation. * @@ -23,11 +79,7 @@ public function getChildrenOf($position) return new static(); } - /** @var Entity $item */ - $item = $this->get($position); - $relation = $item->getChildrenRelationIndex(); - - return $item->getRelation($relation); + return $this->getChildAt($position)->getChildrenFromRelation(); } /** @@ -38,11 +90,9 @@ public function getChildrenOf($position) */ public function hasChildren($position) { - /** @var Entity $item */ - $item = $this->get($position); - $relation = $item->getChildrenRelationIndex(); + $item = $this->getChildAt($position); - return array_key_exists($relation, $item->getRelations()); + return $item !== null && $item->childrenLoaded(); } /** diff --git a/tests/CollectionTestCase.php b/tests/CollectionTestCase.php index 5b9e69d..6d7cb98 100644 --- a/tests/CollectionTestCase.php +++ b/tests/CollectionTestCase.php @@ -6,6 +6,53 @@ class CollectionTestCase extends BaseTestCase { + public function testGetChildAt() + { + $collection = new Collection([ + new Page(['position' => 0]), + new Page(['position' => 1]), + new Page(['position' => 2]), + ]); + + static::assertEquals(1, $collection->getChildAt(1)->position); + static::assertNull($collection->getChildAt(999)); + } + + public function testGetFirstChild() + { + $collection = new Collection([ + new Page(['position' => 0]), + new Page(['position' => 1]), + ]); + + static::assertEquals(0, $collection->getFirstChild()->position); + static::assertNull((new Collection())->getFirstChild()); + } + + public function testGetLastChild() + { + $collection = new Collection([ + new Page(['position' => 0]), + new Page(['position' => 1]), + ]); + + static::assertEquals(1, $collection->getLastChild()->position); + static::assertNull((new Collection())->getLastChild()); + } + + public function testGetRange() + { + $collection = new Collection([ + new Page(['position' => 0]), + new Page(['position' => 1]), + new Page(['position' => 2]), + new Page(['position' => 3]), + ]); + + static::assertEquals([2, 3], $collection->getRange(2)->pluck('position')->toArray()); + static::assertEquals([1, 2, 3], $collection->getRange(1, 3)->pluck('position')->toArray()); + } + public function testToTree() { $rootEntity = new Entity(); @@ -47,19 +94,19 @@ public function testToTree() public function testHasChildren() { - $entity = new Entity(); + $entity = new Page(['position' => 0]); $childrenRelationIndex = $entity->getChildrenRelationIndex(); $collection = new Collection([ $entity, - new Entity(), - new Entity() + new Page(['position' => 1]), + new Page(['position' => 2]) ]); $children = new Collection([ - new Entity(), - new Entity(), - new Entity() + new Page(['position' => 0]), + new Page(['position' => 1]), + new Page(['position' => 2]) ]); /** @var Entity $firstEntity */ @@ -71,19 +118,19 @@ public function testHasChildren() public function testGetChildrenOf() { - $entity = new Entity(); + $entity = new Page(['position' => 0]); $childrenRelationIndex = $entity->getChildrenRelationIndex(); $collection = new Collection([ $entity, - new Entity(), - new Entity() + new Page(['position' => 1]), + new Page(['position' => 2]) ]); $expected = new Collection([ - new Entity(), - new Entity(), - new Entity() + new Page(['position' => 0]), + new Page(['position' => 1]), + new Page(['position' => 2]) ]); /** @var Entity $firstEntity */ From 31e10e555e5d4ab37f1278930c040467c51563c9 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 13:32:21 +0700 Subject: [PATCH 018/113] added and used a bunch of useful scopes, refactored Collection, rewrote tests --- .../ClosureTable/Extensions/Collection.php | 16 +- src/Franzose/ClosureTable/Models/Entity.php | 149 +++++++++++------- tests/CollectionTestCase.php | 76 ++++----- tests/Entity/ChildQueryTests.php | 80 ++++++++++ tests/EntityTestCase.php | 63 -------- tests/Page.php | 2 +- 6 files changed, 211 insertions(+), 175 deletions(-) create mode 100644 tests/Entity/ChildQueryTests.php diff --git a/src/Franzose/ClosureTable/Extensions/Collection.php b/src/Franzose/ClosureTable/Extensions/Collection.php index d20a9ff..0fb69c4 100644 --- a/src/Franzose/ClosureTable/Extensions/Collection.php +++ b/src/Franzose/ClosureTable/Extensions/Collection.php @@ -79,7 +79,7 @@ public function getChildrenOf($position) return new static(); } - return $this->getChildAt($position)->getChildrenFromRelation(); + return $this->getChildAt($position)->children; } /** @@ -92,7 +92,7 @@ public function hasChildren($position) { $item = $this->getChildAt($position); - return $item !== null && $item->childrenLoaded(); + return $item !== null && $item->children->count() > 0; } /** @@ -110,26 +110,24 @@ public function toTree() /** * Performs actual tree building. * - * @param array $items + * @param Entity[] $items * @return array */ - protected function makeTree(array &$items) + protected function makeTree(array $items) { + /** @var Entity[] $result */ $result = []; $tops = []; - /** - * @var Entity $item - */ foreach ($items as $item) { $result[$item->getKey()] = $item; } foreach ($items as $item) { - $parentId = $item->{$item->getParentIdColumn()}; + $parentId = $item->parent_id; if (array_key_exists($parentId, $result)) { - $result[$parentId]->appendRelation($item->getChildrenRelationIndex(), $item); + $result[$parentId]->children->add($item); } else { $tops[] = $item; } diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index cbdfa17..eb9d451 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -1,11 +1,13 @@ queryByParentId(); @@ -541,21 +548,26 @@ protected function queryByParentId($id = null) return $this->where($this->getParentIdColumn(), '=', $id); } + /** + * Returns one-to-many relationship to child nodes. + * + * @return HasMany + */ + public function children() + { + return $this->hasMany(get_class($this), $this->getParentIdColumn()); + } + /** * Retrieves all children of a model. * * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public function getChildren(array $columns = ['*']) { - if ($this->hasChildrenRelation()) { - $result = $this->getRelation($this->getChildrenRelationIndex()); - } else { - $result = $this->children()->get($columns); - } - - return $result; + return $this->children()->get($columns); } /** @@ -565,13 +577,7 @@ public function getChildren(array $columns = ['*']) */ public function countChildren() { - if ($this->hasChildrenRelation()) { - $result = $this->getRelation($this->getChildrenRelationIndex())->count(); - } else { - $result = $this->queryByParentId()->count(); - } - - return $result; + return $this->children()->count(); } /** @@ -581,53 +587,53 @@ public function countChildren() */ public function hasChildren() { - return !!$this->countChildren(); + return (bool) $this->countChildren(); } /** * Indicates whether a model has children as a relation. * * @return bool + * @deprecated from 6.0 */ public function hasChildrenRelation() { - return array_key_exists($this->getChildrenRelationIndex(), $this->getRelations()); + return $this->relationLoaded($this->getChildrenRelationIndex()); } /** - * Pushes a new item to a relation. + * Returns relationship to a child at the given position. * - * @param $relation - * @param $value - * @return $this + * @param Builder $builder + * @param int $position + * + * @return HasMany */ - public function appendRelation($relation, $value) + public function scopeChildAt($builder, $position) { - if (!array_key_exists($relation, $this->getRelations())) { - $this->setRelation($relation, new Collection([$value])); - } else { - $this->getRelation($relation)->add($value); - } - - return $this; + return $this->children()->where($this->getPositionColumn(), '=', $position); } /** * Retrieves a child with given position. * - * @param $position + * @param int $position * @param array $columns * @return Entity */ public function getChildAt($position, array $columns = ['*']) { - if ($this->hasChildrenRelation()) { - $result = $this->getRelation($this->getChildrenRelationIndex())->get($position); - } else { - $result = $this->children($position)->first($columns); - } + return $this->childAt($position)->first($columns); + } - return $result; + /** + * Returns relationship to the first child node. + * + * @return HasMany + */ + public function scopeFirstChild() + { + return $this->childAt(0); } /** @@ -641,6 +647,16 @@ public function getFirstChild(array $columns = ['*']) return $this->getChildAt(0, $columns); } + /** + * Returns relationship to the last child node. + * + * @return HasMany + */ + public function scopeLastChild() + { + return $this->children()->orderBy($this->getPositionColumn(), 'desc'); + } + /** * Retrieves the last child. * @@ -649,13 +665,28 @@ public function getFirstChild(array $columns = ['*']) */ public function getLastChild(array $columns = ['*']) { - if ($this->hasChildrenRelation()) { - $result = $this->getRelation($this->getChildrenRelationIndex())->last(); - } else { - $result = $this->children(static::QUERY_LAST)->first($columns); + return $this->lastChild()->first($columns); + } + + /** + * Returns relationship to child nodes in the range of the given positions. + * + * @param Builder $builder + * @param int $from + * @param int|null $to + * + * @return HasMany + */ + public function scopeChildrenRange($builder, $from, $to = null) + { + $position = $this->getPositionColumn(); + $query = $this->children()->where($position, '>=', $from); + + if ($to !== null) { + $query->where($position, '<=', $to); } - return $result; + return $query; } /** @@ -668,7 +699,7 @@ public function getLastChild(array $columns = ['*']) */ public function getChildrenRange($from, $to = null, array $columns = ['*']) { - return $this->children([$from, $to])->get($columns); + return $this->childrenRange($from, $to)->get($columns); } /** @@ -747,7 +778,7 @@ public function removeChild($position = null, $forceDelete = false) if ($this->exists) { $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - $this->children($position)->$action(); + $this->childrenQuery($position)->$action(); } return $this; @@ -771,7 +802,7 @@ public function removeChildren($from, $to = null, $forceDelete = false) if ($this->exists) { $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - $this->children([$from, $to])->$action(); + $this->childrenQuery([$from, $to])->$action(); } return $this; @@ -839,7 +870,8 @@ protected function siblings($direction = '', $parentId = false, $order = 'asc') * Retrives all siblings of a model. * * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public function getSiblings(array $columns = ['*']) { @@ -870,7 +902,8 @@ public function hasSiblings() * Retrieves neighbors (immediate previous and immediate next models) of a model. * * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public function getNeighbors(array $columns = ['*']) { @@ -926,7 +959,8 @@ public function getPrevSibling(array $columns = ['*']) * Retrieves all previous siblings of a model. * * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public function getPrevSiblings(array $columns = ['*']) { @@ -968,7 +1002,8 @@ public function getNextSibling(array $columns = ['*']) * Retrieves all next siblings of a model. * * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public function getNextSiblings(array $columns = ['*']) { @@ -1060,7 +1095,8 @@ public function addSiblings(array $siblings, $from = null) * Retrieves root (with no ancestors) models. * * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public static function getRoots(array $columns = ['*']) { @@ -1098,7 +1134,8 @@ protected function prepareTreeQueryColumns(array $columns) * Retrieves entire tree. * * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public static function getTree(array $columns = ['*']) { @@ -1118,7 +1155,8 @@ public static function getTree(array $columns = ['*']) * @param mixed $operator * @param mixed $value * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public static function getTreeWhere($column, $operator = null, $value = null, array $columns = ['*']) { @@ -1133,9 +1171,11 @@ public static function getTreeWhere($column, $operator = null, $value = null, ar /** * Retrieves tree with any conditions using QueryBuilder + * * @param EloquentBuilder $query * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public static function getTreeByQuery(EloquentBuilder $query, array $columns = ['*']) { @@ -1152,7 +1192,8 @@ public static function getTreeByQuery(EloquentBuilder $query, array $columns = [ * * @param array $tree * @param \Franzose\ClosureTable\Contracts\EntityInterface $parent - * @return \Franzose\ClosureTable\Extensions\Collection + * + * @return Collection */ public static function createFromArray(array $tree, EntityInterface $parent = null) { diff --git a/tests/CollectionTestCase.php b/tests/CollectionTestCase.php index 6d7cb98..2e748b4 100644 --- a/tests/CollectionTestCase.php +++ b/tests/CollectionTestCase.php @@ -53,46 +53,7 @@ public function testGetRange() static::assertEquals([1, 2, 3], $collection->getRange(1, 3)->pluck('position')->toArray()); } - public function testToTree() - { - $rootEntity = new Entity(); - $rootEntity->save(); - $childEntity = (new Entity())->moveTo(0, $rootEntity); - $grandEntity = (new Entity())->moveTo(0, $childEntity); - - $childrenRelationIndex = $rootEntity->getChildrenRelationIndex(); - - $tree = (new Collection([$rootEntity, $childEntity, $grandEntity]))->toTree(); - - /** @var Entity $rootItem */ - $rootItem = $tree->get(0); - - static::assertArrayHasKey($childrenRelationIndex, $rootItem->getRelations()); - - /** @var Collection $children */ - $children = $rootItem->getRelation($childrenRelationIndex); - - static::assertCount(1, $children); - - /** @var Entity $childItem */ - $childItem = $children->get(0); - - static::assertEquals($childEntity->getKey(), $childItem->getKey()); - static::assertArrayHasKey($childrenRelationIndex, $childItem->getRelations()); - - /** @var Collection $grandItems */ - $grandItems = $childItem->getRelation($childrenRelationIndex); - - static::assertCount(1, $grandItems); - - /** @var Entity $grandItem */ - $grandItem = $grandItems->get(0); - - static::assertEquals($grandEntity->getKey(), $grandItem->getKey()); - static::assertArrayNotHasKey($childrenRelationIndex, $grandItem->getRelations()); - } - - public function testHasChildren() + public function testGetChildrenOf() { $entity = new Page(['position' => 0]); $childrenRelationIndex = $entity->getChildrenRelationIndex(); @@ -103,7 +64,7 @@ public function testHasChildren() new Page(['position' => 2]) ]); - $children = new Collection([ + $expected = new Collection([ new Page(['position' => 0]), new Page(['position' => 1]), new Page(['position' => 2]) @@ -111,12 +72,14 @@ public function testHasChildren() /** @var Entity $firstEntity */ $firstEntity = $collection->get(0); - $firstEntity->setRelation($childrenRelationIndex, $children); + $firstEntity->setRelation($childrenRelationIndex, $expected); - static::assertTrue($collection->hasChildren(0)); + $actual = $collection->getChildrenOf(0); + + static::assertSame($expected, $actual); } - public function testGetChildrenOf() + public function testHasChildren() { $entity = new Page(['position' => 0]); $childrenRelationIndex = $entity->getChildrenRelationIndex(); @@ -127,7 +90,7 @@ public function testGetChildrenOf() new Page(['position' => 2]) ]); - $expected = new Collection([ + $children = new Collection([ new Page(['position' => 0]), new Page(['position' => 1]), new Page(['position' => 2]) @@ -135,10 +98,27 @@ public function testGetChildrenOf() /** @var Entity $firstEntity */ $firstEntity = $collection->get(0); - $firstEntity->setRelation($childrenRelationIndex, $expected); + $firstEntity->setRelation($childrenRelationIndex, $children); - $actual = $collection->getChildrenOf(0); + static::assertTrue($collection->hasChildren(0)); + } - static::assertSame($expected, $actual); + public function testToTree() + { + $root = new Page(['id' => 1]); + $child = new Page(['id' => 2, 'parent_id' => 1]); + $grandChild = new Page(['id' => 3, 'parent_id' => 2]); + + $tree = (new Collection([$root, $child, $grandChild]))->toTree(); + + static::assertCount(1, $tree); + + $children = $tree->get(0)->children; + static::assertCount(1, $children); + static::assertSame($child, $children->get(0)); + + $grandChildren = $children->get(0)->children; + static::assertCount(1, $grandChildren); + static::assertSame($grandChild, $grandChildren->get(0)); } } diff --git a/tests/Entity/ChildQueryTests.php b/tests/Entity/ChildQueryTests.php new file mode 100644 index 0000000..d1c9824 --- /dev/null +++ b/tests/Entity/ChildQueryTests.php @@ -0,0 +1,80 @@ +getChildren()); + static::assertEquals(0, $entity->countChildren()); + static::assertFalse($entity->hasChildren()); + } + + public function testGetChildren() + { + static::assertCount(4, Entity::find(9)->getChildren()); + } + + public function testCountChildren() + { + static::assertEquals(4, Entity::find(9)->countChildren()); + } + + public function testHasChildren() + { + static::assertFalse(Entity::find(1)->hasChildren()); + static::assertTrue(Entity::find(9)->hasChildren()); + } + + public function testGetChildAt() + { + $child = Entity::find(9)->getChildAt(1); + + static::assertInstanceOf(Entity::class, $child); + static::assertEquals(13, $child->getKey()); + } + + public function testGetFirstChild() + { + $entity = Entity::find(9); + + $child = $entity->getFirstChild(); + + static::assertInstanceOf(Entity::class, $child); + static::assertEquals(10, $child->getKey()); + } + + public function testGetLastChild() + { + $entity = Entity::find(9); + $child = $entity->getLastChild(); + + static::assertInstanceOf(Entity::class, $child); + static::assertEquals(15, $child->getKey()); + } + + public function testGetChildrenRange() + { + $entity = Entity::find(9); + $children = $entity->getChildrenRange(0, 2); + + static::assertInstanceOf(Collection::class, $children); + static::assertCount(3, $children); + static::assertEquals(0, $children[0]->position); + static::assertEquals(1, $children[1]->position); + static::assertEquals(2, $children[2]->position); + + $children = $entity->getChildrenRange(2); + + static::assertCount(2, $children); + static::assertEquals(2, $children[0]->position); + static::assertEquals(3, $children[1]->position); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index bd5589b..69caacb 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -191,69 +191,6 @@ public function testGetParentAfterMovingToAnAncestor() $this->assertEquals(15, $parent->getKey()); } - public function testGetChildren() - { - $entity = Entity::find(9); - $children = $entity->getChildren(); - - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $children); - $this->assertCount(4, $children); - $this->assertArrayValuesEquals($children->modelKeys(), [10, 13, 14, 15]); - } - - public function testCountChildren() - { - $entity = Entity::find(9); - $children = $entity->countChildren(); - - $this->assertEquals(4, $children); - } - - public function testGetChildAt() - { - $entity = Entity::find(9); - $child = $entity->getChildAt(2); - - $this->assertInstanceOf('Franzose\ClosureTable\Models\Entity', $child); - $this->assertEquals(2, $child->position); - } - - public function testGetFirstChild() - { - $entity = Entity::find(9); - $child = $entity->getFirstChild(); - - $this->assertInstanceOf('Franzose\ClosureTable\Models\Entity', $child); - $this->assertEquals(0, $child->position); - } - - public function testGetLastChild() - { - $entity = Entity::find(9); - $child = $entity->getLastChild(); - - $this->assertInstanceOf('Franzose\ClosureTable\Models\Entity', $child); - $this->assertEquals(3, $child->position); - } - - public function testGetChildrenRange() - { - $entity = Entity::find(9); - $children = $entity->getChildrenRange(0, 2); - - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $children); - $this->assertCount(3, $children); - $this->assertEquals(0, $children[0]->position); - $this->assertEquals(1, $children[1]->position); - $this->assertEquals(2, $children[2]->position); - - $children = $entity->getChildrenRange(2); - - $this->assertCount(2, $children); - $this->assertEquals(2, $children[0]->position); - $this->assertEquals(3, $children[1]->position); - } - public function testAddChildWithPosition() { $entity = Entity::find(15); diff --git a/tests/Page.php b/tests/Page.php index e861d03..6d91655 100644 --- a/tests/Page.php +++ b/tests/Page.php @@ -6,5 +6,5 @@ class Page extends Entity { protected $table = 'entities'; - protected $fillable = ['id', 'title', 'excerpt', 'body', 'position', 'real_depth']; + protected $fillable = ['id', 'parent_id', 'title', 'excerpt', 'body', 'position', 'real_depth']; } From 932e9ad159baab419f7a3393b63cf97cc4098df7 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 13:58:30 +0700 Subject: [PATCH 019/113] refactor Str to not use global helper functions --- src/Franzose/ClosureTable/Extensions/Str.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Franzose/ClosureTable/Extensions/Str.php b/src/Franzose/ClosureTable/Extensions/Str.php index f94b8b5..de25ce1 100644 --- a/src/Franzose/ClosureTable/Extensions/Str.php +++ b/src/Franzose/ClosureTable/Extensions/Str.php @@ -4,11 +4,11 @@ use Illuminate\Support\Str as BaseStr; /** - * Extension of the base Str class. + * Custom string functions extenstion. * * @package Franzose\ClosureTable\Extensions */ -class Str extends BaseStr +class Str { /** * Makes appropriate class name from given string. @@ -18,7 +18,7 @@ class Str extends BaseStr */ public static function classify($name) { - return studly_case(str_singular($name)); + return BaseStr::studly(BaseStr::singular($name)); } /** @@ -31,6 +31,8 @@ public static function tableize($name) { $name = str_replace('\\', '', $name); - return (ends_with($name, 'Closure') ? snake_case($name) : snake_case(str_plural($name))); + return BaseStr::endsWith($name, 'Closure') + ? BaseStr::snake($name) + : BaseStr::snake(BaseStr::plural($name)); } } From 5cfead9e90514069e1f53d22a0494814cedf9af5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 18:45:43 +0700 Subject: [PATCH 020/113] deprecated EntityInterface and ClosureTableInterface, refactored and tested generators and generated resources --- .../Contracts/ClosureTableInterface.php | 1 + .../Contracts/EntityInterface.php | 1 + .../ClosureTable/Generators/Migration.php | 16 ++--- .../ClosureTable/Generators/Model.php | 26 +------- .../stubs/migrations/closuretable-innodb.php | 35 ----------- .../stubs/migrations/closuretable.php | 10 ++- .../stubs/migrations/entity-innodb.php | 34 ----------- .../Generators/stubs/migrations/entity.php | 10 ++- .../Generators/stubs/models/closuretable.php | 2 +- .../stubs/models/closuretableinterface.php | 8 --- .../Generators/stubs/models/entity.php | 4 +- .../stubs/models/entityinterface.php | 8 --- tests/Generators/MigrationTests.php | 61 +++++++++++++++++++ tests/Generators/ModelTest.php | 34 +++++++++++ tests/Generators/expectedClosure.php | 14 +++++ .../expectedClosureInnoDbMigration.php | 28 +++++++++ tests/Generators/expectedClosureMigration.php | 26 ++++++++ tests/Generators/expectedEntity.php | 21 +++++++ .../expectedEntityInnoDbMigration.php | 27 ++++++++ tests/Generators/expectedEntityMigration.php | 25 ++++++++ 20 files changed, 261 insertions(+), 130 deletions(-) delete mode 100644 src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable-innodb.php delete mode 100644 src/Franzose/ClosureTable/Generators/stubs/migrations/entity-innodb.php delete mode 100644 src/Franzose/ClosureTable/Generators/stubs/models/closuretableinterface.php delete mode 100644 src/Franzose/ClosureTable/Generators/stubs/models/entityinterface.php create mode 100644 tests/Generators/MigrationTests.php create mode 100644 tests/Generators/ModelTest.php create mode 100644 tests/Generators/expectedClosure.php create mode 100644 tests/Generators/expectedClosureInnoDbMigration.php create mode 100644 tests/Generators/expectedClosureMigration.php create mode 100644 tests/Generators/expectedEntity.php create mode 100644 tests/Generators/expectedEntityInnoDbMigration.php create mode 100644 tests/Generators/expectedEntityMigration.php diff --git a/src/Franzose/ClosureTable/Contracts/ClosureTableInterface.php b/src/Franzose/ClosureTable/Contracts/ClosureTableInterface.php index 598d51d..1ded8f2 100644 --- a/src/Franzose/ClosureTable/Contracts/ClosureTableInterface.php +++ b/src/Franzose/ClosureTable/Contracts/ClosureTableInterface.php @@ -4,6 +4,7 @@ /** * Basic ClosureTable model interface. * + * @deprecated since 6.0 * @package Franzose\ClosureTable */ interface ClosureTableInterface diff --git a/src/Franzose/ClosureTable/Contracts/EntityInterface.php b/src/Franzose/ClosureTable/Contracts/EntityInterface.php index a5944d4..0a8da2f 100644 --- a/src/Franzose/ClosureTable/Contracts/EntityInterface.php +++ b/src/Franzose/ClosureTable/Contracts/EntityInterface.php @@ -4,6 +4,7 @@ /** * Basic Entity model interface. * + * @deprecated since 6.0 * @package Franzose\ClosureTable\Contracts */ interface EntityInterface diff --git a/src/Franzose/ClosureTable/Generators/Migration.php b/src/Franzose/ClosureTable/Generators/Migration.php index 183dca0..c0247c5 100644 --- a/src/Franzose/ClosureTable/Generators/Migration.php +++ b/src/Franzose/ClosureTable/Generators/Migration.php @@ -28,24 +28,25 @@ public function create(array $options) $entityClass = $this->getClassName($options['entity-table']); $closureClass = $this->getClassName($options['closure-table']); - $useInnoDB = $options['use-innodb']; - $stubPrefix = $useInnoDB ? '-innodb' : ''; + $innoDb = $options['use-innodb'] ? '$table->engine = \'InnoDB\';' : ''; $paths[] = $path = $this->getPath($options['entity-table'], $options['migrations-path']); - $stub = $this->getStub('entity' . $stubPrefix, 'migrations'); + $stub = $this->getStub('entity', 'migrations'); $this->filesystem->put($path, $this->parseStub($stub, [ 'entity_table' => $options['entity-table'], - 'entity_class' => $entityClass + 'entity_class' => $entityClass, + 'innodb' => $innoDb ])); $paths[] = $path = $this->getPath($options['closure-table'], $options['migrations-path']); - $stub = $this->getStub('closuretable' . $stubPrefix, 'migrations'); + $stub = $this->getStub('closuretable', 'migrations'); $this->filesystem->put($path, $this->parseStub($stub, [ 'closure_table' => $options['closure-table'], 'closure_class' => $closureClass, - 'entity_table' => $options['entity-table'] + 'entity_table' => $options['entity-table'], + 'innodb' => $innoDb ])); return $paths; @@ -84,9 +85,10 @@ protected function getPath($name, $path) { $timestamp = Carbon::now(); - if (in_array($timestamp, $this->usedTimestamps)) { + if (in_array($timestamp, $this->usedTimestamps, true)) { $timestamp->addSecond(); } + $this->usedTimestamps[] = $timestamp; return $path . '/' . $timestamp->format('Y_m_d_His') . '_' . $this->getName($name) . '.php'; diff --git a/src/Franzose/ClosureTable/Generators/Model.php b/src/Franzose/ClosureTable/Generators/Model.php index 791f065..3a90489 100644 --- a/src/Franzose/ClosureTable/Generators/Model.php +++ b/src/Franzose/ClosureTable/Generators/Model.php @@ -20,13 +20,12 @@ public function create(array $options) { $paths = []; - $nsplaceholder = (!empty($options['namespace']) ? "namespace " . $options['namespace'] . ";" : ''); + $nsplaceholder = !empty($options['namespace']) + ? sprintf('namespace %s;', $options['namespace']) + : ''; - $closureInterface = $options['closure'] . 'Interface'; $qualifiedEntityName = $options['entity']; - $qualifiedEntityInterfaceName = $qualifiedEntityName . 'Interface'; $qualifiedClosureName = $options['closure']; - $qualifiedClosureInterfaceName = $qualifiedClosureName . 'Interface'; // First, we make entity classes $paths[] = $path = $this->getPath($qualifiedEntityName, $options['models-path']); @@ -37,16 +36,6 @@ public function create(array $options) 'entity_class' => $options['entity'], 'entity_table' => $options['entity-table'], 'closure_class' => $options['namespace'] . '\\' . $options['closure'], - 'closure_class_short' => $options['closure'], - 'closure_interface' => $closureInterface - ])); - - $paths[] = $path = $this->getPath($qualifiedEntityInterfaceName, $options['models-path']); - $stub = $this->getStub('entityinterface', 'models'); - - $this->filesystem->put($path, $this->parseStub($stub, [ - 'namespace' => $nsplaceholder, - 'entity_class' => $options['entity'] ])); // Second, we make closure classes @@ -59,15 +48,6 @@ public function create(array $options) 'closure_table' => $options['closure-table'] ])); - - $paths[] = $path = $this->getPath($qualifiedClosureInterfaceName, $options['models-path']); - $stub = $this->getStub('closuretableinterface', 'models'); - - $this->filesystem->put($path, $this->parseStub($stub, [ - 'namespace' => $nsplaceholder, - 'closure_class' => $options['closure'] - ])); - return $paths; } diff --git a/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable-innodb.php b/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable-innodb.php deleted file mode 100644 index dadb63f..0000000 --- a/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable-innodb.php +++ /dev/null @@ -1,35 +0,0 @@ -engine = 'InnoDB'; - - Schema::create('{{closure_table}}', function(Blueprint $table) - { - $table->increments('closure_id'); - - $table->integer('ancestor', false, true); - $table->integer('descendant', false, true); - $table->integer('depth', false, true); - - $table->foreign('ancestor')->references('id')->on('{{entity_table}}')->onDelete('cascade'); - $table->foreign('descendant')->references('id')->on('{{entity_table}}')->onDelete('cascade'); - }); - }); - } - - public function down() - { - Schema::table('{{closure_table}}', function(Blueprint $table) - { - Schema::dropIfExists('{{closure_table}}'); - }); - } -} diff --git a/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable.php b/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable.php index ee08a53..d66f3e7 100644 --- a/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable.php +++ b/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable.php @@ -7,8 +7,7 @@ class {{closure_class}} extends Migration { public function up() { - Schema::create('{{closure_table}}', function(Blueprint $table) - { + Schema::create('{{closure_table}}', function(Blueprint $table) { $table->increments('closure_id'); $table->integer('ancestor', false, true); @@ -17,14 +16,13 @@ public function up() $table->foreign('ancestor')->references('id')->on('{{entity_table}}')->onDelete('cascade'); $table->foreign('descendant')->references('id')->on('{{entity_table}}')->onDelete('cascade'); + + {{innodb}} }); } public function down() { - Schema::table('{{closure_table}}', function(Blueprint $table) - { - Schema::dropIfExists('{{closure_table}}'); - }); + Schema::dropIfExists('{{closure_table}}'); } } diff --git a/src/Franzose/ClosureTable/Generators/stubs/migrations/entity-innodb.php b/src/Franzose/ClosureTable/Generators/stubs/migrations/entity-innodb.php deleted file mode 100644 index e7cc714..0000000 --- a/src/Franzose/ClosureTable/Generators/stubs/migrations/entity-innodb.php +++ /dev/null @@ -1,34 +0,0 @@ -engine = 'InnoDB'; - - Schema::create('{{entity_table}}', function(Blueprint $table) - { - $table->increments('id'); - $table->integer('parent_id')->unsigned()->nullable(); - $table->integer('position', false, true); - $table->integer('real_depth', false, true); - $table->softDeletes(); - - $table->foreign('parent_id')->references('id')->on('{{entity_table}}')->onDelete('set null'); - }); - }); - } - - public function down() - { - Schema::table('{{entity_table}}', function(Blueprint $table) - { - Schema::dropIfExists('{{entity_table}}'); - }); - } -} diff --git a/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php b/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php index cf23f4a..323182f 100644 --- a/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php +++ b/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php @@ -7,8 +7,7 @@ class {{entity_class}} extends Migration { public function up() { - Schema::create('{{entity_table}}', function(Blueprint $table) - { + Schema::create('{{entity_table}}', function(Blueprint $table) { $table->increments('id'); $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); @@ -16,14 +15,13 @@ public function up() $table->softDeletes(); $table->foreign('parent_id')->references('id')->on('{{entity_table}}')->onDelete('set null'); + + {{innodb}} }); } public function down() { - Schema::table('{{entity_table}}', function(Blueprint $table) - { - Schema::dropIfExists('{{entity_table}}'); - }); + Schema::dropIfExists('{{entity_table}}'); } } diff --git a/src/Franzose/ClosureTable/Generators/stubs/models/closuretable.php b/src/Franzose/ClosureTable/Generators/stubs/models/closuretable.php index 406015f..001deef 100644 --- a/src/Franzose/ClosureTable/Generators/stubs/models/closuretable.php +++ b/src/Franzose/ClosureTable/Generators/stubs/models/closuretable.php @@ -3,7 +3,7 @@ use Franzose\ClosureTable\Models\ClosureTable; -class {{closure_class}} extends ClosureTable implements {{closure_class}}Interface +class {{closure_class}} extends ClosureTable { /** * The table associated with the model. diff --git a/src/Franzose/ClosureTable/Generators/stubs/models/closuretableinterface.php b/src/Franzose/ClosureTable/Generators/stubs/models/closuretableinterface.php deleted file mode 100644 index 3eb0e53..0000000 --- a/src/Franzose/ClosureTable/Generators/stubs/models/closuretableinterface.php +++ /dev/null @@ -1,8 +0,0 @@ -create([ + 'migrations-path' => __DIR__, + 'entity-table' => 'entity', + 'closure-table' => 'entity_tree', + 'use-innodb' => $useInnoDb + ]); + + Carbon::setTestNow(); + + $entityMigrationPath = __DIR__ . '/2020_04_03_000000_create_entities_table.php'; + $closureMigrationPath = __DIR__ . '/2020_04_03_000000_create_entity_trees_table.php'; + + static::assertFileExists($entityMigrationPath); + static::assertFileExists($closureMigrationPath); + + $expectedEntityMigrationPath = sprintf( + '%s/expectedEntity%sMigration.php', + __DIR__, + $useInnoDb ? 'InnoDb' : '' + ); + + $expectedClosureMigrationPath = sprintf( + '%s/expectedClosure%sMigration.php', + __DIR__, + $useInnoDb ? 'InnoDb' : '' + ); + + static::assertFileEquals($expectedEntityMigrationPath, $entityMigrationPath); + static::assertFileEquals($expectedClosureMigrationPath, $closureMigrationPath); + + unlink($entityMigrationPath); + unlink($closureMigrationPath); + } + + public function useInnoDbDataProvider() + { + return [ + [true, false] + ]; + } +} diff --git a/tests/Generators/ModelTest.php b/tests/Generators/ModelTest.php new file mode 100644 index 0000000..f8024ca --- /dev/null +++ b/tests/Generators/ModelTest.php @@ -0,0 +1,34 @@ +create([ + 'namespace' => 'Foo', + 'entity' => 'FooBar', + 'entity-table' => 'foo_bar', + 'closure' => 'FooBarClosure', + 'closure-table' => 'foo_bar_tree', + 'models-path' => __DIR__, + ]); + + $entityPath = __DIR__ . '/FooBar.php'; + $closurePath = __DIR__ . '/FooBarClosure.php'; + static::assertFileExists($entityPath); + static::assertFileExists($closurePath); + static::assertFileEquals(__DIR__ . '/expectedEntity.php', $entityPath); + static::assertFileEquals(__DIR__ . '/expectedClosure.php', $closurePath); + + unlink($entityPath); + unlink($closurePath); + } +} diff --git a/tests/Generators/expectedClosure.php b/tests/Generators/expectedClosure.php new file mode 100644 index 0000000..7b4899c --- /dev/null +++ b/tests/Generators/expectedClosure.php @@ -0,0 +1,14 @@ +increments('closure_id'); + + $table->integer('ancestor', false, true); + $table->integer('descendant', false, true); + $table->integer('depth', false, true); + + $table->foreign('ancestor')->references('id')->on('entity')->onDelete('cascade'); + $table->foreign('descendant')->references('id')->on('entity')->onDelete('cascade'); + + $table->engine = 'InnoDB'; + }); + } + + public function down() + { + Schema::dropIfExists('entity_tree'); + } +} diff --git a/tests/Generators/expectedClosureMigration.php b/tests/Generators/expectedClosureMigration.php new file mode 100644 index 0000000..8d11fed --- /dev/null +++ b/tests/Generators/expectedClosureMigration.php @@ -0,0 +1,26 @@ +increments('closure_id'); + + $table->integer('ancestor', false, true); + $table->integer('descendant', false, true); + $table->integer('depth', false, true); + + $table->foreign('ancestor')->references('id')->on('entity')->onDelete('cascade'); + $table->foreign('descendant')->references('id')->on('entity')->onDelete('cascade'); + }); + } + + public function down() + { + Schema::dropIfExists('entity_tree'); + } +} diff --git a/tests/Generators/expectedEntity.php b/tests/Generators/expectedEntity.php new file mode 100644 index 0000000..59802d4 --- /dev/null +++ b/tests/Generators/expectedEntity.php @@ -0,0 +1,21 @@ +increments('id'); + $table->integer('parent_id')->unsigned()->nullable(); + $table->integer('position', false, true); + $table->integer('real_depth', false, true); + $table->softDeletes(); + + $table->foreign('parent_id')->references('id')->on('entity')->onDelete('set null'); + + $table->engine = 'InnoDB'; + }); + } + + public function down() + { + Schema::dropIfExists('entity'); + } +} diff --git a/tests/Generators/expectedEntityMigration.php b/tests/Generators/expectedEntityMigration.php new file mode 100644 index 0000000..db3f795 --- /dev/null +++ b/tests/Generators/expectedEntityMigration.php @@ -0,0 +1,25 @@ +increments('id'); + $table->integer('parent_id')->unsigned()->nullable(); + $table->integer('position', false, true); + $table->integer('real_depth', false, true); + $table->softDeletes(); + + $table->foreign('parent_id')->references('id')->on('entity')->onDelete('set null'); + }); + } + + public function down() + { + Schema::dropIfExists('entity'); + } +} From 19c27208e9f65205566d624e5b7e61ab3831eeec Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 18:46:20 +0700 Subject: [PATCH 021/113] removed useless command --- .../Console/ClosureTableCommand.php | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/Franzose/ClosureTable/Console/ClosureTableCommand.php diff --git a/src/Franzose/ClosureTable/Console/ClosureTableCommand.php b/src/Franzose/ClosureTable/Console/ClosureTableCommand.php deleted file mode 100644 index 537dcf6..0000000 --- a/src/Franzose/ClosureTable/Console/ClosureTableCommand.php +++ /dev/null @@ -1,39 +0,0 @@ -info('ClosureTable v' . CT::VERSION); - $this->line('Closure Table database design pattern implementation for Laravel framework.'); - $this->comment('Copyright (c) 2013-2014 Jan Iwanow'); - } -} From ac066d203be346a033434c4748cbb8200734c733 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 21:03:38 +0700 Subject: [PATCH 022/113] refactored child nodes manipulation and the dedicated tests --- src/Franzose/ClosureTable/Models/Entity.php | 178 ++++++++------------ tests/Entity/ChildManipulationTests.php | 125 ++++++++++++++ tests/EntityTestCase.php | 67 -------- 3 files changed, 195 insertions(+), 175 deletions(-) create mode 100644 tests/Entity/ChildManipulationTests.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index eb9d451..bf7c5fa 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -8,6 +8,8 @@ use Franzose\ClosureTable\Contracts\EntityInterface; use Franzose\ClosureTable\Extensions\Collection; use Illuminate\Database\Eloquent\Relations\HasMany; +use InvalidArgumentException; +use Throwable; /** * Basic entity class. @@ -505,49 +507,6 @@ public function hasDescendants() return (bool) $this->countDescendants(); } - /** - * Shorthand of the children query part. - * - * @param array|int|null $position - * @param string $order - * @return QueryBuilder - */ - protected function childrenQuery($position = null, $order = 'asc') - { - $query = $this->queryByParentId(); - - if ($position !== null) { - if (is_array($position)) { - $query->buildWherePosition($this->getPositionColumn(), $position); - } else { - if ($position === static::QUERY_LAST) { - $query->orderBy($this->getPositionColumn(), 'desc'); - } else { - $query->where($this->getPositionColumn(), '=', $position); - } - } - } - - if ($position !== static::QUERY_LAST) { - $query->orderBy($this->getPositionColumn(), $order); - } - - return $query; - } - - /** - * Starts a query by parent identifier. - * - * @param mixed $id - * @return QueryBuilder - */ - protected function queryByParentId($id = null) - { - $id = ($id ?: $this->getKey()); - - return $this->where($this->getParentIdColumn(), '=', $id); - } - /** * Returns one-to-many relationship to child nodes. * @@ -702,18 +661,6 @@ public function getChildrenRange($from, $to = null, array $columns = ['*']) return $this->childrenRange($from, $to)->get($columns); } - /** - * Gets last child position. - * - * @return int - */ - protected function getLastChildPosition() - { - $lastChild = $this->getLastChild([$this->getPositionColumn()]); - - return $lastChild === null ? 0 : $lastChild->position; - } - /** * Appends a child to the model. * @@ -725,47 +672,65 @@ protected function getLastChildPosition() public function addChild(EntityInterface $child, $position = null, $returnChild = false) { if ($this->exists) { - if ($position === null) { - $position = $this->getNextAfterLastPosition($this->getKey()); - } + $position = $position ?: $this->getLatestPosition(); $child->moveTo($position, $this); } - return ($returnChild === true ? $child : $this); + return $returnChild === true ? $child : $this; } /** * Appends a collection of children to the model. * - * @param array $children - * @return $this - * @throws \InvalidArgumentException + * @param Entity[] $children + * + * @return Entity + * @throws InvalidArgumentException + * @throws Throwable */ public function addChildren(array $children) { - if ($this->exists) { - \DB::connection($this->connection)->transaction(function () use ($children) { - $lastChildPosition = $this->getLastChildPosition(); - - foreach ($children as $child) { - if (!$child instanceof EntityInterface) { - if (isset($child['id'])) { - unset($child['id']); - } - - $child = new static($child); - } + return $this->insertChildren($this->getLastChildPosition(), ...$children); + } - $this->addChild($child, $lastChildPosition); - $lastChildPosition++; - } - }); + /** + * Inserts children nodes starting from the specified position. + * + * @param int $position + * @param Entity[] $children + * + * @return Entity + * @throws Throwable + */ + public function insertChildren($position, Entity... $children) + { + if (!$this->exists) { + return $this; } + $this->getConnection()->transaction(function () use (&$position, $children) { + foreach ($children as $child) { + $this->addChild($child, $position); + $position++; + } + }); + return $this; } + /** + * Gets last child position. + * + * @return int + */ + protected function getLastChildPosition() + { + $lastChild = $this->getLastChild([$this->getPositionColumn()]); + + return $lastChild === null ? 0 : $lastChild->position; + } + /** * Removes a model's child with given position. * @@ -775,12 +740,14 @@ public function addChildren(array $children) */ public function removeChild($position = null, $forceDelete = false) { - if ($this->exists) { - $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - - $this->childrenQuery($position)->$action(); + if (!$this->exists) { + return $this; } + $action = ($forceDelete === true ? 'forceDelete' : 'delete'); + + $this->childAt($position)->{$action}(); + return $this; } @@ -791,20 +758,22 @@ public function removeChild($position = null, $forceDelete = false) * @param int $to * @param bool $forceDelete * @return $this - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function removeChildren($from, $to = null, $forceDelete = false) { if (!is_numeric($from) || ($to !== null && !is_numeric($to))) { - throw new \InvalidArgumentException('`from` and `to` are the position boundaries. They must be of type int.'); + throw new InvalidArgumentException('`from` and `to` are the position boundaries. They must be of type int.'); } - if ($this->exists) { - $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - - $this->childrenQuery([$from, $to])->$action(); + if (!$this->exists) { + return $this; } + $action = ($forceDelete === true ? 'forceDelete' : 'delete'); + + $this->childrenRange($from, $to)->{$action}(); + return $this; } @@ -1055,7 +1024,7 @@ public function addSibling(EntityInterface $sibling, $position = null, $returnSi { if ($this->exists) { if ($position === null) { - $position = $this->getNextAfterLastPosition(); + $position = $this->getLatestPosition(); } $sibling->moveTo($position, $this->parent_id); @@ -1075,7 +1044,7 @@ public function addSiblings(array $siblings, $from = null) { if ($this->exists) { if ($from === null) { - $from = $this->getNextAfterLastPosition(); + $from = $this->getLatestPosition(); } $parent = $this->getParent(); @@ -1228,7 +1197,7 @@ public static function createFromArray(array $tree, EntityInterface $parent = nu * @param int $position * @param EntityInterface|int $ancestor * @return Entity - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function moveTo($position, $ancestor = null) { @@ -1239,7 +1208,7 @@ public function moveTo($position, $ancestor = null) } if ($this->getKey() === $parentId) { - throw new \InvalidArgumentException('Target entity is equal to the sender.'); + throw new InvalidArgumentException('Target entity is equal to the sender.'); } $this->parent_id = $parentId; @@ -1285,7 +1254,7 @@ protected function getNewRealDepth($ancestor) protected function performInsert(EloquentBuilder $query, array $options = []) { if ($this->isMoved === false) { - $this->position = $this->position !== null ? $this->position : $this->getNextAfterLastPosition(); + $this->position = $this->position !== null ? $this->position : $this->getLatestPosition(); $this->real_depth = $this->getNewRealDepth($this->parent_id); } @@ -1317,30 +1286,23 @@ protected function performUpdate(EloquentBuilder $query, array $options = []) } /** - * Gets the next sibling position after the last one at the given ancestor. + * Gets the next sibling position after the last one. * - * @param int|bool $parentId * @return int */ - public function getNextAfterLastPosition($parentId = false) - { - $position = $this->getLastPosition($parentId); - return $position === null ? 0 : $position + 1; - } - - public function getLastPosition($parentId = false) + private function getLatestPosition() { $positionColumn = $this->getPositionColumn(); $parentIdColumn = $this->getParentIdColumn(); - $parentId = ($parentId === false ? $this->parent_id : $parentId); - $entity = $this->select($positionColumn) - ->where($parentIdColumn, '=', $parentId) - ->orderBy($positionColumn, 'desc') + ->where($parentIdColumn, '=', $this->parent_id) + ->latest($positionColumn) ->first(); - return $entity !== null ? (int)$entity->position : null; + $position = $entity !== null ? $entity->position : -1; + + return $position + 1; } /** @@ -1457,7 +1419,7 @@ protected function clampPosition() if (!$this->isDirty($this->getPositionColumn())) { return; } - $newPosition = max(0, min($this->position, $this->getNextAfterLastPosition())); + $newPosition = max(0, min($this->position, $this->getLatestPosition())); $this->attributes[$this->getPositionColumn()] = $newPosition; } diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Entity/ChildManipulationTests.php new file mode 100644 index 0000000..a65aeaf --- /dev/null +++ b/tests/Entity/ChildManipulationTests.php @@ -0,0 +1,125 @@ +addChild($child); + + static::assertEquals(14, $child->parent_id); + static::assertEquals(0, $child->position); + static::assertTrue($leaf->isParent()); + } + + public function testAddChildShouldReturnChild() + { + $leaf = Entity::find(14); + $child = Entity::find(15); + + $result = $leaf->addChild($child, 0, true); + + static::assertSame($child, $result); + } + + public function testAddChildToTheLastPosition() + { + $parent = Entity::find(9); + $child = Entity::find(12); + + $parent->addChild($child); + + static::assertEquals(9, $child->parent_id); + static::assertEquals(4, $child->position); + static::assertEquals([0, 1, 2, 3], static::getPositionsByIds([10, 13, 14, 15])); + } + + public function testAddChildToPosition() + { + $parent = Entity::find(9); + $child = Entity::find(12); + + $parent->addChild($child, 2); + + static::assertEquals(9, $child->parent_id); + static::assertEquals(2, $child->position); + static::assertEquals([0, 1, 3, 4], static::getPositionsByIds([10, 13, 14, 15])); + } + + public function testAddChildren() + { + $entity = Entity::find(15); + $child1 = new Entity(); + $child2 = new Entity(); + $child3 = new Entity(); + + $result = $entity->addChildren([ + $child1, + $child2, + $child3 + ]); + + static::assertSame($entity, $result); + static::assertEquals(3, $entity->countChildren()); + static::assertEquals(0, $child1->position); + static::assertEquals(1, $child2->position); + static::assertEquals(2, $child3->position); + } + + public function testInsertChildren() + { + $entity = Entity::find(9); + $child1 = new Entity(); + $child2 = new Entity(); + + $entity->insertChildren(1, $child1, $child2); + + static::assertEquals(6, $entity->countChildren()); + static::assertEquals([0, 3, 4, 5], static::getPositionsByIds([10, 13, 14, 15])); + static::assertEquals(1, $child1->position); + static::assertEquals(2, $child2->position); + } + + public function testRemoveChild() + { + $entity = Entity::find(9); + + $entity->removeChild(0); + + static::assertNull(Entity::find(10)); + static::assertEquals(3, $entity->countChildren()); + } + + public function testRemoveChildren() + { + $entity = Entity::find(9); + $entity->removeChildren(0, 2); + + static::assertEquals(1, $entity->countChildren()); + } + + public function testRemoveChildrenToTheEnd() + { + $entity = Entity::find(9); + + $entity->removeChildren(1); + + static::assertEquals(1, $entity->countChildren()); + static::assertEquals(10, $entity->getFirstChild()->getKey()); + } + + public static function getPositionsByIds(array $entityIds) + { + return Entity::whereIn('id', $entityIds) + ->get(['position']) + ->pluck('position') + ->toArray(); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 69caacb..9478f0f 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -191,73 +191,6 @@ public function testGetParentAfterMovingToAnAncestor() $this->assertEquals(15, $parent->getKey()); } - public function testAddChildWithPosition() - { - $entity = Entity::find(15); - $newone = new Entity; - $result = $entity->addChild($newone, 0); - - $this->assertEquals(0, $newone->position); - $this->assertTrue($entity->isParent()); - $this->assertSame($entity, $result); - } - - public function testAddChildWithoutPosition() - { - $entity = Entity::find(9); - $newone = new Entity; - $result = $entity->addChild($newone); - - $this->assertEquals(4, $newone->position); - $this->assertTrue($entity->isParent()); - $this->assertSame($entity, $result); - } - - public function testAddChildren() - { - $entity = Entity::find(15); - $child1 = new Entity; - $child2 = new Entity; - $child3 = new Entity; - - $result = $entity->addChildren([$child1, $child2, $child3]); - - $this->assertSame($entity, $result); - $this->assertEquals(3, $entity->countChildren()); - - $this->assertEquals(0, $child1->position); - $this->assertEquals(1, $child2->position); - $this->assertEquals(2, $child3->position); - } - - public function testRemoveChild() - { - $entity = Entity::find(9); - $entity->removeChild(0); - - $child = Entity::find(10); - - $this->assertNull($child); - $this->assertEquals(3, $entity->countChildren()); - } - - public function testRemoveChildren() - { - $entity = Entity::find(9); - $entity->removeChildren(0, 1); - - $this->assertEquals(2, $entity->countChildren()); - } - - public function testRemoveChildrenToTheEnd() - { - $entity = Entity::find(9); - $entity->removeChildren(1); - - $this->assertInstanceOf('Franzose\ClosureTable\Models\Entity', $entity->getFirstChild()); - $this->assertEquals(1, $entity->countChildren()); - } - public function testGetSiblings() { $entity = Entity::find(13); From 2607921b2ba549c6a76e23b197ec431f5fd353c5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 22:18:47 +0700 Subject: [PATCH 023/113] added and used a bunch of useful sibling scopes --- src/Franzose/ClosureTable/Models/Entity.php | 216 +++++++++++++++++--- tests/Entity/SiblingQueryTests.php | 139 +++++++++++++ tests/EntityTestCase.php | 146 ------------- 3 files changed, 332 insertions(+), 169 deletions(-) create mode 100644 tests/Entity/SiblingQueryTests.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index bf7c5fa..3f45af8 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -3,7 +3,6 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model as Eloquent; -use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Franzose\ClosureTable\Extensions\QueryBuilder; use Franzose\ClosureTable\Contracts\EntityInterface; use Franzose\ClosureTable\Extensions\Collection; @@ -26,6 +25,17 @@ * @method HasMany firstChild() * @method HasMany lastChild() * @method HasMany childrenRange(int $from, int $to = null) + * @method Builder sibling() + * @method Builder siblings() + * @method Builder neighbors() + * @method Builder siblingAt(int $position) + * @method Builder firstSibling() + * @method Builder lastSibling() + * @method Builder prevSibling() + * @method Builder prevSiblings() + * @method Builder nextSibling() + * @method Builder nextSiblings() + * @method Builder siblingsRange(int $from, int $to = null) * * @package Franzose\ClosureTable */ @@ -613,7 +623,7 @@ public function getFirstChild(array $columns = ['*']) */ public function scopeLastChild() { - return $this->children()->orderBy($this->getPositionColumn(), 'desc'); + return $this->children()->orderByDesc($this->getPositionColumn()); } /** @@ -783,9 +793,10 @@ public function removeChildren($from, $to = null, $forceDelete = false) * @param string|int|array $direction * @param int|bool $parentId * @param string $order + * * @return QueryBuilder */ - protected function siblings($direction = '', $parentId = false, $order = 'asc') + protected function siblingsQuery($direction = '', $parentId = false, $order = 'asc') { $parentId = ($parentId === false ? $this->parent_id : $parentId); @@ -835,6 +846,32 @@ protected function siblings($direction = '', $parentId = false, $order = 'asc') return $query; } + /** + * Returns sibling query builder. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeSibling(Builder $builder) + { + return $builder->where($this->getParentIdColumn(), '=', $this->parent_id); + } + + /** + * Returns siblings query builder. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeSiblings(Builder $builder) + { + return $this + ->scopeSibling($builder) + ->where($this->getPositionColumn(), '<>', $this->position); + } + /** * Retrives all siblings of a model. * @@ -844,7 +881,7 @@ protected function siblings($direction = '', $parentId = false, $order = 'asc') */ public function getSiblings(array $columns = ['*']) { - return $this->siblings(static::QUERY_ALL)->get($columns); + return $this->siblings()->get($columns); } /** @@ -854,7 +891,7 @@ public function getSiblings(array $columns = ['*']) */ public function countSiblings() { - return $this->siblings(static::QUERY_ALL)->count(); + return $this->siblings()->count(); } /** @@ -864,7 +901,23 @@ public function countSiblings() */ public function hasSiblings() { - return !!$this->countSiblings(); + return (bool) $this->countSiblings(); + } + + /** + * Returns neighbors query builder. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeNeighbors(Builder $builder) + { + $position = $this->position; + + return $this + ->scopeSibling($builder) + ->whereIn($this->getPositionColumn(), [$position - 1, $position + 1]); } /** @@ -876,7 +929,22 @@ public function hasSiblings() */ public function getNeighbors(array $columns = ['*']) { - return $this->siblings(static::QUERY_NEIGHBORS)->get($columns); + return $this->neighbors()->get($columns); + } + + /** + * Returns query builder for a sibling at the given position. + * + * @param Builder $builder + * @param int $position + * + * @return Builder + */ + public function scopeSiblingAt(Builder $builder, $position) + { + return $this + ->scopeSibling($builder) + ->where($this->getPositionColumn(), '=', $position); } /** @@ -888,7 +956,17 @@ public function getNeighbors(array $columns = ['*']) */ public function getSiblingAt($position, array $columns = ['*']) { - return $this->siblings($position)->first($columns); + return $this->siblingAt($position)->first($columns); + } + + /** + * Returns query builder for the first sibling. + * + * @return Builder + */ + public function scopeFirstSibling() + { + return $this->siblingAt(0); } /** @@ -902,6 +980,18 @@ public function getFirstSibling(array $columns = ['*']) return $this->getSiblingAt(0, $columns); } + /** + * Returns query builder for the last sibling. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeLastSibling(Builder $builder) + { + return $this->scopeSibling($builder)->orderByDesc($this->getPositionColumn()); + } + /** * Retrieves the last model's sibling. * @@ -910,7 +1000,21 @@ public function getFirstSibling(array $columns = ['*']) */ public function getLastSibling(array $columns = ['*']) { - return $this->siblings(static::QUERY_LAST)->first($columns); + return $this->lastSibling()->first($columns); + } + + /** + * Returns query builder for the previous sibling. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopePrevSibling(Builder $builder) + { + return $this + ->scopeSibling($builder) + ->where($this->getPositionColumn(), '=', $this->position - 1); } /** @@ -921,7 +1025,21 @@ public function getLastSibling(array $columns = ['*']) */ public function getPrevSibling(array $columns = ['*']) { - return $this->siblings(static::QUERY_PREV_ONE)->first($columns); + return $this->prevSibling()->first($columns); + } + + /** + * Returns query builder for the previous siblings. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopePrevSiblings(Builder $builder) + { + return $this + ->scopeSibling($builder) + ->where($this->getPositionColumn(), '<', $this->position); } /** @@ -933,7 +1051,7 @@ public function getPrevSibling(array $columns = ['*']) */ public function getPrevSiblings(array $columns = ['*']) { - return $this->siblings(static::QUERY_PREV_ALL)->get($columns); + return $this->prevSiblings()->get($columns); } /** @@ -943,7 +1061,7 @@ public function getPrevSiblings(array $columns = ['*']) */ public function countPrevSiblings() { - return $this->siblings(static::QUERY_PREV_ALL)->count(); + return $this->prevSiblings()->count(); } /** @@ -953,7 +1071,21 @@ public function countPrevSiblings() */ public function hasPrevSiblings() { - return !!$this->countPrevSiblings(); + return (bool) $this->countPrevSiblings(); + } + + /** + * Returns query builder for the next sibling. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeNextSibling(Builder $builder) + { + return $this + ->scopeSibling($builder) + ->where($this->getPositionColumn(), '=', $this->position + 1); } /** @@ -964,7 +1096,21 @@ public function hasPrevSiblings() */ public function getNextSibling(array $columns = ['*']) { - return $this->siblings(static::QUERY_NEXT_ONE)->first($columns); + return $this->nextSibling()->first($columns); + } + + /** + * Returns query builder for the next siblings. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeNextSiblings(Builder $builder) + { + return $this + ->scopeSibling($builder) + ->where($this->getPositionColumn(), '>', $this->position); } /** @@ -976,7 +1122,7 @@ public function getNextSibling(array $columns = ['*']) */ public function getNextSiblings(array $columns = ['*']) { - return $this->siblings(static::QUERY_NEXT_ALL)->get($columns); + return $this->nextSiblings()->get($columns); } /** @@ -986,7 +1132,7 @@ public function getNextSiblings(array $columns = ['*']) */ public function countNextSiblings() { - return $this->siblings(static::QUERY_NEXT_ALL)->count(); + return $this->nextSiblings()->count(); } /** @@ -996,7 +1142,31 @@ public function countNextSiblings() */ public function hasNextSiblings() { - return !!$this->countNextSiblings(); + return (bool) $this->countNextSiblings(); + } + + /** + * Returns query builder for a range of siblings. + * + * @param Builder $builder + * @param int $from + * @param int|null $to + * + * @return Builder + */ + public function scopeSiblingsRange(Builder $builder, $from, $to = null) + { + $position = $this->getPositionColumn(); + + $query = $this + ->scopeSiblings($builder) + ->where($position, '>=', $from); + + if ($to === null) { + return $query; + } + + return $query->where($position, '<=', $to); } /** @@ -1009,7 +1179,7 @@ public function hasNextSiblings() */ public function getSiblingsRange($from, $to = null, array $columns = ['*']) { - return $this->siblings([$from, $to])->get($columns); + return $this->siblingsRange($from, $to)->get($columns); } /** @@ -1146,7 +1316,7 @@ public static function getTreeWhere($column, $operator = null, $value = null, ar * * @return Collection */ - public static function getTreeByQuery(EloquentBuilder $query, array $columns = ['*']) + public static function getTreeByQuery(Builder $query, array $columns = ['*']) { /** * @var Entity $instance @@ -1251,7 +1421,7 @@ protected function getNewRealDepth($ancestor) * * @return bool */ - protected function performInsert(EloquentBuilder $query, array $options = []) + protected function performInsert(Builder $query, array $options = []) { if ($this->isMoved === false) { $this->position = $this->position !== null ? $this->position : $this->getLatestPosition(); @@ -1269,7 +1439,7 @@ protected function performInsert(EloquentBuilder $query, array $options = []) * * @return bool */ - protected function performUpdate(EloquentBuilder $query, array $options = []) + protected function performUpdate(Builder $query, array $options = []) { if (parent::performUpdate($query, $options)) { if ($this->real_depth != $this->previousRealDepth && $this->isMoved === true) { @@ -1320,9 +1490,9 @@ protected function reorderSiblings($parentIdChanged = false) // As the method called twice (before moving and after moving), // first we gather "old" siblings by the old parent id value of the model. if ($parentIdChanged === true) { - $query = $this->siblings(false, $this->previousParentId); + $query = $this->siblingsQuery(false, $this->previousParentId); } else { - $query = $this->siblings(); + $query = $this->siblingsQuery(); } if ($action) { diff --git a/tests/Entity/SiblingQueryTests.php b/tests/Entity/SiblingQueryTests.php new file mode 100644 index 0000000..71e1ac6 --- /dev/null +++ b/tests/Entity/SiblingQueryTests.php @@ -0,0 +1,139 @@ +getSiblings(); + + static::assertInstanceOf(Collection::class, $siblings); + static::assertCount(3, $siblings); + static::assertEquals(10, $siblings->get(0)->getKey()); + static::assertEquals(14, $siblings->get(1)->getKey()); + static::assertEquals(15, $siblings->get(2)->getKey()); + } + + public function testsCountSiblings() + { + static::assertEquals(3, Entity::find(13)->countSiblings()); + } + + public function testsHasSiblings() + { + static::assertTrue(Entity::find(13)->hasSiblings()); + } + + public function testsGetNeighbors() + { + $entity = Entity::find(13); + + $neighbors = $entity->getNeighbors(); + + static::assertCount(2, $neighbors); + static::assertEquals(10, $neighbors->get(0)->getKey()); + static::assertEquals(14, $neighbors->get(1)->getKey()); + } + + public function testsGetSiblingAt() + { + $entity = Entity::find(13); + + $first = $entity->getSiblingAt(0); + $third = $entity->getSiblingAt(2); + + static::assertEquals(10, $first->getKey()); + static::assertEquals(14, $third->getKey()); + } + + public function testGetFirstSibling() + { + static::assertEquals(10, Entity::find(13)->getFirstSibling()->getKey()); + } + + public function testGetLastSibling() + { + static::assertEquals(15, Entity::find(13)->getLastSibling()->getKey()); + } + + public function testGetPrevSibling() + { + static::assertEquals(14, Entity::find(15)->getPrevSibling()->getKey()); + } + + public function testGetPrevSiblings() + { + $entity = Entity::find(15); + + $siblings = $entity->getPrevSiblings(); + + static::assertCount(3, $siblings); + static::assertEquals(10, $siblings->get(0)->getKey()); + static::assertEquals(13, $siblings->get(1)->getKey()); + static::assertEquals(14, $siblings->get(2)->getKey()); + } + + public function testsCountPrevSiblings() + { + static::assertEquals(3, Entity::find(15)->countPrevSiblings()); + static::assertEquals(0, Entity::find(1)->countPrevSiblings()); + } + + public function testsHasPrevSiblings() + { + static::assertTrue(Entity::find(15)->hasPrevSiblings()); + static::assertFalse(Entity::find(1)->hasPrevSiblings()); + } + + public function testGetNextSibling() + { + static::assertEquals(13, Entity::find(10)->getNextSibling()->getKey()); + } + + public function testGetNextSiblings() + { + $entity = Entity::find(10); + + $siblings = $entity->getNextSiblings(); + + static::assertCount(3, $siblings); + static::assertEquals(13, $siblings->get(0)->getKey()); + static::assertEquals(14, $siblings->get(1)->getKey()); + static::assertEquals(15, $siblings->get(2)->getKey()); + } + + public function testCountNextSiblings() + { + static::assertEquals(3, Entity::find(10)->countNextSiblings()); + static::assertEquals(0, Entity::find(15)->countNextSiblings()); + } + + public function testsHasNextSiblings() + { + static::assertTrue(Entity::find(10)->hasNextSiblings()); + static::assertFalse(Entity::find(15)->hasNextSiblings()); + } + + public function testGetSiblingsRange() + { + $entity = Entity::find(15); + + $siblings = $entity->getSiblingsRange(1, 2); + + static::assertCount(2, $siblings); + static::assertEquals(13, $siblings->get(0)->getKey()); + static::assertEquals(14, $siblings->get(1)->getKey()); + } + + public function testGetSiblingsOpenRange() + { + static::assertCount(2, Entity::find(15)->getSiblingsRange(1)); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 9478f0f..d2efe6a 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -191,152 +191,6 @@ public function testGetParentAfterMovingToAnAncestor() $this->assertEquals(15, $parent->getKey()); } - public function testGetSiblings() - { - $entity = Entity::find(13); - $siblings = $entity->getSiblings(); - - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $siblings); - $this->assertCount(3, $siblings); - $this->assertEquals(10, $siblings[0]->getKey()); - $this->assertEquals(14, $siblings[1]->getKey()); - $this->assertEquals(15, $siblings[2]->getKey()); - } - - public function testsCountSiblings() - { - $entity = Entity::find(13); - $number = $entity->countSiblings(); - - $this->assertEquals(3, $number); - } - - public function testsHasSiblings() - { - $entity = Entity::find(13); - $hasSiblings = $entity->hasSiblings(); - - $this->assertTrue($hasSiblings); - } - - public function testsGetNeighbors() - { - $entity = Entity::find(13); - $neighbors = $entity->getNeighbors(); - - $this->assertCount(2, $neighbors); - $this->assertEquals(10, $neighbors[0]->getKey()); - $this->assertEquals(14, $neighbors[1]->getKey()); - } - - public function testsGetSiblingAt() - { - $entity = Entity::find(13); - $sibling = $entity->getSiblingAt(0); - - $this->assertEquals(10, $sibling->getKey()); - - $sibling = $entity->getSiblingAt(2); - - $this->assertEquals(14, $sibling->getKey()); - } - - public function testGetFirstSibling() - { - $entity = Entity::find(13); - $sibling = $entity->getFirstSibling(); - - $this->assertEquals(10, $sibling->getKey()); - } - - public function testGetLastSibling() - { - $entity = Entity::find(13); - $sibling = $entity->getLastSibling(); - - $this->assertEquals(15, $sibling->getKey()); - } - - public function testGetPrevSibling() - { - $entity = Entity::find(15); - $sibling = $entity->getPrevSibling(); - - $this->assertEquals(14, $sibling->getKey()); - } - - public function testGetPrevSiblings() - { - $entity = Entity::find(15); - $siblings = $entity->getPrevSiblings(); - - $this->assertCount(3, $siblings); - $this->assertEquals(10, $siblings[0]->getKey()); - $this->assertEquals(13, $siblings[1]->getKey()); - $this->assertEquals(14, $siblings[2]->getKey()); - } - - public function testsCountPrevSiblings() - { - $entity = Entity::find(15); - $siblings = $entity->countPrevSiblings(); - - $this->assertEquals(3, $siblings); - } - - public function testsHasPrevSiblings() - { - $entity = Entity::find(15); - $hasPrevSiblings = $entity->hasPrevSiblings(); - - $this->assertTrue($hasPrevSiblings); - } - - public function testGetNextSibling() - { - $entity = Entity::find(10); - $sibling = $entity->getNextSibling(); - - $this->assertEquals(13, $sibling->getKey()); - } - - public function testGetNextSiblings() - { - $entity = Entity::find(10); - $siblings = $entity->getNextSiblings(); - - $this->assertCount(3, $siblings); - $this->assertEquals(13, $siblings[0]->getKey()); - $this->assertEquals(14, $siblings[1]->getKey()); - $this->assertEquals(15, $siblings[2]->getKey()); - } - - public function testCountNextSiblings() - { - $entity = Entity::find(10); - $siblings = $entity->countNextSiblings(); - - $this->assertEquals(3, $siblings); - } - - public function testsHasNextSiblings() - { - $entity = Entity::find(10); - $hasNextSiblings = $entity->hasNextSiblings(); - - $this->assertTrue($hasNextSiblings); - } - - public function testGetSiblingsRange() - { - $entity = Entity::find(15); - $siblings = $entity->getSiblingsRange(1, 2); - - $this->assertCount(2, $siblings); - $this->assertEquals(1, $siblings[0]->position); - $this->assertEquals(2, $siblings[1]->position); - } - public function testAddSibling() { $entity = Entity::find(15); From 25f51e199c8330b50fc866b48aa0d1b7bfb969b8 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 22:35:26 +0700 Subject: [PATCH 024/113] refactored addChildren to match signature of the addSiblings method --- .../Contracts/EntityInterface.php | 3 ++- src/Franzose/ClosureTable/Models/Entity.php | 23 ++++--------------- tests/Entity/ChildManipulationTests.php | 4 ++-- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/Franzose/ClosureTable/Contracts/EntityInterface.php b/src/Franzose/ClosureTable/Contracts/EntityInterface.php index 0a8da2f..23ff7c9 100644 --- a/src/Franzose/ClosureTable/Contracts/EntityInterface.php +++ b/src/Franzose/ClosureTable/Contracts/EntityInterface.php @@ -235,10 +235,11 @@ public function addChild(EntityInterface $child, $position = null, $returnChild * Appends multiple children to the model. * * @param array $children + * @param int $from * @return $this * @throws \InvalidArgumentException */ - public function addChildren(array $children); + public function addChildren(array $children, $from = null); /** * Removes a model's child with given position. diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 3f45af8..1856d39 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -694,35 +694,22 @@ public function addChild(EntityInterface $child, $position = null, $returnChild * Appends a collection of children to the model. * * @param Entity[] $children + * @param int $from * * @return Entity * @throws InvalidArgumentException * @throws Throwable */ - public function addChildren(array $children) - { - return $this->insertChildren($this->getLastChildPosition(), ...$children); - } - - /** - * Inserts children nodes starting from the specified position. - * - * @param int $position - * @param Entity[] $children - * - * @return Entity - * @throws Throwable - */ - public function insertChildren($position, Entity... $children) + public function addChildren(array $children, $from = null) { if (!$this->exists) { return $this; } - $this->getConnection()->transaction(function () use (&$position, $children) { + $this->getConnection()->transaction(function () use (&$from, $children) { foreach ($children as $child) { - $this->addChild($child, $position); - $position++; + $this->addChild($child, $from); + $from++; } }); diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Entity/ChildManipulationTests.php index a65aeaf..f6e761a 100644 --- a/tests/Entity/ChildManipulationTests.php +++ b/tests/Entity/ChildManipulationTests.php @@ -73,13 +73,13 @@ public function testAddChildren() static::assertEquals(2, $child3->position); } - public function testInsertChildren() + public function testAddChildrenFromPosition() { $entity = Entity::find(9); $child1 = new Entity(); $child2 = new Entity(); - $entity->insertChildren(1, $child1, $child2); + $entity->addChildren([$child1, $child2], 1); static::assertEquals(6, $entity->countChildren()); static::assertEquals([0, 3, 4, 5], static::getPositionsByIds([10, 13, 14, 15])); From 6d1af7396ae05137e09eeefb9a9ff1c2b8c93e4c Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 23:12:21 +0700 Subject: [PATCH 025/113] fixed position parameter --- src/Franzose/ClosureTable/Models/Entity.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 1856d39..453fdfd 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -682,7 +682,7 @@ public function getChildrenRange($from, $to = null, array $columns = ['*']) public function addChild(EntityInterface $child, $position = null, $returnChild = false) { if ($this->exists) { - $position = $position ?: $this->getLatestPosition(); + $position = $position === null ? $this->getLatestPosition() : $position; $child->moveTo($position, $this); } @@ -1180,9 +1180,7 @@ public function getSiblingsRange($from, $to = null, array $columns = ['*']) public function addSibling(EntityInterface $sibling, $position = null, $returnSibling = false) { if ($this->exists) { - if ($position === null) { - $position = $this->getLatestPosition(); - } + $position = $position === null ? $this->getLatestPosition() : $position; $sibling->moveTo($position, $this->parent_id); } From 170037ae62cf402c91cba14b7a2bb837154dc7d5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 3 Apr 2020 23:47:37 +0700 Subject: [PATCH 026/113] refactored and tested sibling manipulation --- src/Franzose/ClosureTable/Models/Entity.php | 34 +++++----- tests/Entity/SiblingManipulationTests.php | 75 +++++++++++++++++++++ tests/EntityTestCase.php | 38 ----------- 3 files changed, 94 insertions(+), 53 deletions(-) create mode 100644 tests/Entity/SiblingManipulationTests.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 453fdfd..1c4dcbd 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -1149,11 +1149,11 @@ public function scopeSiblingsRange(Builder $builder, $from, $to = null) ->scopeSiblings($builder) ->where($position, '>=', $from); - if ($to === null) { - return $query; + if ($to !== null) { + $query->where($position, '<=', $to); } - return $query->where($position, '<=', $to); + return $query; } /** @@ -1183,6 +1183,10 @@ public function addSibling(EntityInterface $sibling, $position = null, $returnSi $position = $position === null ? $this->getLatestPosition() : $position; $sibling->moveTo($position, $this->parent_id); + + if ($position < $this->position) { + $this->position++; + } } return ($returnSibling === true ? $sibling : $this); @@ -1191,26 +1195,26 @@ public function addSibling(EntityInterface $sibling, $position = null, $returnSi /** * Appends multiple siblings within the current depth. * - * @param array $siblings + * @param Entity[] $siblings * @param int|null $from - * @return $this + * + * @return Entity + * @throws Throwable */ public function addSiblings(array $siblings, $from = null) { - if ($this->exists) { - if ($from === null) { - $from = $this->getLatestPosition(); - } + if (!$this->exists) { + return $this; + } - $parent = $this->getParent(); - /** - * @var Entity $sibling - */ + $from = $from === null ? $this->getLatestPosition() : $from; + + $this->getConnection()->transaction(function () use ($siblings, &$from) { foreach ($siblings as $sibling) { - $sibling->moveTo($from, $parent); + $this->addSibling($sibling, $from); $from++; } - } + }); return $this; } diff --git a/tests/Entity/SiblingManipulationTests.php b/tests/Entity/SiblingManipulationTests.php new file mode 100644 index 0000000..344f00d --- /dev/null +++ b/tests/Entity/SiblingManipulationTests.php @@ -0,0 +1,75 @@ +addSibling(new Page(['title' => 'Foo!'])); + + $sibling = $entity->getNextSibling(); + static::assertEquals(4, $sibling->position); + static::assertEquals('Foo!', $sibling->title); + } + + public function testAddSiblingAtPosition() + { + $entity = Entity::find(15); + $sibling = new Page(['title' => 'Foo!']); + + $entity->addSibling($sibling, 1); + + static::assertEquals(16, $sibling->getKey()); + static::assertEquals(16, Entity::find(10)->getNextSibling()->getKey()); + static::assertEquals(1, $sibling->position); + } + + public function testAddSiblings() + { + $entity = Entity::find(15); + $entity->addSiblings([ + new Page(['title' => 'One']), + new Page(['title' => 'Two']), + new Page(['title' => 'Three']), + new Page(['title' => 'Four']), + ]); + + $siblings = $entity->getNextSiblings(); + + static::assertCount(4, $siblings); + static::assertEquals(4, $siblings->get(0)->position); + static::assertEquals(5, $siblings->get(1)->position); + static::assertEquals(6, $siblings->get(2)->position); + static::assertEquals(7, $siblings->get(3)->position); + } + + public function testAddSiblingsFromPosition() + { + $entity = Entity::find(15); + + $entity->addSiblings([ + new Page(['title' => 'One']), + new Page(['title' => 'Two']), + new Page(['title' => 'Three']), + new Page(['title' => 'Four']), + ], 1); + + $siblings = $entity->getSiblingsRange(1, 4); + + static::assertEquals(0, Entity::find(10)->position); + static::assertEquals('One', $siblings->get(0)->title); + static::assertEquals('Two', $siblings->get(1)->title); + static::assertEquals('Three', $siblings->get(2)->title); + static::assertEquals('Four', $siblings->get(3)->title); + static::assertEquals(5, Entity::find(13)->position); + static::assertEquals(6, Entity::find(14)->position); + static::assertEquals(7, Entity::find(15)->position); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index d2efe6a..f17d548 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -191,44 +191,6 @@ public function testGetParentAfterMovingToAnAncestor() $this->assertEquals(15, $parent->getKey()); } - public function testAddSibling() - { - $entity = Entity::find(15); - $entity->addSibling(new Entity); - - $sibling = $entity->getNextSibling(); - - $this->assertInstanceOf('Franzose\ClosureTable\Models\Entity', $sibling); - $this->assertEquals(4, $sibling->position); - } - - public function testAddSiblings() - { - $entity = Entity::find(15); - $entity->addSiblings([new Entity, new Entity, new Entity]); - - $siblings = $entity->getNextSiblings(); - - $this->assertCount(3, $siblings); - $this->assertEquals(4, $siblings[0]->position); - $this->assertEquals(5, $siblings[1]->position); - $this->assertEquals(6, $siblings[2]->position); - } - - public function testAddSiblingsFromPosition() - { - $entity = Entity::find(15); - - $entity->addSiblings([new Entity, new Entity, new Entity, new Entity], 1); - - $siblings = $entity->getSiblingsRange(1, 4); - - $this->assertEquals(16, $siblings[0]->getKey()); - $this->assertEquals(17, $siblings[1]->getKey()); - $this->assertEquals(18, $siblings[2]->getKey()); - $this->assertEquals(19, $siblings[3]->getKey()); - } - public function testGetTree() { $tree = Entity::getTree(); From 548e9a04da52ae5f82ea59f62122e8bb0ad428cd Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 10:29:38 +0700 Subject: [PATCH 027/113] changed scope so that it would be impossible to query sibling that is the same node --- src/Franzose/ClosureTable/Models/Entity.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 1c4dcbd..00bce6b 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -903,7 +903,7 @@ public function scopeNeighbors(Builder $builder) $position = $this->position; return $this - ->scopeSibling($builder) + ->scopeSiblings($builder) ->whereIn($this->getPositionColumn(), [$position - 1, $position + 1]); } @@ -930,7 +930,7 @@ public function getNeighbors(array $columns = ['*']) public function scopeSiblingAt(Builder $builder, $position) { return $this - ->scopeSibling($builder) + ->scopeSiblings($builder) ->where($this->getPositionColumn(), '=', $position); } @@ -976,7 +976,7 @@ public function getFirstSibling(array $columns = ['*']) */ public function scopeLastSibling(Builder $builder) { - return $this->scopeSibling($builder)->orderByDesc($this->getPositionColumn()); + return $this->scopeSiblings($builder)->orderByDesc($this->getPositionColumn()); } /** From 2855e3d60950554c9d50318345e461cbd9bdaa6f Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 10:42:53 +0700 Subject: [PATCH 028/113] refactored position asserts --- tests/BaseTestCase.php | 10 ++++++++++ tests/Entity/ChildManipulationTests.php | 12 ++---------- tests/Entity/SiblingManipulationTests.php | 4 +--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 4cef077..289617f 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -68,4 +68,14 @@ protected function assertArrayValuesEquals(array $actual, array $expected, $mess { $this->assertEquals($actual, $expected, $message, $delta, $depth, true); } + + public static function assertPositions(array $expectedPositions, array $entityIds) + { + $actualPositions = Entity::whereIn('id', $entityIds) + ->get(['position']) + ->pluck('position') + ->toArray(); + + static::assertEquals($expectedPositions, $actualPositions); + } } diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Entity/ChildManipulationTests.php index f6e761a..b11a3d5 100644 --- a/tests/Entity/ChildManipulationTests.php +++ b/tests/Entity/ChildManipulationTests.php @@ -38,7 +38,7 @@ public function testAddChildToTheLastPosition() static::assertEquals(9, $child->parent_id); static::assertEquals(4, $child->position); - static::assertEquals([0, 1, 2, 3], static::getPositionsByIds([10, 13, 14, 15])); + static::assertPositions([0, 1, 2, 3], [10, 13, 14, 15]); } public function testAddChildToPosition() @@ -50,7 +50,7 @@ public function testAddChildToPosition() static::assertEquals(9, $child->parent_id); static::assertEquals(2, $child->position); - static::assertEquals([0, 1, 3, 4], static::getPositionsByIds([10, 13, 14, 15])); + static::assertPositions([0, 1, 3, 4], [10, 13, 14, 15]); } public function testAddChildren() @@ -114,12 +114,4 @@ public function testRemoveChildrenToTheEnd() static::assertEquals(1, $entity->countChildren()); static::assertEquals(10, $entity->getFirstChild()->getKey()); } - - public static function getPositionsByIds(array $entityIds) - { - return Entity::whereIn('id', $entityIds) - ->get(['position']) - ->pluck('position') - ->toArray(); - } } diff --git a/tests/Entity/SiblingManipulationTests.php b/tests/Entity/SiblingManipulationTests.php index 344f00d..cbebc19 100644 --- a/tests/Entity/SiblingManipulationTests.php +++ b/tests/Entity/SiblingManipulationTests.php @@ -68,8 +68,6 @@ public function testAddSiblingsFromPosition() static::assertEquals('Two', $siblings->get(1)->title); static::assertEquals('Three', $siblings->get(2)->title); static::assertEquals('Four', $siblings->get(3)->title); - static::assertEquals(5, Entity::find(13)->position); - static::assertEquals(6, Entity::find(14)->position); - static::assertEquals(7, Entity::find(15)->position); + static::assertPositions([0, 5, 6, 7], [10, 13, 14, 15]); } } From add06dc5d2b03ac66f1fb5f8db6946ac72be0a80 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 14:05:31 +0700 Subject: [PATCH 029/113] removed unused method --- src/Franzose/ClosureTable/Models/Entity.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 00bce6b..8f40971 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -716,18 +716,6 @@ public function addChildren(array $children, $from = null) return $this; } - /** - * Gets last child position. - * - * @return int - */ - protected function getLastChildPosition() - { - $lastChild = $this->getLastChild([$this->getPositionColumn()]); - - return $lastChild === null ? 0 : $lastChild->position; - } - /** * Removes a model's child with given position. * From fed13027d252979a28a7a4c06a0b845b3e521216 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 15:34:50 +0700 Subject: [PATCH 030/113] refactored child node scopes so that they returned query builder instead of relation --- src/Franzose/ClosureTable/Models/Entity.php | 61 ++++++++++++++------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 8f40971..98aacdd 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -21,10 +21,11 @@ * @property int parent_id Alias for the direct ancestor identifier attribute name * @property int real_depth Alias for the real depth attribute name * @property Collection children Child nodes loaded from the database - * @method HasMany childAt(int $position) - * @method HasMany firstChild() - * @method HasMany lastChild() - * @method HasMany childrenRange(int $from, int $to = null) + * @method Builder childNode() + * @method Builder childAt(int $position) + * @method Builder firstChild() + * @method Builder lastChild() + * @method Builder childrenRange(int $from, int $to = null) * @method Builder sibling() * @method Builder siblings() * @method Builder neighbors() @@ -571,16 +572,34 @@ public function hasChildrenRelation() } /** - * Returns relationship to a child at the given position. + * Returns query builder for child nodes. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeChildNode(Builder $builder) + { + $parentId = $this->getParentIdColumn(); + + return $builder + ->whereNotNull($parentId) + ->where($parentId, '=', $this->getKey()); + } + + /** + * Returns query builder for a child at the given position. * * @param Builder $builder * @param int $position * - * @return HasMany + * @return Builder */ - public function scopeChildAt($builder, $position) + public function scopeChildAt(Builder $builder, $position) { - return $this->children()->where($this->getPositionColumn(), '=', $position); + return $this + ->scopeChildNode($builder) + ->where($this->getPositionColumn(), '=', $position); } /** @@ -596,13 +615,15 @@ public function getChildAt($position, array $columns = ['*']) } /** - * Returns relationship to the first child node. + * Returns query builder for the first child node. * - * @return HasMany + * @param Builder $builder + * + * @return Builder */ - public function scopeFirstChild() + public function scopeFirstChild(Builder $builder) { - return $this->childAt(0); + return $this->scopeChildAt($builder, 0); } /** @@ -617,13 +638,15 @@ public function getFirstChild(array $columns = ['*']) } /** - * Returns relationship to the last child node. + * Returns query builder for the last child node. * - * @return HasMany + * @param Builder $builder + * + * @return Builder */ - public function scopeLastChild() + public function scopeLastChild(Builder $builder) { - return $this->children()->orderByDesc($this->getPositionColumn()); + return $this->scopeChildNode($builder)->orderByDesc($this->getPositionColumn()); } /** @@ -644,12 +667,12 @@ public function getLastChild(array $columns = ['*']) * @param int $from * @param int|null $to * - * @return HasMany + * @return Builder */ - public function scopeChildrenRange($builder, $from, $to = null) + public function scopeChildrenRange(Builder $builder, $from, $to = null) { $position = $this->getPositionColumn(); - $query = $this->children()->where($position, '>=', $from); + $query = $this->scopeChildNode($builder)->where($position, '>=', $from); if ($to !== null) { $query->where($position, '<=', $to); From 43bceaad9a0c32fca69e338d77a680e49ce54e29 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 16:19:57 +0700 Subject: [PATCH 031/113] made siblings to reorder in case of child nodes removal --- src/Franzose/ClosureTable/Models/Entity.php | 32 ++++++++++++++++++--- tests/Entity/ChildManipulationTests.php | 11 +++++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 98aacdd..ab5df25 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -744,7 +744,9 @@ public function addChildren(array $children, $from = null) * * @param int $position * @param bool $forceDelete + * * @return $this + * @throws Throwable */ public function removeChild($position = null, $forceDelete = false) { @@ -752,9 +754,23 @@ public function removeChild($position = null, $forceDelete = false) return $this; } - $action = ($forceDelete === true ? 'forceDelete' : 'delete'); + $child = $this->getChildAt($position, [ + $this->getKeyName(), + $this->getParentIdColumn(), + $this->getPositionColumn() + ]); + + if ($child === null) { + return $this; + } + + $this->getConnection()->transaction(static function () use ($child, $forceDelete) { + $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - $this->childAt($position)->{$action}(); + $child->{$action}(); + + $child->nextSiblings()->decrement($this->getPositionColumn()); + }); return $this; } @@ -778,9 +794,17 @@ public function removeChildren($from, $to = null, $forceDelete = false) return $this; } - $action = ($forceDelete === true ? 'forceDelete' : 'delete'); + $this->getConnection()->transaction(function () use ($from, $to, $forceDelete) { + $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - $this->childrenRange($from, $to)->{$action}(); + $this->childrenRange($from, $to)->{$action}(); + + if ($to !== null) { + $this + ->childrenRange($to) + ->decrement($this->getPositionColumn(), $to - $from + 1); + } + }); return $this; } diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Entity/ChildManipulationTests.php index b11a3d5..7ec2190 100644 --- a/tests/Entity/ChildManipulationTests.php +++ b/tests/Entity/ChildManipulationTests.php @@ -95,14 +95,18 @@ public function testRemoveChild() static::assertNull(Entity::find(10)); static::assertEquals(3, $entity->countChildren()); + static::assertPositions([0, 1, 2], [13, 14, 15]); } public function testRemoveChildren() { $entity = Entity::find(9); + $entity->addChild(new Entity()); + $entity->removeChildren(0, 2); - static::assertEquals(1, $entity->countChildren()); + static::assertEquals(2, $entity->countChildren()); + static::assertPositions([0, 1], [15, 16]); } public function testRemoveChildrenToTheEnd() @@ -112,6 +116,9 @@ public function testRemoveChildrenToTheEnd() $entity->removeChildren(1); static::assertEquals(1, $entity->countChildren()); - static::assertEquals(10, $entity->getFirstChild()->getKey()); + + $firstChild = $entity->getFirstChild(); + static::assertEquals(10, $firstChild->getKey()); + static::assertEquals(0, $firstChild->position); } } From 8d925935875d2d293cf5f493fb508bb93c4e6422 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 16:23:19 +0700 Subject: [PATCH 032/113] added convenient helper method --- src/Franzose/ClosureTable/Models/Entity.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index ab5df25..1083c3f 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -729,7 +729,7 @@ public function addChildren(array $children, $from = null) return $this; } - $this->getConnection()->transaction(function () use (&$from, $children) { + $this->transactional(function () use (&$from, $children) { foreach ($children as $child) { $this->addChild($child, $from); $from++; @@ -764,7 +764,7 @@ public function removeChild($position = null, $forceDelete = false) return $this; } - $this->getConnection()->transaction(static function () use ($child, $forceDelete) { + $this->transactional(static function () use ($child, $forceDelete) { $action = ($forceDelete === true ? 'forceDelete' : 'delete'); $child->{$action}(); @@ -794,7 +794,7 @@ public function removeChildren($from, $to = null, $forceDelete = false) return $this; } - $this->getConnection()->transaction(function () use ($from, $to, $forceDelete) { + $this->transactional(function () use ($from, $to, $forceDelete) { $action = ($forceDelete === true ? 'forceDelete' : 'delete'); $this->childrenRange($from, $to)->{$action}(); @@ -1244,7 +1244,7 @@ public function addSiblings(array $siblings, $from = null) $from = $from === null ? $this->getLatestPosition() : $from; - $this->getConnection()->transaction(function () use ($siblings, &$from) { + $this->transactional(function () use ($siblings, &$from) { foreach ($siblings as $sibling) { $this->addSibling($sibling, $from); $from++; @@ -1660,4 +1660,17 @@ protected function newBaseQueryBuilder() return new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); } + + /** + * Executes queries within a transaction. + * + * @param callable $callable + * + * @return mixed + * @throws Throwable + */ + private function transactional(callable $callable) + { + return $this->getConnection()->transaction($callable); + } } From b63dc604b6b62954f36a6edaaafc1760207d5140 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 17:50:50 +0700 Subject: [PATCH 033/113] wrote test for the makeRoot() method --- tests/Entity/ParentRootTests.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Entity/ParentRootTests.php b/tests/Entity/ParentRootTests.php index e2e3734..18d3823 100644 --- a/tests/Entity/ParentRootTests.php +++ b/tests/Entity/ParentRootTests.php @@ -44,4 +44,15 @@ public function testGetRoots() static::assertEquals($idx + 1, $roots->get($idx)->getKey()); } } + + public function testMakeRoot() + { + $child = Entity::find(13); + + $child->makeRoot(4); + + static::assertTrue($child->isRoot()); + static::assertPositions([0, 1, 2], [10, 14, 15]); + static::assertPositions([5, 6, 7, 8, 9], [5, 6, 7, 8, 9]); + } } From 7846779b36a368156f502234e2392e3621fd1328 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 17:56:03 +0700 Subject: [PATCH 034/113] fixed entity and tests --- src/Franzose/ClosureTable/Models/Entity.php | 2 +- tests/Entity/ChildManipulationTests.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 1083c3f..f19b80d 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -764,7 +764,7 @@ public function removeChild($position = null, $forceDelete = false) return $this; } - $this->transactional(static function () use ($child, $forceDelete) { + $this->transactional(function () use ($child, $forceDelete) { $action = ($forceDelete === true ? 'forceDelete' : 'delete'); $child->{$action}(); diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Entity/ChildManipulationTests.php index 7ec2190..7f1d7c3 100644 --- a/tests/Entity/ChildManipulationTests.php +++ b/tests/Entity/ChildManipulationTests.php @@ -82,7 +82,7 @@ public function testAddChildrenFromPosition() $entity->addChildren([$child1, $child2], 1); static::assertEquals(6, $entity->countChildren()); - static::assertEquals([0, 3, 4, 5], static::getPositionsByIds([10, 13, 14, 15])); + static::assertPositions([0, 3, 4, 5], [10, 13, 14, 15]); static::assertEquals(1, $child1->position); static::assertEquals(2, $child2->position); } From 1e7f450738f25ec7274f4190f449107a64a3b2da Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 5 Apr 2020 19:40:41 +0700 Subject: [PATCH 035/113] corrected assertion --- tests/BaseTestCase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 289617f..d43f3f8 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -72,10 +72,10 @@ protected function assertArrayValuesEquals(array $actual, array $expected, $mess public static function assertPositions(array $expectedPositions, array $entityIds) { $actualPositions = Entity::whereIn('id', $entityIds) - ->get(['position']) - ->pluck('position') + ->get(['id', 'position']) + ->pluck('position', 'id') ->toArray(); - static::assertEquals($expectedPositions, $actualPositions); + static::assertEquals(array_combine($entityIds, $expectedPositions), $actualPositions); } } From e745e0edbadd220e281a8f4b6aea9240f206ba20 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 18:25:45 +0700 Subject: [PATCH 036/113] fixed tests --- tests/Entity/ConstructionTests.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Entity/ConstructionTests.php b/tests/Entity/ConstructionTests.php index a7f84af..959b6a3 100644 --- a/tests/Entity/ConstructionTests.php +++ b/tests/Entity/ConstructionTests.php @@ -52,8 +52,8 @@ public function testNewFromBuilder() 'real_depth' => 0 ]); - static::assertEquals(321, static::readAttribute($newEntity, 'old_parent_id')); - static::assertEquals(0, static::readAttribute($newEntity, 'old_position')); - static::assertEquals(0, static::readAttribute($newEntity, 'old_real_depth')); + static::assertEquals(321, static::readAttribute($newEntity, 'previousParentId')); + static::assertEquals(0, static::readAttribute($newEntity, 'previousPosition')); + static::assertEquals(0, static::readAttribute($newEntity, 'previousRealDepth')); } } From d1a93a3c0e9e3ec8d9feb59800b38413dbe83cd3 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 19:33:57 +0700 Subject: [PATCH 037/113] fixed various positioning bugs and improved tests --- src/Franzose/ClosureTable/Models/Entity.php | 295 ++++++-------------- tests/BaseTestCase.php | 10 +- tests/Entity/ChildManipulationTests.php | 120 +++++++- tests/Entity/ConstructionTests.php | 9 + tests/Entity/ParentRootTests.php | 12 +- tests/Entity/PositioningTests.php | 69 +++++ tests/Entity/SiblingManipulationTests.php | 15 +- 7 files changed, 303 insertions(+), 227 deletions(-) create mode 100644 tests/Entity/PositioningTests.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index f19b80d..09bbff6 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -200,7 +200,7 @@ public function setPositionAttribute($value) return; } $this->previousPosition = $this->position; - $this->attributes[$this->getPositionColumn()] = (int) $value; + $this->attributes[$this->getPositionColumn()] = max(0, (int) $value); } /** @@ -286,26 +286,65 @@ public static function boot() { parent::boot(); - // If model's parent identifier was changed, - // the closure table rows will update automatically. - static::saving(function (Entity $entity) { - $entity->clampPosition(); - $entity->moveNode(); + static::saving(static function (Entity $entity) { + if (!$entity->isDirty($entity->getPositionColumn())) { + return; + } + + $newPosition = max(0, min($entity->position, $entity->getLatestPosition())); + $entity->attributes[$entity->getPositionColumn()] = $newPosition; + }); + + static::creating(static function (Entity $entity) { + if ($entity->isMoved) { + return; + } + + $entity->position = $entity->position !== null + ? $entity->position + : $entity->getLatestPosition(); + + $entity->real_depth = $entity->getNewRealDepth($entity->parent_id); }); // When entity is created, the appropriate // data will be put into the closure table. - static::created(function (Entity $entity) { - $entity->previousParentId = false; - $entity->previousPosition = $entity->position; - $entity->insertNode(); + static::created(static function (Entity $entity) { + $entity->previousParentId = null; + $entity->previousPosition = null; + $entity->previousRealDepth = null; + + $descendant = $entity->getKey(); + $ancestor = isset($entity->parent_id) ? $entity->parent_id : $descendant; + + $entity->closure->insertNode($ancestor, $descendant); }); - // Everytime the model's position or parent - // is changed, its siblings reordering will happen, - // so they will always keep the proper order. - static::saved(function (Entity $entity) { - $entity->reorderSiblings(); + static::saved(static function (Entity $entity) { + if ($entity->isDirty($entity->getRealDepthColumn())) { + $action = $entity->real_depth > $entity->previousRealDepth ? 'increment' : 'decrement'; + $amount = abs($entity->real_depth - $entity->previousRealDepth); + + $entity->subqueryClosureBy('descendant') + ->$action($entity->getRealDepthColumn(), $amount); + } + + $parentIdChanged = $entity->isDirty($entity->getParentIdColumn()); + + if ($parentIdChanged || $entity->isDirty($entity->getPositionColumn())) { + $entity->reorderSiblings(); + } + + if ($entity->closure->ancestor === null) { + $primaryKey = $entity->getKey(); + $entity->closure->ancestor = $primaryKey; + $entity->closure->descendant = $primaryKey; + $entity->closure->depth = 0; + } + + if ($parentIdChanged) { + $entity->closure->moveNodeTo($entity->parent_id); + } }); } @@ -705,7 +744,7 @@ public function getChildrenRange($from, $to = null, array $columns = ['*']) public function addChild(EntityInterface $child, $position = null, $returnChild = false) { if ($this->exists) { - $position = $position === null ? $this->getLatestPosition() : $position; + $position = $position !== null ? $position : $this->getLatestChildPosition(); $child->moveTo($position, $this); } @@ -713,6 +752,18 @@ public function addChild(EntityInterface $child, $position = null, $returnChild return $returnChild === true ? $child : $this; } + /** + * Returns the latest child position. + * + * @return int + */ + private function getLatestChildPosition() + { + $lastChild = $this->lastChild()->first([$this->getPositionColumn()]); + + return $lastChild !== null ? $lastChild->position + 1 : 0; + } + /** * Appends a collection of children to the model. * @@ -781,8 +832,10 @@ public function removeChild($position = null, $forceDelete = false) * @param int $from * @param int $to * @param bool $forceDelete + * * @return $this * @throws InvalidArgumentException + * @throws Throwable */ public function removeChildren($from, $to = null, $forceDelete = false) { @@ -809,65 +862,6 @@ public function removeChildren($from, $to = null, $forceDelete = false) return $this; } - /** - * Builds a part of the siblings query. - * - * @param string|int|array $direction - * @param int|bool $parentId - * @param string $order - * - * @return QueryBuilder - */ - protected function siblingsQuery($direction = '', $parentId = false, $order = 'asc') - { - $parentId = ($parentId === false ? $this->parent_id : $parentId); - - /** - * @var QueryBuilder $query - */ - $query = $this->where($this->getParentIdColumn(), '=', $parentId); - - $column = $this->getPositionColumn(); - - switch ($direction) { - case static::QUERY_ALL: - $query->where($column, '<>', $this->position)->orderBy($column, $order); - break; - - case static::QUERY_PREV_ALL: - $query->where($column, '<', $this->position)->orderBy($column, $order); - break; - - case static::QUERY_PREV_ONE: - $query->where($column, '=', $this->position - 1); - break; - - case static::QUERY_NEXT_ALL: - $query->where($column, '>', $this->position)->orderBy($column, $order); - break; - - case static::QUERY_NEXT_ONE: - $query->where($column, '=', $this->position + 1); - break; - - case static::QUERY_NEIGHBORS: - $query->whereIn($column, [$this->position - 1, $this->position + 1]); - break; - - case static::QUERY_LAST: - $query->orderBy($column, 'desc'); - break; - } - - if (is_int($direction)) { - $query->where($column, '=', $direction); - } else if (is_array($direction)) { - $query->buildWherePosition($this->getPositionColumn(), $direction); - } - - return $query; - } - /** * Returns sibling query builder. * @@ -1437,48 +1431,6 @@ protected function getNewRealDepth($ancestor) } } - /** - * Perform a model insert operation. - * - * @param EloquentBuilder $query - * @param array $options - * - * @return bool - */ - protected function performInsert(Builder $query, array $options = []) - { - if ($this->isMoved === false) { - $this->position = $this->position !== null ? $this->position : $this->getLatestPosition(); - $this->real_depth = $this->getNewRealDepth($this->parent_id); - } - - return parent::performInsert($query, $options); - } - - /** - * Perform a model update operation. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param array $options - * - * @return bool - */ - protected function performUpdate(Builder $query, array $options = []) - { - if (parent::performUpdate($query, $options)) { - if ($this->real_depth != $this->previousRealDepth && $this->isMoved === true) { - $action = ($this->real_depth > $this->previousRealDepth ? 'increment' : 'decrement'); - $amount = abs($this->real_depth - $this->previousRealDepth); - - $this->subqueryClosureBy('descendant')->$action($this->getRealDepthColumn(), $amount); - } - - return true; - } - - return false; - } - /** * Gets the next sibling position after the last one. * @@ -1500,109 +1452,26 @@ private function getLatestPosition() } /** - * Reorders model's siblings when one is moved to another position or ancestor. + * Reorders node's siblings when it is moved to another position or ancestor. * - * @param bool $parentIdChanged * @return void */ - protected function reorderSiblings($parentIdChanged = false) + private function reorderSiblings() { - list($range, $action) = $this->setupReordering($parentIdChanged); - - $positionColumn = $this->getPositionColumn(); - - // As the method called twice (before moving and after moving), - // first we gather "old" siblings by the old parent id value of the model. - if ($parentIdChanged === true) { - $query = $this->siblingsQuery(false, $this->previousParentId); - } else { - $query = $this->siblingsQuery(); - } - - if ($action) { - $query->buildWherePosition($positionColumn, $range) - ->where($this->getKeyName(), '<>', $this->getKey()) - ->$action($positionColumn); - } - } - - /** - * Setups model's siblings reordering. - * - * Actually, the method determines siblings that will be reordered - * by creating range of theirs positions and determining the action - * that will be used in reordering ('increment' or 'decrement'). - * - * @param bool $parentIdChanged - * @return array - */ - protected function setupReordering($parentIdChanged) - { - $range = $action = null; - // If the model's parent was changed, firstly we decrement - // positions of the 'old' next siblings of the model. - if ($parentIdChanged === true) { - $range = $this->previousPosition; - $action = 'decrement'; - } else { - // TODO: There's probably a bug here where if you just created an entity and you set it to be - // a root (parent_id = null) then it comes in here (while it should have gone in the else) - // Reordering within the same ancestor - if ($this->previousParentId !== false && $this->previousParentId == $this->parent_id) { - if ($this->position > $this->previousPosition) { - $range = [$this->previousPosition, $this->position]; - $action = 'decrement'; - } else if ($this->position < $this->previousPosition) { - $range = [$this->position, $this->previousPosition]; - $action = 'increment'; - } - } // Ancestor has changed - else { - $range = $this->position; - $action = 'increment'; - } - } + $position = $this->getPositionColumn(); - if (!is_array($range)) { - $range = [$range, null]; + if ($this->previousPosition !== null) { + $this + ->where($this->getParentIdColumn(), '=', $this->previousParentId) + ->where($position, '>', $this->previousPosition) + ->decrement($position); } - return [$range, $action]; - } - - /** - * Inserts new node to closure table. - * - * @return void - */ - protected function insertNode() - { - $descendant = $this->getKey(); - $ancestor = (isset($this->parent_id) ? $this->parent_id : $descendant); - - $this->closure->insertNode($ancestor, $descendant); - } - - /** - * Moves node to another ancestor. - * - * @return void - */ - protected function moveNode() - { - if ($this->exists) { - if ($this->closure->ancestor === null) { - $primaryKey = $this->getKey(); - $this->closure->ancestor = $primaryKey; - $this->closure->descendant = $primaryKey; - $this->closure->depth = 0; - } - - if ($this->isDirty($this->getParentIdColumn())) { - $this->reorderSiblings(true); - $this->closure->moveNodeTo($this->parent_id); - } - } + $this + ->sibling() + ->where($this->getKeyName(), '<>', $this->getKey()) + ->where($position, '>=', $this->position) + ->increment($position); } /** diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index d43f3f8..a4b3994 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -69,13 +69,13 @@ protected function assertArrayValuesEquals(array $actual, array $expected, $mess $this->assertEquals($actual, $expected, $message, $delta, $depth, true); } - public static function assertPositions(array $expectedPositions, array $entityIds) + public static function assertModelAttribute($attribute, array $expected) { - $actualPositions = Entity::whereIn('id', $entityIds) - ->get(['id', 'position']) - ->pluck('position', 'id') + $actual = Entity::whereIn('id', array_keys($expected)) + ->get(['id', $attribute]) + ->pluck($attribute, 'id') ->toArray(); - static::assertEquals(array_combine($entityIds, $expectedPositions), $actualPositions); + static::assertEquals($expected, $actual); } } diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Entity/ChildManipulationTests.php index 7f1d7c3..835651e 100644 --- a/tests/Entity/ChildManipulationTests.php +++ b/tests/Entity/ChildManipulationTests.php @@ -19,6 +19,41 @@ public function testAddChild() static::assertTrue($leaf->isParent()); } + public function testAddChild2() + { + $parent = Entity::find(11); + $child = Entity::find(13); + + $parent->addChild($child, 0); + + static::assertEquals(11, $child->parent_id); + static::assertEquals(0, $child->position); + static::assertModelAttribute('position', [ + 10 => 0, + 14 => 1, + 15 => 2, + 11 => 0, + 12 => 1 + ]); + } + + public function testAddChildReordersNodesOnThePreviousLevel() + { + $parent = Entity::find(13); + $child = Entity::find(5); + + $parent->addChild($child); + + static::assertModelAttribute('position', [ + 5 => 0, + // previous level nodes + 6 => 4, + 7 => 5, + 8 => 6, + 9 => 7, + ]); + } + public function testAddChildShouldReturnChild() { $leaf = Entity::find(14); @@ -38,7 +73,12 @@ public function testAddChildToTheLastPosition() static::assertEquals(9, $child->parent_id); static::assertEquals(4, $child->position); - static::assertPositions([0, 1, 2, 3], [10, 13, 14, 15]); + static::assertModelAttribute('position', [ + 10 => 0, + 13 => 1, + 14 => 2, + 15 => 3 + ]); } public function testAddChildToPosition() @@ -50,7 +90,32 @@ public function testAddChildToPosition() static::assertEquals(9, $child->parent_id); static::assertEquals(2, $child->position); - static::assertPositions([0, 1, 3, 4], [10, 13, 14, 15]); + static::assertModelAttribute('position', [ + 10 => 0, + 13 => 1, + 12 => 2, + 14 => 3, + 15 => 4 + ]); + } + + public function testAddChildHavingChildren() + { + $parent = Entity::find(13); + $child = Entity::find(10); + + $parent->addChild($child); + + static::assertEquals(13, $child->parent_id); + static::assertEquals(0, $child->position); + static::assertModelAttribute('position', [ + 13 => 0, + 14 => 1, + 15 => 2, + 10 => 0, + 11 => 0, + 12 => 0 + ]); } public function testAddChildren() @@ -82,9 +147,14 @@ public function testAddChildrenFromPosition() $entity->addChildren([$child1, $child2], 1); static::assertEquals(6, $entity->countChildren()); - static::assertPositions([0, 3, 4, 5], [10, 13, 14, 15]); static::assertEquals(1, $child1->position); static::assertEquals(2, $child2->position); + static::assertModelAttribute('position', [ + 10 => 0, + 13 => 3, + 14 => 4, + 15 => 5 + ]); } public function testRemoveChild() @@ -95,7 +165,26 @@ public function testRemoveChild() static::assertNull(Entity::find(10)); static::assertEquals(3, $entity->countChildren()); - static::assertPositions([0, 1, 2], [13, 14, 15]); + static::assertModelAttribute('position', [ + 13 => 0, + 14 => 1, + 15 => 2 + ]); + } + + public function testRemoveChildHavingChildren() + { + $entity = Entity::find(9); + + $entity->removeChild(0, true); + + static::assertNull(Entity::find(10)); + + $entity11 = Entity::find(11); + $entity12 = Entity::find(12); + + static::assertTrue($entity11->isRoot()); + static::assertFalse($entity12->isRoot()); } public function testRemoveChildren() @@ -105,8 +194,12 @@ public function testRemoveChildren() $entity->removeChildren(0, 2); + static::assertEmpty(Entity::whereIn('id', [10, 13, 14])->get()); static::assertEquals(2, $entity->countChildren()); - static::assertPositions([0, 1], [15, 16]); + static::assertModelAttribute('position', [ + 15 => 0, + 16 => 1 + ]); } public function testRemoveChildrenToTheEnd() @@ -115,10 +208,27 @@ public function testRemoveChildrenToTheEnd() $entity->removeChildren(1); + static::assertEmpty(Entity::whereIn('id', [13, 14, 15])->get()); static::assertEquals(1, $entity->countChildren()); $firstChild = $entity->getFirstChild(); static::assertEquals(10, $firstChild->getKey()); static::assertEquals(0, $firstChild->position); } + + public function testRemoveChildrenHavingChildren() + { + Entity::find(13)->addChildren([new Entity(), new Entity()]); + + $parent = Entity::find(9); + + $parent->removeChildren(0, 1); + + static::assertEmpty(Entity::whereIn('id', [10, 13])->get()); + static::assertEquals(2, $parent->countChildren()); + static::assertModelAttribute('position', [ + 14 => 0, + 15 => 1 + ]); + } } diff --git a/tests/Entity/ConstructionTests.php b/tests/Entity/ConstructionTests.php index 959b6a3..8a53159 100644 --- a/tests/Entity/ConstructionTests.php +++ b/tests/Entity/ConstructionTests.php @@ -56,4 +56,13 @@ public function testNewFromBuilder() static::assertEquals(0, static::readAttribute($newEntity, 'previousPosition')); static::assertEquals(0, static::readAttribute($newEntity, 'previousRealDepth')); } + + public function testSetPositionAttribute() + { + $entity = new Entity(); + + $entity->position = -1; + + static::assertEquals(0, $entity->position); + } } diff --git a/tests/Entity/ParentRootTests.php b/tests/Entity/ParentRootTests.php index 18d3823..293bc2b 100644 --- a/tests/Entity/ParentRootTests.php +++ b/tests/Entity/ParentRootTests.php @@ -52,7 +52,15 @@ public function testMakeRoot() $child->makeRoot(4); static::assertTrue($child->isRoot()); - static::assertPositions([0, 1, 2], [10, 14, 15]); - static::assertPositions([5, 6, 7, 8, 9], [5, 6, 7, 8, 9]); + static::assertModelAttribute('position', [ + 10 => 0, + 14 => 1, + 15 => 2, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + 9 => 9 + ]); } } diff --git a/tests/Entity/PositioningTests.php b/tests/Entity/PositioningTests.php new file mode 100644 index 0000000..6426980 --- /dev/null +++ b/tests/Entity/PositioningTests.php @@ -0,0 +1,69 @@ +position = 0; + $entity->save(); + + static::assertModelAttribute('position', [ + 9 => 0, + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + ]); + } + + public function testMoveToTheFifthPosition() + { + $entity = Entity::find(9); + + $entity->position = 5; + $entity->save(); + + static::assertModelAttribute('position', [ + 1 => 0, + 2 => 1, + 3 => 2, + 4 => 3, + 5 => 4, + 9 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + ]); + } + + public function testMoveToPositionWhichIsOutOfTheUpperBound() + { + $entity = Entity::find(1); + + $entity->position = 999; + $entity->save(); + + static::assertModelAttribute('position', [ + 1 => 8, // + 2 => 0, + 3 => 1, + 4 => 2, + 5 => 3, + 6 => 4, + 7 => 5, + 8 => 6, + 9 => 7, + ]); + } +} diff --git a/tests/Entity/SiblingManipulationTests.php b/tests/Entity/SiblingManipulationTests.php index cbebc19..ab1707e 100644 --- a/tests/Entity/SiblingManipulationTests.php +++ b/tests/Entity/SiblingManipulationTests.php @@ -27,8 +27,14 @@ public function testAddSiblingAtPosition() $entity->addSibling($sibling, 1); static::assertEquals(16, $sibling->getKey()); - static::assertEquals(16, Entity::find(10)->getNextSibling()->getKey()); static::assertEquals(1, $sibling->position); + static::assertModelAttribute('position', [ + 10 => 0, + 16 => 1, + 13 => 2, + 14 => 3, + 15 => 4 + ]); } public function testAddSiblings() @@ -68,6 +74,11 @@ public function testAddSiblingsFromPosition() static::assertEquals('Two', $siblings->get(1)->title); static::assertEquals('Three', $siblings->get(2)->title); static::assertEquals('Four', $siblings->get(3)->title); - static::assertPositions([0, 5, 6, 7], [10, 13, 14, 15]); + static::assertModelAttribute('position', [ + 10 => 0, + 13 => 5, + 14 => 6, + 15 => 7 + ]); } } From bc0eed4f27bc9d33ca63e2af845d0183ea0d0769 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 20:49:38 +0700 Subject: [PATCH 038/113] deprecated real_depth stuff --- .../Contracts/EntityInterface.php | 14 --- .../Generators/stubs/migrations/entity.php | 1 - src/Franzose/ClosureTable/Models/Entity.php | 88 +------------------ tests/EntitiesSeeder.php | 16 ++-- tests/Entity/AncestorTests.php | 4 +- tests/Entity/ConstructionTests.php | 28 ++---- tests/EntityTestCase.php | 11 +-- .../expectedEntityInnoDbMigration.php | 1 - tests/Generators/expectedEntityMigration.php | 1 - tests/Page.php | 2 +- ...014_01_18_162506_create_entities_table.php | 1 - 11 files changed, 22 insertions(+), 145 deletions(-) diff --git a/src/Franzose/ClosureTable/Contracts/EntityInterface.php b/src/Franzose/ClosureTable/Contracts/EntityInterface.php index 23ff7c9..7434405 100644 --- a/src/Franzose/ClosureTable/Contracts/EntityInterface.php +++ b/src/Franzose/ClosureTable/Contracts/EntityInterface.php @@ -23,20 +23,6 @@ public function getParentIdColumn(); */ public function getPositionColumn(); - /** - * Gets the short name of the "real depth" column. - * - * @return string - */ - public function getRealDepthColumn(); - - /** - * Gets the "children" relation index. - * - * @return string - */ - public function getChildrenRelationIndex(); - /** * "Query all models" flag. * diff --git a/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php b/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php index 323182f..b5d8799 100644 --- a/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php +++ b/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php @@ -11,7 +11,6 @@ public function up() $table->increments('id'); $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); - $table->integer('real_depth', false, true); $table->softDeletes(); $table->foreign('parent_id')->references('id')->on('{{entity_table}}')->onDelete('set null'); diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 09bbff6..cd63e99 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -19,7 +19,6 @@ * * @property int position Alias for the current position attribute name * @property int parent_id Alias for the direct ancestor identifier attribute name - * @property int real_depth Alias for the real depth attribute name * @property Collection children Child nodes loaded from the database * @method Builder childNode() * @method Builder childAt(int $position) @@ -63,13 +62,6 @@ class Entity extends Eloquent implements EntityInterface */ private $previousPosition; - /** - * Cached "previous" (i.e. before the model is moved) model real depth. - * - * @var int - */ - private $previousRealDepth; - /** * Indicates if the model is being moved to another ancestor. * @@ -101,18 +93,13 @@ class Entity extends Eloquent implements EntityInterface public function __construct(array $attributes = []) { $position = $this->getPositionColumn(); - $depth = $this->getRealDepthColumn(); - $this->fillable(array_merge($this->getFillable(), [$position, $depth])); + $this->fillable(array_merge($this->getFillable(), [$position])); if (isset($attributes[$position]) && $attributes[$position] < 0) { $attributes[$position] = 0; } - if (!isset($attributes[$depth]) || $attributes[$depth] < 0) { - $attributes[$depth] = 0; - } - $this->closure = new $this->closure; // The default class name of the closure table was not changed @@ -131,7 +118,6 @@ public function newFromBuilder($attributes = [], $connection = null) $instance = parent::newFromBuilder($attributes); $instance->previousParentId = $instance->parent_id; $instance->previousPosition = $instance->position; - $instance->previousRealDepth = $instance->real_depth; return $instance; } @@ -223,30 +209,6 @@ public function getPositionColumn() return 'position'; } - /** - * Gets value of the "real depth" attribute. - * - * @return int - */ - public function getRealDepthAttribute() - { - return $this->getAttributeFromArray($this->getRealDepthColumn()); - } - - /** - * Sets value of the "real depth" attribute. - * - * @param int $value - */ - protected function setRealDepthAttribute($value) - { - if ($this->real_depth === $value) { - return; - } - $this->previousRealDepth = $this->real_depth; - $this->attributes[$this->getRealDepthColumn()] = (int) $value; - } - /** * Gets the fully qualified "real depth" column. * @@ -261,6 +223,7 @@ public function getQualifiedRealDepthColumn() * Gets the short name of the "real depth" column. * * @return string + * @deprecated since 6.0 */ public function getRealDepthColumn() { @@ -271,6 +234,7 @@ public function getRealDepthColumn() * Gets the "children" relation index. * * @return string + * @deprecated since 6.0 */ public function getChildrenRelationIndex() { @@ -303,8 +267,6 @@ public static function boot() $entity->position = $entity->position !== null ? $entity->position : $entity->getLatestPosition(); - - $entity->real_depth = $entity->getNewRealDepth($entity->parent_id); }); // When entity is created, the appropriate @@ -312,7 +274,6 @@ public static function boot() static::created(static function (Entity $entity) { $entity->previousParentId = null; $entity->previousPosition = null; - $entity->previousRealDepth = null; $descendant = $entity->getKey(); $ancestor = isset($entity->parent_id) ? $entity->parent_id : $descendant; @@ -321,14 +282,6 @@ public static function boot() }); static::saved(static function (Entity $entity) { - if ($entity->isDirty($entity->getRealDepthColumn())) { - $action = $entity->real_depth > $entity->previousRealDepth ? 'increment' : 'decrement'; - $amount = abs($entity->real_depth - $entity->previousRealDepth); - - $entity->subqueryClosureBy('descendant') - ->$action($entity->getRealDepthColumn(), $amount); - } - $parentIdChanged = $entity->isDirty($entity->getParentIdColumn()); if ($parentIdChanged || $entity->isDirty($entity->getPositionColumn())) { @@ -1354,7 +1307,7 @@ public static function getTreeByQuery(Builder $query, array $columns = ['*']) */ public static function createFromArray(array $tree, EntityInterface $parent = null) { - $childrenRelationIndex = with(new static)->getChildrenRelationIndex(); + $childrenRelationIndex = (new static())->getChildrenRelationIndex(); $entities = []; foreach ($tree as $item) { @@ -1401,8 +1354,6 @@ public function moveTo($position, $ancestor = null) $this->parent_id = $parentId; $this->position = $position; - $this->real_depth = $this->getNewRealDepth($ancestor); - $this->isMoved = true; $this->save(); @@ -1412,25 +1363,6 @@ public function moveTo($position, $ancestor = null) return $this; } - /** - * Gets real depth of the new ancestor of the model. - * - * @param Entity|int|null $ancestor - * @return int - */ - protected function getNewRealDepth($ancestor) - { - if (!$ancestor instanceof EntityInterface) { - if ($ancestor === null) { - return 0; - } else { - return static::find($ancestor)->real_depth + 1; - } - } else { - return $ancestor->real_depth + 1; - } - } - /** * Gets the next sibling position after the last one. * @@ -1474,18 +1406,6 @@ private function reorderSiblings() ->increment($position); } - /** - * Clamp the position between 0 and the last position of the current parent. - */ - protected function clampPosition() - { - if (!$this->isDirty($this->getPositionColumn())) { - return; - } - $newPosition = max(0, min($this->position, $this->getLatestPosition())); - $this->attributes[$this->getPositionColumn()] = $newPosition; - } - /** * Deletes a subtree from database. * diff --git a/tests/EntitiesSeeder.php b/tests/EntitiesSeeder.php index 67ae573..00b6a8d 100644 --- a/tests/EntitiesSeeder.php +++ b/tests/EntitiesSeeder.php @@ -8,7 +8,7 @@ class EntitiesSeeder extends Seeder { public function run() { - $entitiesSql = 'insert into entities (id, parent_id, title, excerpt, body, position, real_depth) values(?, ?, ?, ?, ?, ?, ?)'; + $entitiesSql = 'insert into entities (id, parent_id, title, excerpt, body, position) values(?, ?, ?, ?, ?, ?)'; $closuresSql = 'insert into entities_closure (ancestor, descendant, depth) values(?, ?, ?)'; // 1 @@ -25,13 +25,13 @@ public function run() // 9 > 15 foreach (range(0, 8) as $idx) { - DB::insert($entitiesSql, [$idx + 1, null, 'The title', 'The excerpt', 'The body', $idx, 0]); + DB::insert($entitiesSql, [$idx + 1, null, 'The title', 'The excerpt', 'The body', $idx]); DB::insert($closuresSql, [$idx + 1, $idx + 1, 0]); } - DB::insert($entitiesSql, [10, 9, 'The title', 'The excerpt', 'The body', 0, 1]); - DB::insert($entitiesSql, [11, 10, 'The title', 'The excerpt', 'The body', 0, 2]); - DB::insert($entitiesSql, [12, 11, 'The title', 'The excerpt', 'The body', 0, 3]); + DB::insert($entitiesSql, [10, 9, 'The title', 'The excerpt', 'The body', 0]); + DB::insert($entitiesSql, [11, 10, 'The title', 'The excerpt', 'The body', 0]); + DB::insert($entitiesSql, [12, 11, 'The title', 'The excerpt', 'The body', 0]); DB::insert($closuresSql, [10, 10, 0]); DB::insert($closuresSql, [11, 11, 0]); DB::insert($closuresSql, [12, 12, 0]); @@ -44,15 +44,15 @@ public function run() DB::insert($closuresSql, [10, 12, 2]); DB::insert($closuresSql, [9, 12, 3]); - DB::insert($entitiesSql, [13, 9, 'The title', 'The excerpt', 'The body', 1, 1]); + DB::insert($entitiesSql, [13, 9, 'The title', 'The excerpt', 'The body', 1]); DB::insert($closuresSql, [13, 13, 0]); DB::insert($closuresSql, [9, 13, 1]); - DB::insert($entitiesSql, [14, 9, 'The title', 'The excerpt', 'The body', 2, 1]); + DB::insert($entitiesSql, [14, 9, 'The title', 'The excerpt', 'The body', 2]); DB::insert($closuresSql, [14, 14, 0]); DB::insert($closuresSql, [9, 14, 1]); - DB::insert($entitiesSql, [15, 9, 'The title', 'The excerpt', 'The body', 3, 1]); + DB::insert($entitiesSql, [15, 9, 'The title', 'The excerpt', 'The body', 3]); DB::insert($closuresSql, [15, 15, 0]); DB::insert($closuresSql, [9, 15, 1]); } diff --git a/tests/Entity/AncestorTests.php b/tests/Entity/AncestorTests.php index a14a219..d0ee510 100644 --- a/tests/Entity/AncestorTests.php +++ b/tests/Entity/AncestorTests.php @@ -30,12 +30,12 @@ public function testAncestorsWhere() { $entity = Entity::find(12); - $ancestors = $entity->getAncestorsWhere('real_depth', '<', 2); + $ancestors = $entity->getAncestorsWhere('position', '<', 2); static::assertInstanceOf(Collection::class, $ancestors); static::assertCount(2, $ancestors); static::assertContainsOnlyInstancesOf(Entity::class, $ancestors); - $this->assertArrayValuesEquals($ancestors->modelKeys(), [9, 10]); + $this->assertArrayValuesEquals($ancestors->modelKeys(), [10, 11]); } public function testCountAncestors() diff --git a/tests/Entity/ConstructionTests.php b/tests/Entity/ConstructionTests.php index 8a53159..8d303be 100644 --- a/tests/Entity/ConstructionTests.php +++ b/tests/Entity/ConstructionTests.php @@ -8,25 +8,21 @@ class ConstructionTests extends TestCase { - public function testPositionAndRealDepthColumnsMustBeFillable() + public function testPositionMustBeFillable() { $entity = new Entity(); static::assertTrue($entity->isFillable('position')); - static::assertTrue($entity->isFillable('real_depth')); } public function testPositionShouldBeCorrect() { static::assertNull((new Entity())->position); static::assertEquals(0, (new Entity(['position' => -1]))->position); - } - public function testRealDepthShouldBeSetToZero() - { - static::assertEquals(0, (new Entity())->real_depth); - static::assertEquals(0, (new Entity(['real_depth' => null]))->real_depth); - static::assertEquals(0, (new Entity(['real_depth' => -1]))->real_depth); + $entity = new Entity(); + $entity->position = -1; + static::assertEquals(0, $entity->position); } public function testEntityShouldUseDefaultClosureTable() @@ -42,27 +38,15 @@ public function testNewFromBuilder() { $entity = new Entity([ 'parent_id' => 123, - 'position' => 5, - 'real_depth' => 2 + 'position' => 5 ]); $newEntity = $entity->newFromBuilder([ 'parent_id' => 321, - 'position' => 0, - 'real_depth' => 0 + 'position' => 0 ]); static::assertEquals(321, static::readAttribute($newEntity, 'previousParentId')); static::assertEquals(0, static::readAttribute($newEntity, 'previousPosition')); - static::assertEquals(0, static::readAttribute($newEntity, 'previousRealDepth')); - } - - public function testSetPositionAttribute() - { - $entity = new Entity(); - - $entity->position = -1; - - static::assertEquals(0, $entity->position); } } diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index f17d548..2e1eee4 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -36,7 +36,7 @@ public function setUp() } $this->entity = new Entity; - $this->entity->fillable(['title', 'excerpt', 'body', 'position', 'real_depth']); + $this->entity->fillable(['title', 'excerpt', 'body', 'position']); $this->childrenRelationIndex = $this->entity->getChildrenRelationIndex(); } @@ -116,15 +116,6 @@ public function testCreateDoesNotChangePositionOfSiblings() $this->assertEquals(9, Entity::find($id)->position); } - public function testCreateSetsRealDepth() - { - $entity = new Page(['title' => 'Item 3']); - $entity->parent_id = 9; - $entity->save(); - - $this->assertEquals(1, $entity->real_depth); - } - public function testSavingLoadedEntityShouldNotTriggerReordering() { $entity1 = new Page(['title' => 'Item 1']); diff --git a/tests/Generators/expectedEntityInnoDbMigration.php b/tests/Generators/expectedEntityInnoDbMigration.php index 5181e0d..97b6467 100644 --- a/tests/Generators/expectedEntityInnoDbMigration.php +++ b/tests/Generators/expectedEntityInnoDbMigration.php @@ -11,7 +11,6 @@ public function up() $table->increments('id'); $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); - $table->integer('real_depth', false, true); $table->softDeletes(); $table->foreign('parent_id')->references('id')->on('entity')->onDelete('set null'); diff --git a/tests/Generators/expectedEntityMigration.php b/tests/Generators/expectedEntityMigration.php index db3f795..b4b46e8 100644 --- a/tests/Generators/expectedEntityMigration.php +++ b/tests/Generators/expectedEntityMigration.php @@ -11,7 +11,6 @@ public function up() $table->increments('id'); $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); - $table->integer('real_depth', false, true); $table->softDeletes(); $table->foreign('parent_id')->references('id')->on('entity')->onDelete('set null'); diff --git a/tests/Page.php b/tests/Page.php index 6d91655..4e83b9a 100644 --- a/tests/Page.php +++ b/tests/Page.php @@ -6,5 +6,5 @@ class Page extends Entity { protected $table = 'entities'; - protected $fillable = ['id', 'parent_id', 'title', 'excerpt', 'body', 'position', 'real_depth']; + protected $fillable = ['id', 'parent_id', 'title', 'excerpt', 'body', 'position']; } diff --git a/tests/migrations/2014_01_18_162506_create_entities_table.php b/tests/migrations/2014_01_18_162506_create_entities_table.php index c23eee8..df15437 100644 --- a/tests/migrations/2014_01_18_162506_create_entities_table.php +++ b/tests/migrations/2014_01_18_162506_create_entities_table.php @@ -19,7 +19,6 @@ public function up() $table->string('excerpt')->default(''); $table->string('body')->default(''); $table->integer('position', false, true); - $table->integer('real_depth', false, true); $table->softDeletes(); $table->foreign('parent_id')->references('id')->on('entities')->onDelete('set null'); From 7313052497e53d3194185734eede3da0a05fd711 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 21:02:13 +0700 Subject: [PATCH 039/113] removed unused QueryBuilder --- .../ClosureTable/Extensions/QueryBuilder.php | 30 ------------------- src/Franzose/ClosureTable/Models/Entity.php | 15 +--------- 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 src/Franzose/ClosureTable/Extensions/QueryBuilder.php diff --git a/src/Franzose/ClosureTable/Extensions/QueryBuilder.php b/src/Franzose/ClosureTable/Extensions/QueryBuilder.php deleted file mode 100644 index 8ab21ff..0000000 --- a/src/Franzose/ClosureTable/Extensions/QueryBuilder.php +++ /dev/null @@ -1,30 +0,0 @@ -where($column, '>=', $values[0]); - } else { - $this->whereIn($column, range($values[0], $values[1])); - } - - return $this; - } -} diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index cd63e99..9f03652 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -3,10 +3,10 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model as Eloquent; -use Franzose\ClosureTable\Extensions\QueryBuilder; use Franzose\ClosureTable\Contracts\EntityInterface; use Franzose\ClosureTable\Extensions\Collection; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Query\Builder as QueryBuilder; use InvalidArgumentException; use Throwable; @@ -1437,19 +1437,6 @@ public function newCollection(array $models = array()) return new Collection($models); } - /** - * Get a new query builder instance for the connection. - * - * @return QueryBuilder - */ - protected function newBaseQueryBuilder() - { - $conn = $this->getConnection(); - $grammar = $conn->getQueryGrammar(); - - return new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); - } - /** * Executes queries within a transaction. * From 8175ac8bda1928b490f43e8183f91666a0cd927f Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 21:14:09 +0700 Subject: [PATCH 040/113] removed redundant code --- src/Franzose/ClosureTable/Models/Entity.php | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 9f03652..ce20168 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -62,13 +62,6 @@ class Entity extends Eloquent implements EntityInterface */ private $previousPosition; - /** - * Indicates if the model is being moved to another ancestor. - * - * @var bool - */ - private $isMoved = false; - /** * Indicates if the model should soft delete. * @@ -259,16 +252,6 @@ public static function boot() $entity->attributes[$entity->getPositionColumn()] = $newPosition; }); - static::creating(static function (Entity $entity) { - if ($entity->isMoved) { - return; - } - - $entity->position = $entity->position !== null - ? $entity->position - : $entity->getLatestPosition(); - }); - // When entity is created, the appropriate // data will be put into the closure table. static::created(static function (Entity $entity) { @@ -1354,12 +1337,8 @@ public function moveTo($position, $ancestor = null) $this->parent_id = $parentId; $this->position = $position; - $this->isMoved = true; - $this->save(); - $this->isMoved = false; - return $this; } From fd16904796cfa623a1868bcd41ee4ffa835f687c Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 21:49:35 +0700 Subject: [PATCH 041/113] added several useful scopes and deprecated stuff --- src/Franzose/ClosureTable/Models/Entity.php | 131 +++++++++++--------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index ce20168..e21f565 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -6,7 +6,6 @@ use Franzose\ClosureTable\Contracts\EntityInterface; use Franzose\ClosureTable\Extensions\Collection; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Query\Builder as QueryBuilder; use InvalidArgumentException; use Throwable; @@ -20,6 +19,10 @@ * @property int position Alias for the current position attribute name * @property int parent_id Alias for the direct ancestor identifier attribute name * @property Collection children Child nodes loaded from the database + * @method Builder ancestors(bool $withSelf = false) + * @method Builder ancestorsWithSelf() + * @method Builder descendants(bool $withSelf = false) + * @method Builder descendantsWithSelf() * @method Builder childNode() * @method Builder childAt(int $position) * @method Builder firstChild() @@ -316,69 +319,38 @@ public function getParent(array $columns = ['*']) } /** - * Builds closure table join based on the given column. + * Returns query builder for ancestors. * - * @param string $column + * @param Builder $builder * @param bool $withSelf - * @return QueryBuilder + * + * @return Builder */ - protected function joinClosureBy($column, $withSelf = false) + public function scopeAncestors(Builder $builder, $withSelf = false) { - $primary = $this->getQualifiedKeyName(); - $closure = $this->closure->getTable(); - $ancestor = $this->closure->getQualifiedAncestorColumn(); - $descendant = $this->closure->getQualifiedDescendantColumn(); - - switch ($column) { - case 'ancestor': - $query = $this->join($closure, $ancestor, '=', $primary) - ->where($descendant, '=', $this->getKey()); - break; - - case 'descendant': - $query = $this->join($closure, $descendant, '=', $primary) - ->where($ancestor, '=', $this->getKey()); - break; - } + $depthOperator = $withSelf ? '>=' : '>'; - $depthOperator = ($withSelf === true ? '>=' : '>'); - - $query->where($this->closure->getQualifiedDepthColumn(), $depthOperator, 0); - - return $query; + return $builder + ->join( + $this->closure->getTable(), + $this->closure->getAncestorColumn(), + '=', + $this->getQualifiedKeyName() + ) + ->where($this->closure->getDescendantColumn(), '=', $this->getKey()) + ->where($this->closure->getDepthColumn(), $depthOperator, 0); } /** - * Builds closure table "where in" query on the given column. + * Returns query builder for ancestors including the current node. * - * @param string $column - * @param bool $withSelf - * @return QueryBuilder + * @param Builder $builder + * + * @return Builder */ - protected function subqueryClosureBy($column, $withSelf = false) + public function scopeAncestorsWithSelf(Builder $builder) { - $self = $this; - - return $this->whereIn($this->getQualifiedKeyName(), function ($qb) use ($self, $column, $withSelf) { - switch ($column) { - case 'ancestor': - $selectedColumn = $self->closure->getAncestorColumn(); - $whereColumn = $self->closure->getDescendantColumn(); - break; - - case 'descendant': - $selectedColumn = $self->closure->getDescendantColumn(); - $whereColumn = $self->closure->getAncestorColumn(); - break; - } - - $depthOperator = ($withSelf === true ? '>=' : '>'); - - return $qb->select($selectedColumn) - ->from($self->closure->getTable()) - ->where($whereColumn, '=', $self->getKey()) - ->where($self->closure->getDepthColumn(), $depthOperator, 0); - }); + return $this->scopeAncestors($builder, true); } /** @@ -389,7 +361,7 @@ protected function subqueryClosureBy($column, $withSelf = false) */ public function getAncestors(array $columns = ['*']) { - return $this->joinClosureBy('ancestor')->get($columns); + return $this->ancestors()->get($columns); } /** @@ -397,6 +369,7 @@ public function getAncestors(array $columns = ['*']) * * @param array $columns * @return Collection + * @deprecated since 6.0, use {@link Collection::toTree()} instead */ public function getAncestorsTree(array $columns = ['*']) { @@ -411,10 +384,11 @@ public function getAncestorsTree(array $columns = ['*']) * @param mixed $value * @param array $columns * @return Collection + * @deprecated since 6.0, use {@link Entity::ancestors()} scope instead */ public function getAncestorsWhere($column, $operator = null, $value = null, array $columns = ['*']) { - return $this->joinClosureBy('ancestor')->where($column, $operator, $value)->get($columns); + return $this->ancestors()->where($column, $operator, $value)->get($columns); } /** @@ -424,7 +398,7 @@ public function getAncestorsWhere($column, $operator = null, $value = null, arra */ public function countAncestors() { - return $this->joinClosureBy('ancestor')->count(); + return $this->ancestors()->count(); } /** @@ -437,6 +411,41 @@ public function hasAncestors() return (bool) $this->countAncestors(); } + /** + * Returns query builder for descendants. + * + * @param Builder $builder + * @param bool $withSelf + * + * @return Builder + */ + public function scopeDescendants(Builder $builder, $withSelf = false) + { + $depthOperator = $withSelf ? '>=' : '>'; + + return $builder + ->join( + $this->closure->getTable(), + $this->closure->getDescendantColumn(), + '=', + $this->getQualifiedKeyName() + ) + ->where($this->closure->getAncestorColumn(), '=', $this->getKey()) + ->where($this->closure->getDepthColumn(), $depthOperator, 0); + } + + /** + * Returns query builder for descendants including the current node. + * + * @param Builder $builder + * + * @return Builder + */ + public function scopeDescendantsWithSelf(Builder $builder) + { + return $this->scopeDescendants($builder, true); + } + /** * Retrieves all descendants of a model. * @@ -445,7 +454,7 @@ public function hasAncestors() */ public function getDescendants(array $columns = ['*']) { - return $this->joinClosureBy('descendant')->get($columns); + return $this->descendants()->get($columns); } /** @@ -453,6 +462,7 @@ public function getDescendants(array $columns = ['*']) * * @param array $columns * @return Collection + * @deprecated since 6.0, use {@link Collection::toTree()} instead */ public function getDescendantsTree(array $columns = ['*']) { @@ -467,10 +477,11 @@ public function getDescendantsTree(array $columns = ['*']) * @param mixed $value * @param array $columns * @return Collection + * @deprecated since 6.0, use {@link Entity::descendants()} scope instead */ public function getDescendantsWhere($column, $operator = null, $value = null, array $columns = ['*']) { - return $this->joinClosureBy('descendant')->where($column, $operator, $value)->get($columns); + return $this->descendants()->where($column, $operator, $value)->get($columns); } /** @@ -480,7 +491,7 @@ public function getDescendantsWhere($column, $operator = null, $value = null, ar */ public function countDescendants() { - return $this->joinClosureBy('descendant')->count(); + return $this->descendants()->count(); } /** @@ -1396,7 +1407,7 @@ public function deleteSubtree($withSelf = false, $forceDelete = false) { $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - $ids = $this->joinClosureBy('descendant', $withSelf)->pluck($this->getKeyName()); + $ids = $this->descendants($withSelf)->pluck($this->getKeyName()); if ($forceDelete) { $this->closure->whereIn($this->closure->getDescendantColumn(), $ids)->delete(); From e460053a548607c230dbd76759edf77df4903f62 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 23:34:01 +0700 Subject: [PATCH 042/113] refactored tree stuff --- .../Contracts/EntityInterface.php | 8 - src/Franzose/ClosureTable/Models/Entity.php | 30 +-- tests/Entity/TreeTests.php | 165 +++++++++++++++ tests/EntityTestCase.php | 192 ------------------ 4 files changed, 182 insertions(+), 213 deletions(-) create mode 100644 tests/Entity/TreeTests.php diff --git a/src/Franzose/ClosureTable/Contracts/EntityInterface.php b/src/Franzose/ClosureTable/Contracts/EntityInterface.php index 7434405..3fd5ca0 100644 --- a/src/Franzose/ClosureTable/Contracts/EntityInterface.php +++ b/src/Franzose/ClosureTable/Contracts/EntityInterface.php @@ -407,14 +407,6 @@ public static function getRoots(array $columns = ['*']); */ public function makeRoot($position); - /** - * Retrieves entire tree. - * - * @param array $columns - * @return \Franzose\ClosureTable\Extensions\Collection - */ - public static function getTree(array $columns = ['*']); - /** * Saves models from the given attributes array. * diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index e21f565..34b5a9a 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -6,6 +6,7 @@ use Franzose\ClosureTable\Contracts\EntityInterface; use Franzose\ClosureTable\Extensions\Collection; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Arr; use InvalidArgumentException; use Throwable; @@ -247,10 +248,6 @@ public static function boot() parent::boot(); static::saving(static function (Entity $entity) { - if (!$entity->isDirty($entity->getPositionColumn())) { - return; - } - $newPosition = max(0, min($entity->position, $entity->getLatestPosition())); $entity->attributes[$entity->getPositionColumn()] = $newPosition; }); @@ -1240,6 +1237,7 @@ protected function prepareTreeQueryColumns(array $columns) * @param array $columns * * @return Collection + * @deprecated since 6.0 */ public static function getTree(array $columns = ['*']) { @@ -1248,8 +1246,12 @@ public static function getTree(array $columns = ['*']) */ $instance = new static; - return $instance->orderBy($instance->getParentIdColumn())->orderBy($instance->getPositionColumn()) - ->get($instance->prepareTreeQueryColumns($columns))->toTree(); + return $instance + ->load('children') + ->orderBy($instance->getParentIdColumn()) + ->orderBy($instance->getPositionColumn()) + ->get($instance->prepareTreeQueryColumns($columns)) + ->toTree(); } /** @@ -1261,6 +1263,7 @@ public static function getTree(array $columns = ['*']) * @param array $columns * * @return Collection + * @deprecated since 6.0 */ public static function getTreeWhere($column, $operator = null, $value = null, array $columns = ['*']) { @@ -1276,10 +1279,11 @@ public static function getTreeWhere($column, $operator = null, $value = null, ar /** * Retrieves tree with any conditions using QueryBuilder * - * @param EloquentBuilder $query + * @param Builder $query * @param array $columns * * @return Collection + * @deprecated since 6.0 */ public static function getTreeByQuery(Builder $query, array $columns = ['*']) { @@ -1295,17 +1299,17 @@ public static function getTreeByQuery(Builder $query, array $columns = ['*']) * Saves models from the given attributes array. * * @param array $tree - * @param \Franzose\ClosureTable\Contracts\EntityInterface $parent + * @param EntityInterface $parent * * @return Collection + * @throws Throwable */ public static function createFromArray(array $tree, EntityInterface $parent = null) { - $childrenRelationIndex = (new static())->getChildrenRelationIndex(); $entities = []; foreach ($tree as $item) { - $children = array_pull($item, $childrenRelationIndex); + $children = Arr::pull($item, 'children'); /** * @var Entity $entity @@ -1315,9 +1319,7 @@ public static function createFromArray(array $tree, EntityInterface $parent = nu $entity->save(); if ($children !== null) { - $children = static::createFromArray($children, $entity); - $entity->setRelation($childrenRelationIndex, $children); - $entity->addChildren($children->all()); + $entity->addChildren(static::createFromArray($children, $entity)->all()); } $entities[] = $entity; @@ -1401,7 +1403,9 @@ private function reorderSiblings() * * @param bool $withSelf * @param bool $forceDelete + * * @return void + * @throws \Exception */ public function deleteSubtree($withSelf = false, $forceDelete = false) { diff --git a/tests/Entity/TreeTests.php b/tests/Entity/TreeTests.php new file mode 100644 index 0000000..f825179 --- /dev/null +++ b/tests/Entity/TreeTests.php @@ -0,0 +1,165 @@ +deleteSubtree(); + + static::assertEquals(1, Entity::whereBetween('id', [9, 15])->count()); + static::assertEquals(8, Entity::whereBetween('id', [1, 8])->count()); + } + + public function testDeleteSubtreeWithAncestor() + { + $entity = Entity::find(9); + $entity->deleteSubtree(true); + + static::assertEquals(0, Entity::whereBetween('id', [9, 15])->count()); + static::assertEquals(8, Entity::whereBetween('id', [1, 8])->count()); + } + + public function testForceDeleteSubtree() + { + $entity = Entity::find(9); + $entity->deleteSubtree(false, true); + + static::assertEquals(1, Entity::whereBetween('id', [9, 15])->count()); + static::assertEquals(1, ClosureTable::whereBetween('ancestor', [9, 15])->count()); + } + + public function testForceDeleteDeepSubtree() + { + Entity::find(9)->moveTo(0, 8); + Entity::find(8)->moveTo(0, 7); + Entity::find(7)->moveTo(0, 6); + Entity::find(6)->moveTo(0, 5); + Entity::find(5)->moveTo(0, 4); + Entity::find(4)->moveTo(0, 3); + Entity::find(3)->moveTo(0, 2); + Entity::find(2)->moveTo(0, 1); + + Entity::find(1)->deleteSubtree(false, true); + + static::assertEquals(1, Entity::whereBetween('id', [1, 9])->count()); + static::assertEquals(1, ClosureTable::whereBetween('ancestor', [1, 9])->count()); + } + + public function testForceDeleteSubtreeWithSelf() + { + $entity = Entity::find(9); + $entity->deleteSubtree(true, true); + + static::assertEquals(0, Entity::whereBetween('id', [9, 15])->count()); + static::assertEquals(0, ClosureTable::whereBetween('ancestor', [9, 15])->count()); + } + + public function testCreateFromArray() + { + $array = [ + [ + 'id' => 90, + 'title' => 'About', + 'position' => 0, + 'children' => [ + [ + 'id' => 93, + 'title' => 'Testimonials' + ] + ] + ], + [ + 'id' => 91, + 'title' => 'Blog', + 'position' => 1 + ], + [ + 'id' => 92, + 'title' => 'Portfolio', + 'position' => 2 + ], + ]; + + $pages = Page::createFromArray($array); + + static::assertCount(3, $pages); + + $pageZero = $pages->get(0); + static::assertEquals(90, $pageZero->getKey()); + static::assertEquals(91, $pages->get(1)->getKey()); + static::assertEquals(92, $pages->get(2)->getKey()); + static::assertEquals(93, $pageZero->getChildAt(0)->getKey()); + } + + public function testCreateFromArrayBug81() + { + $array = [ + [ + 'title' => 'About', + 'children' => [ + [ + 'title' => 'Testimonials', + 'children' => [ + [ + 'title' => 'child 1', + ], + [ + 'title' => 'child 2', + ], + ] + ] + ] + ], + [ + 'title' => 'Blog', + ], + [ + 'title' => 'Portfolio', + ], + ]; + + $pages = Page::createFromArray($array); + + $about = $pages[0]; + static::assertEquals('About', $about->title); + static::assertEquals(1, $about->countChildren()); + static::assertEquals(16, $about->getKey()); + + $blog = $pages[1]; + static::assertEquals('Blog', $blog->title); + static::assertEquals(0, $blog->countChildren()); + static::assertEquals(20, $blog->getKey()); + + $portfolio = $pages[2]; + static::assertEquals('Portfolio', $portfolio->title); + static::assertEquals(0, $portfolio->countChildren()); + static::assertEquals(21, $portfolio->getKey()); + + $pages = $pages[0]->getChildren(); + + $testimonials = $pages[0]; + static::assertEquals('Testimonials', $testimonials->title); + static::assertEquals(2, $testimonials->countChildren()); + static::assertEquals(17, $testimonials->getKey()); + + $pages = $pages[0]->getChildren(); + + $child1 = $pages[0]; + static::assertEquals('child 1', $child1->title); + static::assertEquals(0, $child1->countChildren()); + static::assertEquals(18, $child1->getKey()); + + $child2 = $pages[1]; + static::assertEquals('child 2', $child2->title); + static::assertEquals(0, $child2->countChildren()); + static::assertEquals(19, $child2->getKey()); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 2e1eee4..3681c9b 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -182,198 +182,6 @@ public function testGetParentAfterMovingToAnAncestor() $this->assertEquals(15, $parent->getKey()); } - public function testGetTree() - { - $tree = Entity::getTree(); - - $this->assertCount(9, $tree); - - $ninth = $tree[8]; - $this->assertArrayHasKey($this->childrenRelationIndex, $ninth->getRelations()); - - $tenth = $ninth->getChildren(); - - $this->assertCount(4, $tenth); - } - - public function testGetTreeWhere() - { - $tree = Entity::getTreeWhere($this->entity->getPositionColumn(), '>=', 1, [ - $this->entity->getKeyName(), - $this->entity->getPositionColumn() - ]); - - $this->assertCount(8, $tree); - $this->assertEquals(1, $tree[0]->position); - - $eight = $tree[7]; - - $this->assertArrayHasKey($this->childrenRelationIndex, $eight->getRelations()); - $this->assertEquals(1, $eight->getChildAt(0)->position); - - $ninth = $eight->getChildren(); - - $this->assertCount(3, $ninth); - } - - public function testDeleteSubtree() - { - $entity = Entity::find(9); - $entity->deleteSubtree(); - - $this->assertEquals(1, Entity::whereBetween('id', [9, 15])->count()); - $this->assertEquals(8, Entity::whereBetween('id', [1, 8])->count()); - } - - public function testDeleteSubtreeWithAncestor() - { - $entity = Entity::find(9); - $entity->deleteSubtree(true); - - $this->assertEquals(0, Entity::whereBetween('id', [9, 15])->count()); - $this->assertEquals(8, Entity::whereBetween('id', [1, 8])->count()); - } - - public function testForceDeleteSubtree() - { - $entity = Entity::find(9); - $entity->deleteSubtree(false, true); - - $this->assertEquals(1, Entity::whereBetween('id', [9, 15])->count()); - $this->assertEquals(1, ClosureTable::whereBetween('ancestor', [9, 15])->count()); - } - - public function testForceDeleteDeepSubtree() - { - Entity::find(9)->moveTo(0, 8); - Entity::find(8)->moveTo(0, 7); - Entity::find(7)->moveTo(0, 6); - Entity::find(6)->moveTo(0, 5); - Entity::find(5)->moveTo(0, 4); - Entity::find(4)->moveTo(0, 3); - Entity::find(3)->moveTo(0, 2); - Entity::find(2)->moveTo(0, 1); - - Entity::find(1)->deleteSubtree(false, true); - - $this->assertEquals(1, Entity::whereBetween('id', [1, 9])->count()); - $this->assertEquals(1, ClosureTable::whereBetween('ancestor', [1, 9])->count()); - } - - public function testForceDeleteSubtreeWithSelf() - { - $entity = Entity::find(9); - $entity->deleteSubtree(true, true); - - $this->assertEquals(0, Entity::whereBetween('id', [9, 15])->count()); - $this->assertEquals(0, ClosureTable::whereBetween('ancestor', [9, 15])->count()); - } - - public function testCreateFromArray() - { - $array = [ - [ - 'id' => 90, - 'title' => 'About', - 'position' => 0, - 'children' => [ - [ - 'id' => 93, - 'title' => 'Testimonials' - ] - ] - ], - [ - 'id' => 91, - 'title' => 'Blog', - 'position' => 1 - ], - [ - 'id' => 92, - 'title' => 'Portfolio', - 'position' => 2 - ], - ]; - - $pages = Page::createFromArray($array); - - $this->assertInstanceOf('Franzose\ClosureTable\Extensions\Collection', $pages); - $this->assertCount(3, $pages); - - $pageZero = $pages[0]; - - $this->assertTrue($pageZero->hasChildrenRelation()); - - $this->assertEquals(90, $pageZero->getKey()); - $this->assertEquals(91, $pages[1]->getKey()); - $this->assertEquals(92, $pages[2]->getKey()); - $this->assertEquals(93, $pageZero->getChildAt(0)->getKey()); - } - - public function testCreateFromArrayBug81() - { - $array = [ - [ - 'title' => 'About', - 'children' => [ - [ - 'title' => 'Testimonials', - 'children' => [ - [ - 'title' => 'child 1', - ], - [ - 'title' => 'child 2', - ], - ] - ] - ] - ], - [ - 'title' => 'Blog', - ], - [ - 'title' => 'Portfolio', - ], - ]; - - $pages = Page::createFromArray($array); - - $about = $pages[0]; - $this->assertEquals('About', $about->title); - $this->assertEquals(1, $about->countChildren()); - $this->assertEquals(16, $about->getKey()); - - $blog = $pages[1]; - $this->assertEquals('Blog', $blog->title); - $this->assertEquals(0, $blog->countChildren()); - $this->assertEquals(20, $blog->getKey()); - - $portfolio = $pages[2]; - $this->assertEquals('Portfolio', $portfolio->title); - $this->assertEquals(0, $portfolio->countChildren()); - $this->assertEquals(21, $portfolio->getKey()); - - $pages = $pages[0]->getChildren(); - - $testimonials = $pages[0]; - $this->assertEquals('Testimonials', $testimonials->title); - $this->assertEquals(2, $testimonials->countChildren()); - $this->assertEquals(17, $testimonials->getKey()); - - $pages = $pages[0]->getChildren(); - - $child1 = $pages[0]; - $this->assertEquals('child 1', $child1->title); - $this->assertEquals(0, $child1->countChildren()); - $this->assertEquals(18, $child1->getKey()); - - $child2 = $pages[1]; - $this->assertEquals('child 2', $child2->title); - $this->assertEquals(0, $child2->countChildren()); - $this->assertEquals(19, $child2->getKey()); - } - public function testInsertNode() { $entity = Entity::create(['title' => 'abcde']); From a50e7454013c2858d521041be716237e361708a5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 6 Apr 2020 23:34:33 +0700 Subject: [PATCH 043/113] removed unused property --- src/Franzose/ClosureTable/Models/Entity.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 34b5a9a..1bb9266 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -80,8 +80,6 @@ class Entity extends Eloquent implements EntityInterface */ public $timestamps = false; - public static $debug = false; - /** * Entity constructor. * From c0a4996be6759604e759a86d4feeb1c25f363bf5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 7 Apr 2020 10:07:18 +0700 Subject: [PATCH 044/113] added scopes to query ancestors/descendants by the given node ID --- src/Franzose/ClosureTable/Models/Entity.php | 140 ++++++++++++++++---- tests/Entity/AncestorTests.php | 40 +++++- tests/Entity/DescendantTests.php | 36 +++++ 3 files changed, 186 insertions(+), 30 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 1bb9266..7a724ed 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -20,10 +20,14 @@ * @property int position Alias for the current position attribute name * @property int parent_id Alias for the direct ancestor identifier attribute name * @property Collection children Child nodes loaded from the database - * @method Builder ancestors(bool $withSelf = false) + * @method Builder ancestors() + * @method Builder ancestorsOf($id) * @method Builder ancestorsWithSelf() - * @method Builder descendants(bool $withSelf = false) + * @method Builder ancestorsWithSelfOf($id) + * @method Builder descendants() + * @method Builder descendantsOf($id) * @method Builder descendantsWithSelf() + * @method Builder descendantsWithSelfOf($id) * @method Builder childNode() * @method Builder childAt(int $position) * @method Builder firstChild() @@ -317,23 +321,25 @@ public function getParent(array $columns = ['*']) * Returns query builder for ancestors. * * @param Builder $builder - * @param bool $withSelf * * @return Builder */ - public function scopeAncestors(Builder $builder, $withSelf = false) + public function scopeAncestors(Builder $builder) { - $depthOperator = $withSelf ? '>=' : '>'; + return $this->buildAncestorsQuery($builder, $this->getKey(), false); + } - return $builder - ->join( - $this->closure->getTable(), - $this->closure->getAncestorColumn(), - '=', - $this->getQualifiedKeyName() - ) - ->where($this->closure->getDescendantColumn(), '=', $this->getKey()) - ->where($this->closure->getDepthColumn(), $depthOperator, 0); + /** + * Returns query builder for ancestors of the node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeAncestorsOf(Builder $builder, $id) + { + return $this->buildAncestorsQuery($builder, $id, false); } /** @@ -345,7 +351,44 @@ public function scopeAncestors(Builder $builder, $withSelf = false) */ public function scopeAncestorsWithSelf(Builder $builder) { - return $this->scopeAncestors($builder, true); + return $this->buildAncestorsQuery($builder, $this->getKey(), true); + } + + /** + * Returns query builder for ancestors of the node with given ID including that node also. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeAncestorsWithSelfOf(Builder $builder, $id) + { + return $this->buildAncestorsQuery($builder, $id, true); + } + + /** + * Builds base ancestors query. + * + * @param Builder $builder + * @param mixed $id + * @param bool $withSelf + * + * @return Builder + */ + private function buildAncestorsQuery(Builder $builder, $id, $withSelf) + { + $depthOperator = $withSelf ? '>=' : '>'; + + return $builder + ->join( + $this->closure->getTable(), + $this->closure->getAncestorColumn(), + '=', + $this->getQualifiedKeyName() + ) + ->where($this->closure->getDescendantColumn(), '=', $id) + ->where($this->closure->getDepthColumn(), $depthOperator, 0); } /** @@ -414,19 +457,22 @@ public function hasAncestors() * * @return Builder */ - public function scopeDescendants(Builder $builder, $withSelf = false) + public function scopeDescendants(Builder $builder) { - $depthOperator = $withSelf ? '>=' : '>'; + return $this->buildDescendantsQuery($builder, $this->getKey(), false); + } - return $builder - ->join( - $this->closure->getTable(), - $this->closure->getDescendantColumn(), - '=', - $this->getQualifiedKeyName() - ) - ->where($this->closure->getAncestorColumn(), '=', $this->getKey()) - ->where($this->closure->getDepthColumn(), $depthOperator, 0); + /** + * Returns query builder for descendants of the node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeDescendantsOf(Builder $builder, $id) + { + return $this->buildDescendantsQuery($builder, $id, false); } /** @@ -438,7 +484,44 @@ public function scopeDescendants(Builder $builder, $withSelf = false) */ public function scopeDescendantsWithSelf(Builder $builder) { - return $this->scopeDescendants($builder, true); + return $this->buildDescendantsQuery($builder, $this->getKey(), true); + } + + /** + * Returns query builder for descendants including the current node of the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeDescendantsWithSelfOf(Builder $builder, $id) + { + return $this->buildDescendantsQuery($builder, $id, true); + } + + /** + * Builds base descendants query. + * + * @param Builder $builder + * @param mixed $id + * @param bool $withSelf + * + * @return Builder + */ + private function buildDescendantsQuery(Builder $builder, $id, $withSelf) + { + $depthOperator = $withSelf ? '>=' : '>'; + + return $builder + ->join( + $this->closure->getTable(), + $this->closure->getDescendantColumn(), + '=', + $this->getQualifiedKeyName() + ) + ->where($this->closure->getAncestorColumn(), '=', $id) + ->where($this->closure->getDepthColumn(), $depthOperator, 0); } /** @@ -1409,7 +1492,8 @@ public function deleteSubtree($withSelf = false, $forceDelete = false) { $action = ($forceDelete === true ? 'forceDelete' : 'delete'); - $ids = $this->descendants($withSelf)->pluck($this->getKeyName()); + $query = $withSelf ? $this->descendantsWithSelf() : $this->descendants(); + $ids = $query->pluck($this->getKeyName()); if ($forceDelete) { $this->closure->whereIn($this->closure->getDescendantColumn(), $ids)->delete(); diff --git a/tests/Entity/AncestorTests.php b/tests/Entity/AncestorTests.php index d0ee510..78188d5 100644 --- a/tests/Entity/AncestorTests.php +++ b/tests/Entity/AncestorTests.php @@ -14,6 +14,42 @@ public function testGetAncestorsShouldReturnAnEmptyCollection() static::assertCount(0, Entity::find(1)->getAncestors()); } + public function testAncestorsScope() + { + $entity = Entity::find(12); + + $ancestors = $entity->ancestors()->get(); + + static::assertCount(3, $ancestors); + static::assertEquals([11, 10, 9], $ancestors->modelKeys()); + } + + public function testAncestorsOfScope() + { + $ancestors = Entity::ancestorsOf(12)->get(); + + static::assertCount(3, $ancestors); + static::assertEquals([11, 10, 9], $ancestors->modelKeys()); + } + + public function testAncestorsWithSelfScope() + { + $entity = Entity::find(12); + + $ancestors = $entity->ancestorsWithSelf()->get(); + + static::assertCount(4, $ancestors); + static::assertEquals([12, 11, 10, 9], $ancestors->modelKeys()); + } + + public function testAncestorsWithSelfOfScope() + { + $ancestors = Entity::ancestorsWithSelfOf(12)->get(); + + static::assertCount(4, $ancestors); + static::assertEquals([12, 11, 10, 9], $ancestors->modelKeys()); + } + public function testGetAncestorsShouldNotBeEmpty() { $entity = Entity::find(12); @@ -23,7 +59,7 @@ public function testGetAncestorsShouldNotBeEmpty() static::assertInstanceOf(Collection::class, $ancestors); static::assertCount(3, $ancestors); static::assertContainsOnlyInstancesOf(Entity::class, $ancestors); - $this->assertArrayValuesEquals($ancestors->modelKeys(), [9, 10, 11]); + static::assertEquals([11, 10, 9], $ancestors->modelKeys()); } public function testAncestorsWhere() @@ -35,7 +71,7 @@ public function testAncestorsWhere() static::assertInstanceOf(Collection::class, $ancestors); static::assertCount(2, $ancestors); static::assertContainsOnlyInstancesOf(Entity::class, $ancestors); - $this->assertArrayValuesEquals($ancestors->modelKeys(), [10, 11]); + static::assertEquals([11, 10], $ancestors->modelKeys()); } public function testCountAncestors() diff --git a/tests/Entity/DescendantTests.php b/tests/Entity/DescendantTests.php index 7a07229..67b87de 100644 --- a/tests/Entity/DescendantTests.php +++ b/tests/Entity/DescendantTests.php @@ -14,6 +14,42 @@ public function testGetDescendantsShouldReturnAnEmptyCollection() static::assertCount(0, Entity::find(1)->getDescendants()); } + public function testDescendantsScope() + { + $entity = Entity::find(9); + + $descendants = $entity->descendants()->get(); + + static::assertCount(6, $descendants); + static::assertEquals([10, 11, 12, 13, 14, 15], $descendants->modelKeys()); + } + + public function testDescendantsOfScope() + { + $descendants = Entity::descendantsOf(9)->get(); + + static::assertCount(6, $descendants); + static::assertEquals([10, 11, 12, 13, 14, 15], $descendants->modelKeys()); + } + + public function testDescendantsWithSelfScope() + { + $entity = Entity::find(9); + + $descendants = $entity->descendantsWithSelf()->get(); + + static::assertCount(7, $descendants); + static::assertEquals([9, 10, 11, 12, 13, 14, 15], $descendants->modelKeys()); + } + + public function testDescendantsWithSelfOfScope() + { + $descendants = Entity::descendantsWithSelfOf(9)->get(); + + static::assertCount(7, $descendants); + static::assertEquals([9, 10, 11, 12, 13, 14, 15], $descendants->modelKeys()); + } + public function testGetDescendants() { $entity = Entity::find(9); From 5b0d53c86bfcc7a3e9c221e5a816bbe3a77786c0 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 7 Apr 2020 10:46:05 +0700 Subject: [PATCH 045/113] added scopes to query children by the given node ID --- src/Franzose/ClosureTable/Models/Entity.php | 86 ++++++++++++++++++++- tests/Entity/ChildQueryTests.php | 40 ++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index 7a724ed..e486e9b 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -29,10 +29,15 @@ * @method Builder descendantsWithSelf() * @method Builder descendantsWithSelfOf($id) * @method Builder childNode() + * @method Builder childNodeOf($id) * @method Builder childAt(int $position) + * @method Builder childOf($id, int $position) * @method Builder firstChild() + * @method Builder firstChildOf($id) * @method Builder lastChild() + * @method Builder lastChildOf($id) * @method Builder childrenRange(int $from, int $to = null) + * @method Builder childrenRangeOf($id, int $from, int $to = null) * @method Builder sibling() * @method Builder siblings() * @method Builder neighbors() @@ -643,12 +648,25 @@ public function hasChildrenRelation() * @return Builder */ public function scopeChildNode(Builder $builder) + { + return $this->scopeChildNodeOf($builder, $this->getKey()); + } + + /** + * Returns query builder for child nodes of the node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeChildNodeOf(Builder $builder, $id) { $parentId = $this->getParentIdColumn(); return $builder ->whereNotNull($parentId) - ->where($parentId, '=', $this->getKey()); + ->where($parentId, '=', $id); } /** @@ -666,6 +684,22 @@ public function scopeChildAt(Builder $builder, $position) ->where($this->getPositionColumn(), '=', $position); } + /** + * Returns query builder for a child at the given position of the node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * @param int $position + * + * @return Builder + */ + public function scopeChildOf(Builder $builder, $id, $position) + { + return $this + ->scopeChildNodeOf($builder, $id) + ->where($this->getPositionColumn(), '=', $position); + } + /** * Retrieves a child with given position. * @@ -690,6 +724,19 @@ public function scopeFirstChild(Builder $builder) return $this->scopeChildAt($builder, 0); } + /** + * Returns query builder for the first child node of the node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeFirstChildOf(Builder $builder, $id) + { + return $this->scopeChildOf($builder, $id, 0); + } + /** * Retrieves the first child. * @@ -713,6 +760,19 @@ public function scopeLastChild(Builder $builder) return $this->scopeChildNode($builder)->orderByDesc($this->getPositionColumn()); } + /** + * Returns query builder for the last child node of the node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeLastChildOf(Builder $builder, $id) + { + return $this->scopeChildNodeOf($builder, $id)->orderByDesc($this->getPositionColumn()); + } + /** * Retrieves the last child. * @@ -725,7 +785,7 @@ public function getLastChild(array $columns = ['*']) } /** - * Returns relationship to child nodes in the range of the given positions. + * Returns query builder to child nodes in the range of the given positions. * * @param Builder $builder * @param int $from @@ -745,6 +805,28 @@ public function scopeChildrenRange(Builder $builder, $from, $to = null) return $query; } + /** + * Returns query builder to child nodes in the range of the given positions for the node of the given ID. + * + * @param Builder $builder + * @param mixed $id + * @param int $from + * @param int|null $to + * + * @return Builder + */ + public function scopeChildrenRangeOf(Builder $builder, $id, $from, $to = null) + { + $position = $this->getPositionColumn(); + $query = $this->scopeChildNodeOf($builder, $id)->where($position, '>=', $from); + + if ($to !== null) { + $query->where($position, '<=', $to); + } + + return $query; + } + /** * Retrieves children within given positions range. * diff --git a/tests/Entity/ChildQueryTests.php b/tests/Entity/ChildQueryTests.php index d1c9824..e9c2244 100644 --- a/tests/Entity/ChildQueryTests.php +++ b/tests/Entity/ChildQueryTests.php @@ -77,4 +77,44 @@ public function testGetChildrenRange() static::assertEquals(2, $children[0]->position); static::assertEquals(3, $children[1]->position); } + + public function testChildNodeOfScope() + { + $child = Entity::childNodeOf(9)->where('position', '=', 2)->first(); + + static::assertInstanceOf(Entity::class, $child); + static::assertEquals(14, $child->getKey()); + } + + public function testChildOfScope() + { + $child = Entity::childOf(9, 2)->first(); + + static::assertInstanceOf(Entity::class, $child); + static::assertEquals(14, $child->getKey()); + } + + public function testFirstChildOfScope() + { + $child = Entity::firstChildOf(9)->first(); + + static::assertInstanceOf(Entity::class, $child); + static::assertEquals(10, $child->getKey()); + } + + public function testLastChildOfScope() + { + $child = Entity::lastChildOf(9)->first(); + + static::assertInstanceOf(Entity::class, $child); + static::assertEquals(15, $child->getKey()); + } + + public function testChildrenRangeOfScope() + { + $children = Entity::childrenRangeOf(9, 0, 2)->get(); + + static::assertCount(3, $children); + static::assertEquals([10, 13, 14], $children->modelKeys()); + } } From 8b4a00e7cc9bae3d9a17ef79471e06b568545adf Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 7 Apr 2020 14:35:26 +0700 Subject: [PATCH 046/113] added scopes to query siblings of a node with the given ID --- src/Franzose/ClosureTable/Models/Entity.php | 240 +++++++++++++++++++- tests/Entity/SiblingQueryTests.php | 69 ++++++ 2 files changed, 307 insertions(+), 2 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index e486e9b..c29179a 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -39,16 +39,27 @@ * @method Builder childrenRange(int $from, int $to = null) * @method Builder childrenRangeOf($id, int $from, int $to = null) * @method Builder sibling() + * @method Builder siblingOf($id) * @method Builder siblings() + * @method Builder siblingsOf($id) * @method Builder neighbors() + * @method Builder neighborsOf($id) * @method Builder siblingAt(int $position) + * @method Builder siblingOfAt($id, int $position) * @method Builder firstSibling() + * @method Builder firstSiblingOf($id) * @method Builder lastSibling() + * @method Builder lastSiblingOf($id) * @method Builder prevSibling() + * @method Builder prevSiblingOf($id) * @method Builder prevSiblings() + * @method Builder prevSiblingsOf($id) * @method Builder nextSibling() + * @method Builder nextSiblingOf($id) * @method Builder nextSiblings() + * @method Builder nextSiblingsOf($id) * @method Builder siblingsRange(int $from, int $to = null) + * @method Builder siblingsRangeOf($id, int $from, int $to = null) * * @package Franzose\ClosureTable */ @@ -981,6 +992,19 @@ public function scopeSibling(Builder $builder) return $builder->where($this->getParentIdColumn(), '=', $this->parent_id); } + /** + * Returns query builder for siblings of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeSiblingOf(Builder $builder, $id) + { + return $this->buildSiblingQuery($builder, $id); + } + /** * Returns siblings query builder. * @@ -995,6 +1019,23 @@ public function scopeSiblings(Builder $builder) ->where($this->getPositionColumn(), '<>', $this->position); } + /** + * Return query builder for siblings of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeSiblingsOf(Builder $builder, $id) + { + return $this->buildSiblingQuery($builder, $id, function ($position) { + return function (Builder $builder) use ($position) { + $builder->where($this->getPositionColumn(), '<>', $position); + }; + }); + } + /** * Retrives all siblings of a model. * @@ -1043,6 +1084,23 @@ public function scopeNeighbors(Builder $builder) ->whereIn($this->getPositionColumn(), [$position - 1, $position + 1]); } + /** + * Returns query builder for the neighbors of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeNeighborsOf(Builder $builder, $id) + { + return $this->buildSiblingQuery($builder, $id, function ($position) { + return function (Builder $builder) use ($position) { + return $builder->whereIn($this->getPositionColumn(), [$position - 1, $position + 1]); + }; + }); + } + /** * Retrieves neighbors (immediate previous and immediate next models) of a model. * @@ -1070,6 +1128,22 @@ public function scopeSiblingAt(Builder $builder, $position) ->where($this->getPositionColumn(), '=', $position); } + /** + * Returns query builder for a sibling at the given position of a node of the given ID. + * + * @param Builder $builder + * @param mixed $id + * @param int $position + * + * @return Builder + */ + public function scopeSiblingOfAt(Builder $builder, $id, $position) + { + return $this + ->scopeSiblingOf($builder, $id) + ->where($this->getPositionColumn(), '=', $position); + } + /** * Retrieves a model's sibling with given position. * @@ -1085,11 +1159,26 @@ public function getSiblingAt($position, array $columns = ['*']) /** * Returns query builder for the first sibling. * + * @param Builder $builder + * + * @return Builder + */ + public function scopeFirstSibling(Builder $builder) + { + return $this->scopeSiblingAt($builder, 0); + } + + /** + * Returns query builder for the first sibling of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * * @return Builder */ - public function scopeFirstSibling() + public function scopeFirstSiblingOf(Builder $builder, $id) { - return $this->siblingAt(0); + return $this->scopeSiblingOfAt($builder, $id, 0); } /** @@ -1115,6 +1204,22 @@ public function scopeLastSibling(Builder $builder) return $this->scopeSiblings($builder)->orderByDesc($this->getPositionColumn()); } + /** + * Returns query builder for the last sibling of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeLastSiblingOf(Builder $builder, $id) + { + return $this + ->scopeSiblingOf($builder, $id) + ->orderByDesc($this->getPositionColumn()) + ->limit(1); + } + /** * Retrieves the last model's sibling. * @@ -1140,6 +1245,23 @@ public function scopePrevSibling(Builder $builder) ->where($this->getPositionColumn(), '=', $this->position - 1); } + /** + * Returns query builder for the previous sibling of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopePrevSiblingOf(Builder $builder, $id) + { + return $this->buildSiblingQuery($builder, $id, function ($position) { + return function (Builder $builder) use ($position) { + return $builder->where($this->getPositionColumn(), '=', $position - 1); + }; + }); + } + /** * Retrieves immediate previous sibling of a model. * @@ -1165,6 +1287,23 @@ public function scopePrevSiblings(Builder $builder) ->where($this->getPositionColumn(), '<', $this->position); } + /** + * Returns query builder for the previous siblings of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopePrevSiblingsOf(Builder $builder, $id) + { + return $this->buildSiblingQuery($builder, $id, function ($position) { + return function (Builder $builder) use ($position) { + return $builder->where($this->getPositionColumn(), '<', $position); + }; + }); + } + /** * Retrieves all previous siblings of a model. * @@ -1211,6 +1350,23 @@ public function scopeNextSibling(Builder $builder) ->where($this->getPositionColumn(), '=', $this->position + 1); } + /** + * Returns query builder for the next sibling of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeNextSiblingOf(Builder $builder, $id) + { + return $this->buildSiblingQuery($builder, $id, function ($position) { + return function (Builder $builder) use ($position) { + return $builder->where($this->getPositionColumn(), '=', $position + 1); + }; + }); + } + /** * Retrieves immediate next sibling of a model. * @@ -1236,6 +1392,23 @@ public function scopeNextSiblings(Builder $builder) ->where($this->getPositionColumn(), '>', $this->position); } + /** + * Returns query builder for the next siblings of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * + * @return Builder + */ + public function scopeNextSiblingsOf(Builder $builder, $id) + { + return $this->buildSiblingQuery($builder, $id, function ($position) { + return function (Builder $builder) use ($position) { + return $builder->where($this->getPositionColumn(), '>', $position); + }; + }); + } + /** * Retrieves all next siblings of a model. * @@ -1292,6 +1465,31 @@ public function scopeSiblingsRange(Builder $builder, $from, $to = null) return $query; } + /** + * Returns query builder for a range of siblings of a node with the given ID. + * + * @param Builder $builder + * @param mixed $id + * @param int $from + * @param int|null $to + * + * @return Builder + */ + public function scopeSiblingsRangeOf(Builder $builder, $id, $from, $to = null) + { + $position = $this->getPositionColumn(); + + $query = $this + ->buildSiblingQuery($builder, $id) + ->where($position, '>=', $from); + + if ($to !== null) { + $query->where($position, '<=', $to); + } + + return $query; + } + /** * Retrieves siblings within given positions range. * @@ -1305,6 +1503,44 @@ public function getSiblingsRange($from, $to = null, array $columns = ['*']) return $this->siblingsRange($from, $to)->get($columns); } + /** + * Builds query for siblings. + * + * @param Builder $builder + * @param mixed $id + * @param callable|null $positionCallback + * + * @return Builder + */ + private function buildSiblingQuery(Builder $builder, $id, callable $positionCallback = null) + { + $parentIdColumn = $this->getParentIdColumn(); + $positionColumn = $this->getPositionColumn(); + + $entity = $this + ->select([$this->getKeyName(), $parentIdColumn, $positionColumn]) + ->from($this->getTable()) + ->where($this->getKeyName(), '=', $id) + ->limit(1) + ->first(); + + if ($entity === null) { + return $builder; + } + + if ($entity->parent_id === null) { + $builder->whereNull($parentIdColumn); + } else { + $builder->where($parentIdColumn, '=', $entity->parent_id); + } + + if (is_callable($positionCallback)) { + $builder->where($positionCallback($entity->position)); + } + + return $builder; + } + /** * Appends a sibling within the current depth. * diff --git a/tests/Entity/SiblingQueryTests.php b/tests/Entity/SiblingQueryTests.php index 71e1ac6..e71cda3 100644 --- a/tests/Entity/SiblingQueryTests.php +++ b/tests/Entity/SiblingQueryTests.php @@ -136,4 +136,73 @@ public function testGetSiblingsOpenRange() { static::assertCount(2, Entity::find(15)->getSiblingsRange(1)); } + + public function testSiblingOfScope() + { + static::assertEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], Entity::siblingOf(9)->get()->modelKeys()); + static::assertEquals([10, 13, 14, 15], Entity::siblingOf(10)->get()->modelKeys()); + } + + public function testSiblingsOfScope() + { + static::assertEquals([1, 2, 3, 4, 5, 6, 7, 8], Entity::siblingsOf(9)->get()->modelKeys()); + static::assertEquals([10, 14, 15], Entity::siblingsOf(13)->get()->modelKeys()); + } + + public function testNeighborsOfScope() + { + static::assertEquals([7, 9], Entity::neighborsOf(8)->get()->modelKeys()); + static::assertEquals([10, 14], Entity::neighborsOf(13)->get()->modelKeys()); + } + + public function testSiblingOfAtScope() + { + static::assertEquals([2], Entity::siblingOfAt(9, 1)->get()->modelKeys()); + static::assertEquals([14], Entity::siblingOfAt(10, 2)->get()->modelKeys()); + } + + + public function testFirstSiblingOfScope() + { + static::assertEquals([1], Entity::firstSiblingOf(9)->get()->modelKeys()); + static::assertEquals([10], Entity::firstSiblingOf(15)->get()->modelKeys()); + } + + public function testLastSiblingOfScope() + { + static::assertEquals([9], Entity::lastSiblingOf(1)->get()->modelKeys()); + static::assertEquals([15], Entity::lastSiblingOf(10)->get()->modelKeys()); + } + + public function testPrevSiblingOfScope() + { + static::assertEquals([8], Entity::prevSiblingOf(9)->get()->modelKeys()); + static::assertEquals([14], Entity::prevSiblingOf(15)->get()->modelKeys()); + } + + public function testPrevSiblingsOfScope() + { + static::assertEquals([1, 2, 3, 4, 5, 6, 7, 8], Entity::prevSiblingsOf(9)->get()->modelKeys()); + static::assertEquals([10, 13, 14], Entity::prevSiblingsOf(15)->get()->modelKeys()); + } + + public function testNextSiblingOfScope() + { + static::assertEquals([9], Entity::nextSiblingOf(8)->get()->modelKeys()); + static::assertEquals([15], Entity::nextSiblingOf(14)->get()->modelKeys()); + } + + public function testNextSiblingsOfScope() + { + static::assertEquals([2, 3, 4, 5, 6, 7, 8, 9], Entity::nextSiblingsOf(1)->get()->modelKeys()); + static::assertEquals([13, 14, 15], Entity::nextSiblingsOf(10)->get()->modelKeys()); + } + + public function testSiblingsRangeOfScope() + { + static::assertEquals([6, 7, 8, 9], Entity::siblingsRangeOf(1, 5)->get()->modelKeys()); + static::assertEquals([3, 4, 5, 6], Entity::siblingsRangeOf(1, 2, 5)->get()->modelKeys()); + static::assertEquals([13, 14, 15], Entity::siblingsRangeOf(10, 1)->get()->modelKeys()); + static::assertEquals([13, 14, 15], Entity::siblingsRangeOf(10, 1, 3)->get()->modelKeys()); + } } From dd93c189dc93d6e9149ca8b2ae800ac82638a847 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 7 Apr 2020 20:02:30 +0700 Subject: [PATCH 047/113] fixed more positioning issues --- src/Franzose/ClosureTable/Models/Entity.php | 36 +++++-- tests/Entity/ChildManipulationTests.php | 3 +- tests/Entity/ConstructionTests.php | 22 +++- tests/Entity/PositioningTests.php | 60 +++++++++++ tests/EntityTestCase.php | 112 -------------------- 5 files changed, 112 insertions(+), 121 deletions(-) diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Franzose/ClosureTable/Models/Entity.php index c29179a..39c8ae0 100644 --- a/src/Franzose/ClosureTable/Models/Entity.php +++ b/src/Franzose/ClosureTable/Models/Entity.php @@ -86,6 +86,13 @@ class Entity extends Eloquent implements EntityInterface */ private $previousPosition; + /** + * Whether this node is being moved to another parent node. + * + * @var bool + */ + private $isMoved = false; + /** * Indicates if the model should soft delete. * @@ -156,8 +163,10 @@ public function setParentIdAttribute($value) if ($this->parent_id === $value) { return; } - $this->previousParentId = $this->parent_id; - $this->attributes[$this->getParentIdColumn()] = $value; + + $parentId = $this->getParentIdColumn(); + $this->previousParentId = isset($this->original[$parentId]) ? $this->original[$parentId] : null; + $this->attributes[$parentId] = $value; } /** @@ -200,8 +209,10 @@ public function setPositionAttribute($value) if ($this->position === $value) { return; } - $this->previousPosition = $this->position; - $this->attributes[$this->getPositionColumn()] = max(0, (int) $value); + + $position = $this->getPositionColumn(); + $this->previousPosition = isset($this->original[$position]) ? $this->original[$position] : null; + $this->attributes[$position] = max(0, (int) $value); } /** @@ -266,8 +277,17 @@ public static function boot() parent::boot(); static::saving(static function (Entity $entity) { - $newPosition = max(0, min($entity->position, $entity->getLatestPosition())); - $entity->attributes[$entity->getPositionColumn()] = $newPosition; + if ($entity->isDirty($entity->getPositionColumn())) { + $latest = $entity->getLatestPosition(); + + if (!$entity->isMoved) { + $latest--; + } + + $entity->position = max(0, min($entity->position, $latest)); + } elseif (!$entity->exists) { + $entity->position = $entity->getLatestPosition(); + } }); // When entity is created, the appropriate @@ -1749,7 +1769,10 @@ public function moveTo($position, $ancestor = null) $this->parent_id = $parentId; $this->position = $position; + + $this->isMoved = true; $this->save(); + $this->isMoved = false; return $this; } @@ -1785,6 +1808,7 @@ private function reorderSiblings() if ($this->previousPosition !== null) { $this + ->where($this->getKeyName(), '<>', $this->getKey()) ->where($this->getParentIdColumn(), '=', $this->previousParentId) ->where($position, '>', $this->previousPosition) ->decrement($position); diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Entity/ChildManipulationTests.php index 835651e..268362e 100644 --- a/tests/Entity/ChildManipulationTests.php +++ b/tests/Entity/ChildManipulationTests.php @@ -77,7 +77,8 @@ public function testAddChildToTheLastPosition() 10 => 0, 13 => 1, 14 => 2, - 15 => 3 + 15 => 3, + 12 => 4, ]); } diff --git a/tests/Entity/ConstructionTests.php b/tests/Entity/ConstructionTests.php index 8d303be..935c3ed 100644 --- a/tests/Entity/ConstructionTests.php +++ b/tests/Entity/ConstructionTests.php @@ -4,9 +4,10 @@ use Franzose\ClosureTable\Models\ClosureTable; use Franzose\ClosureTable\Models\Entity; -use PHPUnit\Framework\TestCase; +use Franzose\ClosureTable\Tests\BaseTestCase; +use Franzose\ClosureTable\Tests\Page; -class ConstructionTests extends TestCase +class ConstructionTests extends BaseTestCase { public function testPositionMustBeFillable() { @@ -49,4 +50,21 @@ public function testNewFromBuilder() static::assertEquals(321, static::readAttribute($newEntity, 'previousParentId')); static::assertEquals(0, static::readAttribute($newEntity, 'previousPosition')); } + + public function testCreate() + { + $entity = new Page(['title' => 'Item 1']); + + static::assertEquals(null, $entity->position); + static::assertEquals(null, static::readAttribute($entity, 'previousPosition')); + static::assertEquals(null, $entity->parent_id); + static::assertEquals(null, static::readAttribute($entity, 'previousParentId')); + + $entity->save(); + + static::assertEquals(9, $entity->position); + static::assertEquals(null, static::readAttribute($entity, 'previousPosition')); + static::assertEquals(null, $entity->parent_id); + static::assertEquals(null, static::readAttribute($entity, 'previousParentId')); + } } diff --git a/tests/Entity/PositioningTests.php b/tests/Entity/PositioningTests.php index 6426980..02a1ae9 100644 --- a/tests/Entity/PositioningTests.php +++ b/tests/Entity/PositioningTests.php @@ -2,11 +2,71 @@ namespace Franzose\ClosureTable\Tests\Entity; +use DB; +use Franzose\ClosureTable\Models\ClosureTable; use Franzose\ClosureTable\Models\Entity; use Franzose\ClosureTable\Tests\BaseTestCase; +use Franzose\ClosureTable\Tests\Page; final class PositioningTests extends BaseTestCase { + public function testCreate() + { + DB::statement('SET foreign_key_checks=0'); + ClosureTable::truncate(); + Entity::truncate(); + DB::statement('SET foreign_key_checks=1'); + + $entity1 = new Entity; + $entity1->save(); + static::assertEquals(0, $entity1->position); + + $entity2 = new Entity; + $entity2->save(); + static::assertEquals(1, $entity2->position); + + static::assertModelAttribute('position', [1 => 0, 2 => 1]); + } + + public function testSavingLoadedEntityShouldNotTriggerReordering() + { + $entity = new Page(['title' => 'Item 1']); + $entity->save(); + + $id = $entity->getKey(); + $parentId = $entity->parent_id; + $position = $entity->position; + + $sameEntity = Page::find($id); + + // Sibling node that shouldn't move + static::assertEquals(8, Page::find(9)->position); + static::assertEquals( + $position, + $sameEntity->position, + 'Position should be the same after a load' + ); + + static::assertEquals( + $parentId, + $sameEntity->parent_id, + 'Parent should be the same after a load' + ); + + $sameEntity->title = 'New title'; + $sameEntity->save(); + + static::assertEquals( + 8, + Page::find(9)->position, + 'Sibling node should not have been moved' + ); + + static::assertEquals($id, $sameEntity->getKey()); + static::assertEquals($position, $sameEntity->position); + static::assertEquals($parentId, $sameEntity->parent_id); + } + public function testMoveToTheFirstPosition() { $entity = Entity::find(9); diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php index 3681c9b..3da4fdb 100644 --- a/tests/EntityTestCase.php +++ b/tests/EntityTestCase.php @@ -41,103 +41,6 @@ public function setUp() $this->childrenRelationIndex = $this->entity->getChildrenRelationIndex(); } - public function testCreate() - { - DB::statement("SET foreign_key_checks=0"); - ClosureTable::truncate(); - Entity::truncate(); - DB::statement("SET foreign_key_checks=1"); - - $entity1 = new Entity; - $entity1->save(); - - $this->assertEquals(0, $entity1->position); - - $entity2 = new Entity; - $entity2->save(); - $this->assertEquals(1, $entity2->position); - } - - public function testCreateSetsPosition() - { - $entity = new Page(['title' => 'Item 1']); - - $this->assertEquals(null, $entity->position); - $this->assertEquals(null, $this->readAttribute($entity, 'previousPosition')); - $this->assertEquals(null, $entity->parent_id); - $this->assertEquals(null, $this->readAttribute($entity, 'previousParentId')); - - $entity->save(); - - $this->assertEquals(9, $entity->position); - $this->assertEquals($entity->position, $this->readAttribute($entity, 'previousPosition')); - $this->assertEquals(null, $entity->parent_id); - $this->assertEquals($entity->parent_id, $this->readAttribute($entity, 'previousParentId')); - } - - /** - * @dataProvider createUseGivenPositionProvider - */ - public function testCreateUseGivenPosition($initial_position, $test_entity, $assign_position, $expected_position, $test_position) - { - $this->assertEquals($initial_position, Page::find($test_entity)->position, 'Prerequisite doesn\'t match expectation'); - - $entity = new Page(['title' => 'Item 1']); - $entity->position = $assign_position; - $entity->save(); - - $this->assertEquals($expected_position, $entity->position, 'Saved position should match expected position'); - $this->assertEquals($test_position, Page::find($test_entity)->position, 'Test entity should have expected position'); - } - - public function createUseGivenPositionProvider() - { - return [ - [0, 1, -1, 0, 1, 1], // Negative clamps to 0 - [0, 1, 0, 0, 1], // 0 moves previous 0 to 1 - [3, 4, 3, 3, 4], // Test in mid range - [8, 9, 8, 8, 9], // Last existing entity - [8, 9, 9, 9, 8], // Add after last position - [8, 9, null, 9, 8], // Do not specify position = after last position - ]; - } - - public function testCreateDoesNotChangePositionOfSiblings() - { - $entity1 = new Page(['title' => 'Item 1']); - $entity1->save(); - - $id = $entity1->getKey(); - - $entity2 = new Page(['title' => 'Item 2']); - $entity2->save(); - - $this->assertEquals(10, $entity2->position); - $this->assertEquals(9, Entity::find($id)->position); - } - - public function testSavingLoadedEntityShouldNotTriggerReordering() - { - $entity1 = new Page(['title' => 'Item 1']); - $entity1->save(); - - $id = $entity1->getKey(); - - $entity1 = Page::find($id); - - $this->assertEquals(8, Page::find(9)->position); // Sibling node that shouldn't move - - $this->assertEquals($entity1->position, $this->readAttribute($entity1, 'previousPosition'), 'Position should be the same after a load'); - $this->assertEquals($entity1->parent_id, $this->readAttribute($entity1, 'previousParentId'), 'Parent should be the same after a load'); - - $entity1->title = 'New title'; - $entity1->save(); - - $this->assertEquals(8, Page::find(9)->position, 'Sibling node should not have moved'); - $this->assertEquals($entity1->position, $this->readAttribute($entity1, 'previousPosition')); - $this->assertEquals($entity1->parent_id, $this->readAttribute($entity1, 'previousParentId')); - } - /** * @expectedException \InvalidArgumentException */ @@ -157,21 +60,6 @@ public function testMoveTo() $this->assertEquals($this->entity->getParent()->getKey(), $ancestor->getKey()); } - public function testClampPosition() - { - $ancestor = Entity::find(9); - $entity = Entity::find(15); - $entity->position = -1; - $entity->save(); - - $this->assertEquals(0, $entity->position); - - $entity->position = 100; - $entity->save(); - - $this->assertEquals($ancestor->countChildren(), $entity->position); - } - public function testGetParentAfterMovingToAnAncestor() { $entity = Entity::find(10); From 7cf3fa1ea57e088e3d38d29d1380bad66e52fdc9 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 7 Apr 2020 20:13:19 +0700 Subject: [PATCH 048/113] refactored tests --- tests/ClosureTableTestCase.php | 11 +++ tests/Entity/MovementTests.php | 109 ++++++++++++++++++++++ tests/EntityTestCase.php | 161 --------------------------------- 3 files changed, 120 insertions(+), 161 deletions(-) create mode 100644 tests/Entity/MovementTests.php delete mode 100644 tests/EntityTestCase.php diff --git a/tests/ClosureTableTestCase.php b/tests/ClosureTableTestCase.php index cf88e4f..e2bff9d 100644 --- a/tests/ClosureTableTestCase.php +++ b/tests/ClosureTableTestCase.php @@ -2,6 +2,7 @@ namespace Franzose\ClosureTable\Tests; use Franzose\ClosureTable\Models\ClosureTable; +use Franzose\ClosureTable\Models\Entity; class ClosureTableTestCase extends BaseTestCase { @@ -58,4 +59,14 @@ public function testDepthQualifiedKeyName() $this->ctable->getQualifiedDepthColumn() ); } + + public function testNewNodeShouldBeInsertedIntoClosureTable() + { + $entity = Entity::create(['title' => 'abcde']); + $closure = ClosureTable::whereDescendant($entity->getKey())->first(); + + static::assertNotNull($closure); + static::assertEquals($entity->getKey(), $closure->ancestor); + static::assertEquals(0, $closure->depth); + } } diff --git a/tests/Entity/MovementTests.php b/tests/Entity/MovementTests.php new file mode 100644 index 0000000..1032195 --- /dev/null +++ b/tests/Entity/MovementTests.php @@ -0,0 +1,109 @@ +expectException(InvalidArgumentException::class); + + $entity = Entity::find(9); + + $entity->moveTo(0, $entity); + } + + public function testMoveTo() + { + $parent = Entity::find(1); + $child = Entity::find(2); + $result = $child->moveTo(0, $parent); + + static::assertSame($child, $result); + static::assertEquals(0, $result->position); + static::assertEquals(1, $result->parent_id); + static::assertEquals($parent->getKey(), $result->getParent()->getKey()); + } + + public function testInsertedNodeDepth() + { + $entity = Entity::create(['title' => 'abcde']); + $child = Entity::create(['title' => 'abcde']); + $child->moveTo(0, $entity); + + $closure = ClosureTable::whereDescendant($child->getKey()) + ->whereAncestor($entity->getKey())->first(); + + static::assertNotNull($closure); + static::assertEquals(1, $closure->depth); + } + + public function testValidNumberOfRowsInsertedByInsertNode() + { + $ancestor = Entity::create(['title' => 'abcde']); + $descendant = Entity::create(['title' => 'abcde']); + $descendant->moveTo(0, $ancestor); + + $ancestorRows = ClosureTable::whereDescendant($ancestor->getKey())->count(); + $descendantRows = ClosureTable::whereDescendant($descendant->getKey())->count(); + static::assertEquals(1, $ancestorRows); + static::assertEquals(2, $descendantRows); + } + + public function testMoveNodeToAnotherAncestor() + { + $descendant = Entity::find(1); + $descendant->moveTo(0, 2); + + $ancestors = ClosureTable::whereDescendant(2)->count(); + $descendants = ClosureTable::whereDescendant(1)->count(); + static::assertEquals(1, $ancestors); + static::assertEquals(2, $descendants); + } + + public function testMoveNodeToDeepNesting() + { + $item = Entity::find(1); + $item->moveTo(0, 2); + + $item = Entity::find(2); + $item->moveTo(0, 3); + + $item = Entity::find(3); + $item->moveTo(0, 4); + + $item = Entity::find(4); + $item->moveTo(0, 5); + + $descendantRows = ClosureTable::whereDescendant(1)->count(); + $ancestorRows = ClosureTable::whereDescendant(2)->count(); + + static::assertEquals(4, $ancestorRows); + static::assertEquals(5, $descendantRows); + } + + public function testMoveNodeToBecomeRoot() + { + $item = Entity::find(1); + $item->moveTo(0, 2); + + $item = Entity::find(2); + $item->moveTo(0, 3); + + $item = Entity::find(3); + $item->moveTo(0, 4); + + $item = Entity::find(4); + $item->moveTo(0, 5); + + $item = Entity::find(1); + $item->moveTo(0); + + static::assertEquals(1, ClosureTable::whereDescendant(1)->count()); + } +} diff --git a/tests/EntityTestCase.php b/tests/EntityTestCase.php deleted file mode 100644 index 3da4fdb..0000000 --- a/tests/EntityTestCase.php +++ /dev/null @@ -1,161 +0,0 @@ -entity = new Entity; - $this->entity->fillable(['title', 'excerpt', 'body', 'position']); - - $this->childrenRelationIndex = $this->entity->getChildrenRelationIndex(); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testMoveToThrowsException() - { - $this->entity->moveTo(0, $this->entity); - } - - public function testMoveTo() - { - $ancestor = Entity::find(1); - $result = $this->entity->moveTo(0, $ancestor); - - $this->assertSame($this->entity, $result); - $this->assertEquals(0, $result->position); - $this->assertEquals(1, $result->parent_id); - $this->assertEquals($this->entity->getParent()->getKey(), $ancestor->getKey()); - } - - public function testGetParentAfterMovingToAnAncestor() - { - $entity = Entity::find(10); - $entity->moveTo(0, 15); - $parent = $entity->getParent(); - - $this->assertInstanceOf('Franzose\ClosureTable\Models\Entity', $parent); - $this->assertEquals(15, $parent->getKey()); - } - - public function testInsertNode() - { - $entity = Entity::create(['title' => 'abcde']); - $closure = ClosureTable::whereDescendant($entity->getKey())->first(); - - $this->assertNotNull($closure); - $this->assertEquals($entity->getKey(), $closure->ancestor); - $this->assertEquals(0, $closure->depth); - } - - public function testInsertedNodeDepth() - { - $entity = Entity::create(['title' => 'abcde']); - $child = Entity::create(['title' => 'abcde']); - $child->moveTo(0, $entity); - - $closure = ClosureTable::whereDescendant($child->getKey()) - ->whereAncestor($entity->getKey())->first(); - - $this->assertNotNull($closure); - $this->assertEquals(1, $closure->depth); - } - - public function testValidNumberOfRowsInsertedByInsertNode() - { - $ancestor = Entity::create(['title' => 'abcde']); - $descendant = Entity::create(['title' => 'abcde']); - $descendant->moveTo(0, $ancestor); - - $ancestorRows = ClosureTable::whereDescendant($ancestor->getKey())->count(); - $descendantRows = ClosureTable::whereDescendant($descendant->getKey())->count(); - - $this->assertEquals(1, $ancestorRows); - $this->assertEquals(2, $descendantRows); - } - - public function testMoveNodeToAnotherAncestor() - { - $descendant = Entity::find(1); - $descendant->moveTo(0, 2); - - $ancestors = ClosureTable::whereDescendant(2)->count(); - $descendants = ClosureTable::whereDescendant(1)->count(); - - $this->assertEquals(1, $ancestors); - $this->assertEquals(2, $descendants); - } - - public function testMoveNodeToDeepNesting() - { - $item = Entity::find(1); - $item->moveTo(0, 2); - - $item = Entity::find(2); - $item->moveTo(0, 3); - - $item = Entity::find(3); - $item->moveTo(0, 4); - - $item = Entity::find(4); - $item->moveTo(0, 5); - - $descendantRows = ClosureTable::whereDescendant(1)->count(); - $ancestorRows = ClosureTable::whereDescendant(2)->count(); - - $this->assertEquals(4, $ancestorRows); - $this->assertEquals(5, $descendantRows); - } - - public function testMoveNodeToBecomeRoot() - { - $item = Entity::find(1); - $item->moveTo(0, 2); - - $item = Entity::find(2); - $item->moveTo(0, 3); - - $item = Entity::find(3); - $item->moveTo(0, 4); - - $item = Entity::find(4); - $item->moveTo(0, 5); - - $item = Entity::find(1); - $item->moveTo(0); - - $this->assertEquals(1, ClosureTable::whereDescendant(1)->count()); - } -} From 7c5bb81fe8ac44b30d0616bd7679fca8b01d48f6 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 7 Apr 2020 20:15:47 +0700 Subject: [PATCH 049/113] refactored tests --- tests/BaseTestCase.php | 13 ------------- tests/Entity/DescendantTests.php | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index a4b3994..f13d693 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -56,19 +56,6 @@ protected function getEnvironmentSetUp($app) ]); } - /** - * Asserts if two arrays have similar values, sorting them before the fact in order to "ignore" ordering. - * @param array $actual - * @param array $expected - * @param string $message - * @param float $delta - * @param int $depth - */ - protected function assertArrayValuesEquals(array $actual, array $expected, $message = '', $delta = 0.0, $depth = 10) - { - $this->assertEquals($actual, $expected, $message, $delta, $depth, true); - } - public static function assertModelAttribute($attribute, array $expected) { $actual = Entity::whereIn('id', array_keys($expected)) diff --git a/tests/Entity/DescendantTests.php b/tests/Entity/DescendantTests.php index 67b87de..4fc71c6 100644 --- a/tests/Entity/DescendantTests.php +++ b/tests/Entity/DescendantTests.php @@ -57,7 +57,7 @@ public function testGetDescendants() static::assertInstanceOf(Collection::class, $descendants); static::assertCount(6, $descendants); - $this->assertArrayValuesEquals($descendants->modelKeys(), [10, 11, 12, 13, 14, 15]); + static::assertEquals([10, 11, 12, 13, 14, 15], $descendants->modelKeys()); } public function testGetDescendantsWhere() @@ -65,7 +65,7 @@ public function testGetDescendantsWhere() $descendants = Entity::find(9)->getDescendantsWhere('position', '=', 1); static::assertCount(1, $descendants); - $this->assertArrayValuesEquals($descendants->modelKeys(), [13]); + static::assertEquals([13], $descendants->modelKeys()); } public function testCountDescendants() From 29dcdc90bca666609dfeceb6c34e6d960010d650 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 8 Apr 2020 22:42:09 +0700 Subject: [PATCH 050/113] added some useful methods to Collection --- .../ClosureTable/Extensions/Collection.php | 44 ++++++++++++++++++ tests/CollectionTestCase.php | 45 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/Franzose/ClosureTable/Extensions/Collection.php b/src/Franzose/ClosureTable/Extensions/Collection.php index 0fb69c4..e3cd8f7 100644 --- a/src/Franzose/ClosureTable/Extensions/Collection.php +++ b/src/Franzose/ClosureTable/Extensions/Collection.php @@ -67,6 +67,50 @@ public function getRange($from, $to = null) }); } + /** + * Filters collection to return nodes on the "left" + * and on the "right" from the node with the given position. + * + * @param int $position + * + * @return Collection + */ + public function getNeighbors($position) + { + return $this->filter(static function (Entity $entity) use ($position) { + return $entity->position === $position - 1 || + $entity->position === $position + 1; + }); + } + + /** + * Filters collection to return previous siblings of a node with the given position. + * + * @param int $position + * + * @return Collection + */ + public function getPrevSiblings($position) + { + return $this->filter(static function (Entity $entity) use ($position) { + return $entity->position < $position; + }); + } + + /** + * Filters collection to return next siblings of a node with the given position. + * + * @param int $position + * + * @return Collection + */ + public function getNextSiblings($position) + { + return $this->filter(static function (Entity $entity) use ($position) { + return $entity->position > $position; + }); + } + /** * Retrieves children relation. * diff --git a/tests/CollectionTestCase.php b/tests/CollectionTestCase.php index 2e748b4..31c49cb 100644 --- a/tests/CollectionTestCase.php +++ b/tests/CollectionTestCase.php @@ -53,6 +53,51 @@ public function testGetRange() static::assertEquals([1, 2, 3], $collection->getRange(1, 3)->pluck('position')->toArray()); } + public function testGetNeighbors() + { + $collection = new Collection([ + new Page(['position' => 0]), + new Page(['position' => 1]), + new Page(['position' => 2]), + new Page(['position' => 3]), + ]); + + $neighbors = $collection->getNeighbors(1); + + static::assertCount(2, $neighbors); + static::assertEquals([0, 2], $neighbors->pluck('position')->toArray()); + } + + public function testGetPrevSiblings() + { + $collection = new Collection([ + new Page(['position' => 0]), + new Page(['position' => 1]), + new Page(['position' => 2]), + new Page(['position' => 3]), + ]); + + $siblings = $collection->getPrevSiblings(3); + + static::assertCount(3, $siblings); + static::assertEquals([0, 1, 2], $siblings->pluck('position')->toArray()); + } + + public function testGetNextSiblings() + { + $collection = new Collection([ + new Page(['position' => 0]), + new Page(['position' => 1]), + new Page(['position' => 2]), + new Page(['position' => 3]), + ]); + + $siblings = $collection->getNextSiblings(0); + + static::assertCount(3, $siblings); + static::assertEquals([1, 2, 3], $siblings->pluck('position')->toArray()); + } + public function testGetChildrenOf() { $entity = new Page(['position' => 0]); From bbbebc222f590dade2fd2a71215357d7d5f7eb9d Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 12:35:08 +0700 Subject: [PATCH 051/113] removed non-existing command --- src/Franzose/ClosureTable/ClosureTableServiceProvider.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Franzose/ClosureTable/ClosureTableServiceProvider.php b/src/Franzose/ClosureTable/ClosureTableServiceProvider.php index 7fe80bb..38a096b 100644 --- a/src/Franzose/ClosureTable/ClosureTableServiceProvider.php +++ b/src/Franzose/ClosureTable/ClosureTableServiceProvider.php @@ -2,7 +2,6 @@ namespace Franzose\ClosureTable; use Illuminate\Support\ServiceProvider; -use Franzose\ClosureTable\Console\ClosureTableCommand; use Franzose\ClosureTable\Console\MakeCommand; use Franzose\ClosureTable\Generators\Migration as Migrator; use Franzose\ClosureTable\Generators\Model as Modeler; @@ -43,16 +42,11 @@ public function boot() */ public function register() { - // Here we register commands for artisan - $this->app->singleton('command.closuretable', function ($app) { - return new ClosureTableCommand; - }); - $this->app->singleton('command.closuretable.make', function ($app) { return $app['Franzose\ClosureTable\Console\MakeCommand']; }); - $this->commands('command.closuretable', 'command.closuretable.make'); + $this->commands('command.closuretable.make'); } /** From c402d2010de4579043869d677e68d450cae68520 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 12:49:48 +0700 Subject: [PATCH 052/113] cleaned up the service provider --- .../ClosureTableServiceProvider.php | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/Franzose/ClosureTable/ClosureTableServiceProvider.php b/src/Franzose/ClosureTable/ClosureTableServiceProvider.php index 38a096b..17df77d 100644 --- a/src/Franzose/ClosureTable/ClosureTableServiceProvider.php +++ b/src/Franzose/ClosureTable/ClosureTableServiceProvider.php @@ -3,8 +3,6 @@ use Illuminate\Support\ServiceProvider; use Franzose\ClosureTable\Console\MakeCommand; -use Franzose\ClosureTable\Generators\Migration as Migrator; -use Franzose\ClosureTable\Generators\Model as Modeler; /** * ClosureTable service provider @@ -13,19 +11,6 @@ */ class ClosureTableServiceProvider extends ServiceProvider { - - /** - * Current library version - */ - const VERSION = 4; - - /** - * Indicates if loading of the provider is deferred. - * - * @var bool - */ - protected $defer = false; - /** * Bootstrap the application events. * @@ -42,8 +27,8 @@ public function boot() */ public function register() { - $this->app->singleton('command.closuretable.make', function ($app) { - return $app['Franzose\ClosureTable\Console\MakeCommand']; + $this->app->singleton('command.closuretable.make', static function ($app) { + return $app[MakeCommand::class]; }); $this->commands('command.closuretable.make'); @@ -56,7 +41,7 @@ public function register() */ public function provides() { - return array(); + return []; } } From 2456d97052d711927248f455c0826c99615a95bd Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 12:50:17 +0700 Subject: [PATCH 053/113] moved collection tests to extensions namespace --- tests/{CollectionTestCase.php => Extensions/CollectionTests.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{CollectionTestCase.php => Extensions/CollectionTests.php} (100%) diff --git a/tests/CollectionTestCase.php b/tests/Extensions/CollectionTests.php similarity index 100% rename from tests/CollectionTestCase.php rename to tests/Extensions/CollectionTests.php From d9c2d38c455ef7eb926e7edfe2518708df85c9bc Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 12:50:43 +0700 Subject: [PATCH 054/113] wrote tests for the Str helper class --- src/Franzose/ClosureTable/Extensions/Str.php | 4 ++-- tests/Extensions/StrTests.php | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 tests/Extensions/StrTests.php diff --git a/src/Franzose/ClosureTable/Extensions/Str.php b/src/Franzose/ClosureTable/Extensions/Str.php index de25ce1..8b72b70 100644 --- a/src/Franzose/ClosureTable/Extensions/Str.php +++ b/src/Franzose/ClosureTable/Extensions/Str.php @@ -13,7 +13,7 @@ class Str /** * Makes appropriate class name from given string. * - * @param $name + * @param string $name * @return string */ public static function classify($name) @@ -24,7 +24,7 @@ public static function classify($name) /** * Makes database table name from given class name. * - * @param $name + * @param string $name * @return string */ public static function tableize($name) diff --git a/tests/Extensions/StrTests.php b/tests/Extensions/StrTests.php new file mode 100644 index 0000000..eefd788 --- /dev/null +++ b/tests/Extensions/StrTests.php @@ -0,0 +1,20 @@ + Date: Thu, 9 Apr 2020 12:58:51 +0700 Subject: [PATCH 055/113] moved tests so that they better reflected the source structure --- tests/{ => Models}/ClosureTableTestCase.php | 0 tests/{ => Models}/Entity/AncestorTests.php | 0 tests/{ => Models}/Entity/ChildManipulationTests.php | 0 tests/{ => Models}/Entity/ChildQueryTests.php | 0 tests/{ => Models}/Entity/ConstructionTests.php | 0 tests/{ => Models}/Entity/CustomEntity.php | 0 tests/{ => Models}/Entity/DescendantTests.php | 0 tests/{ => Models}/Entity/MovementTests.php | 0 tests/{ => Models}/Entity/ParentRootTests.php | 0 tests/{ => Models}/Entity/PositioningTests.php | 0 tests/{ => Models}/Entity/SiblingManipulationTests.php | 0 tests/{ => Models}/Entity/SiblingQueryTests.php | 0 tests/{ => Models}/Entity/TreeTests.php | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => Models}/ClosureTableTestCase.php (100%) rename tests/{ => Models}/Entity/AncestorTests.php (100%) rename tests/{ => Models}/Entity/ChildManipulationTests.php (100%) rename tests/{ => Models}/Entity/ChildQueryTests.php (100%) rename tests/{ => Models}/Entity/ConstructionTests.php (100%) rename tests/{ => Models}/Entity/CustomEntity.php (100%) rename tests/{ => Models}/Entity/DescendantTests.php (100%) rename tests/{ => Models}/Entity/MovementTests.php (100%) rename tests/{ => Models}/Entity/ParentRootTests.php (100%) rename tests/{ => Models}/Entity/PositioningTests.php (100%) rename tests/{ => Models}/Entity/SiblingManipulationTests.php (100%) rename tests/{ => Models}/Entity/SiblingQueryTests.php (100%) rename tests/{ => Models}/Entity/TreeTests.php (100%) diff --git a/tests/ClosureTableTestCase.php b/tests/Models/ClosureTableTestCase.php similarity index 100% rename from tests/ClosureTableTestCase.php rename to tests/Models/ClosureTableTestCase.php diff --git a/tests/Entity/AncestorTests.php b/tests/Models/Entity/AncestorTests.php similarity index 100% rename from tests/Entity/AncestorTests.php rename to tests/Models/Entity/AncestorTests.php diff --git a/tests/Entity/ChildManipulationTests.php b/tests/Models/Entity/ChildManipulationTests.php similarity index 100% rename from tests/Entity/ChildManipulationTests.php rename to tests/Models/Entity/ChildManipulationTests.php diff --git a/tests/Entity/ChildQueryTests.php b/tests/Models/Entity/ChildQueryTests.php similarity index 100% rename from tests/Entity/ChildQueryTests.php rename to tests/Models/Entity/ChildQueryTests.php diff --git a/tests/Entity/ConstructionTests.php b/tests/Models/Entity/ConstructionTests.php similarity index 100% rename from tests/Entity/ConstructionTests.php rename to tests/Models/Entity/ConstructionTests.php diff --git a/tests/Entity/CustomEntity.php b/tests/Models/Entity/CustomEntity.php similarity index 100% rename from tests/Entity/CustomEntity.php rename to tests/Models/Entity/CustomEntity.php diff --git a/tests/Entity/DescendantTests.php b/tests/Models/Entity/DescendantTests.php similarity index 100% rename from tests/Entity/DescendantTests.php rename to tests/Models/Entity/DescendantTests.php diff --git a/tests/Entity/MovementTests.php b/tests/Models/Entity/MovementTests.php similarity index 100% rename from tests/Entity/MovementTests.php rename to tests/Models/Entity/MovementTests.php diff --git a/tests/Entity/ParentRootTests.php b/tests/Models/Entity/ParentRootTests.php similarity index 100% rename from tests/Entity/ParentRootTests.php rename to tests/Models/Entity/ParentRootTests.php diff --git a/tests/Entity/PositioningTests.php b/tests/Models/Entity/PositioningTests.php similarity index 100% rename from tests/Entity/PositioningTests.php rename to tests/Models/Entity/PositioningTests.php diff --git a/tests/Entity/SiblingManipulationTests.php b/tests/Models/Entity/SiblingManipulationTests.php similarity index 100% rename from tests/Entity/SiblingManipulationTests.php rename to tests/Models/Entity/SiblingManipulationTests.php diff --git a/tests/Entity/SiblingQueryTests.php b/tests/Models/Entity/SiblingQueryTests.php similarity index 100% rename from tests/Entity/SiblingQueryTests.php rename to tests/Models/Entity/SiblingQueryTests.php diff --git a/tests/Entity/TreeTests.php b/tests/Models/Entity/TreeTests.php similarity index 100% rename from tests/Entity/TreeTests.php rename to tests/Models/Entity/TreeTests.php From 9d99eb736dbf77e3a018e8a839495565f0c49d3b Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 13:01:38 +0700 Subject: [PATCH 056/113] PSR-0 -> PSR-4 --- composer.json | 4 ++-- .../ClosureTable => }/ClosureTableServiceProvider.php | 0 src/{Franzose/ClosureTable => }/Console/MakeCommand.php | 0 .../ClosureTable => }/Contracts/ClosureTableInterface.php | 0 src/{Franzose/ClosureTable => }/Contracts/EntityInterface.php | 0 src/{Franzose/ClosureTable => }/Extensions/Collection.php | 0 src/{Franzose/ClosureTable => }/Extensions/Str.php | 0 src/{Franzose/ClosureTable => }/Generators/Generator.php | 0 src/{Franzose/ClosureTable => }/Generators/Migration.php | 0 src/{Franzose/ClosureTable => }/Generators/Model.php | 0 .../Generators/stubs/migrations/closuretable.php | 0 .../ClosureTable => }/Generators/stubs/migrations/entity.php | 0 .../Generators/stubs/models/closuretable.php | 0 .../ClosureTable => }/Generators/stubs/models/entity.php | 0 src/{Franzose/ClosureTable => }/Models/ClosureTable.php | 0 src/{Franzose/ClosureTable => }/Models/Entity.php | 0 16 files changed, 2 insertions(+), 2 deletions(-) rename src/{Franzose/ClosureTable => }/ClosureTableServiceProvider.php (100%) rename src/{Franzose/ClosureTable => }/Console/MakeCommand.php (100%) rename src/{Franzose/ClosureTable => }/Contracts/ClosureTableInterface.php (100%) rename src/{Franzose/ClosureTable => }/Contracts/EntityInterface.php (100%) rename src/{Franzose/ClosureTable => }/Extensions/Collection.php (100%) rename src/{Franzose/ClosureTable => }/Extensions/Str.php (100%) rename src/{Franzose/ClosureTable => }/Generators/Generator.php (100%) rename src/{Franzose/ClosureTable => }/Generators/Migration.php (100%) rename src/{Franzose/ClosureTable => }/Generators/Model.php (100%) rename src/{Franzose/ClosureTable => }/Generators/stubs/migrations/closuretable.php (100%) rename src/{Franzose/ClosureTable => }/Generators/stubs/migrations/entity.php (100%) rename src/{Franzose/ClosureTable => }/Generators/stubs/models/closuretable.php (100%) rename src/{Franzose/ClosureTable => }/Generators/stubs/models/entity.php (100%) rename src/{Franzose/ClosureTable => }/Models/ClosureTable.php (100%) rename src/{Franzose/ClosureTable => }/Models/Entity.php (100%) diff --git a/composer.json b/composer.json index b34c6bd..8b0b5a3 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,8 @@ "orchestra/testbench": "3.4.12" }, "autoload": { - "psr-0": { - "Franzose\\ClosureTable": "src/" + "psr-4": { + "Franzose\\ClosureTable\\": "src/" } }, "autoload-dev": { diff --git a/src/Franzose/ClosureTable/ClosureTableServiceProvider.php b/src/ClosureTableServiceProvider.php similarity index 100% rename from src/Franzose/ClosureTable/ClosureTableServiceProvider.php rename to src/ClosureTableServiceProvider.php diff --git a/src/Franzose/ClosureTable/Console/MakeCommand.php b/src/Console/MakeCommand.php similarity index 100% rename from src/Franzose/ClosureTable/Console/MakeCommand.php rename to src/Console/MakeCommand.php diff --git a/src/Franzose/ClosureTable/Contracts/ClosureTableInterface.php b/src/Contracts/ClosureTableInterface.php similarity index 100% rename from src/Franzose/ClosureTable/Contracts/ClosureTableInterface.php rename to src/Contracts/ClosureTableInterface.php diff --git a/src/Franzose/ClosureTable/Contracts/EntityInterface.php b/src/Contracts/EntityInterface.php similarity index 100% rename from src/Franzose/ClosureTable/Contracts/EntityInterface.php rename to src/Contracts/EntityInterface.php diff --git a/src/Franzose/ClosureTable/Extensions/Collection.php b/src/Extensions/Collection.php similarity index 100% rename from src/Franzose/ClosureTable/Extensions/Collection.php rename to src/Extensions/Collection.php diff --git a/src/Franzose/ClosureTable/Extensions/Str.php b/src/Extensions/Str.php similarity index 100% rename from src/Franzose/ClosureTable/Extensions/Str.php rename to src/Extensions/Str.php diff --git a/src/Franzose/ClosureTable/Generators/Generator.php b/src/Generators/Generator.php similarity index 100% rename from src/Franzose/ClosureTable/Generators/Generator.php rename to src/Generators/Generator.php diff --git a/src/Franzose/ClosureTable/Generators/Migration.php b/src/Generators/Migration.php similarity index 100% rename from src/Franzose/ClosureTable/Generators/Migration.php rename to src/Generators/Migration.php diff --git a/src/Franzose/ClosureTable/Generators/Model.php b/src/Generators/Model.php similarity index 100% rename from src/Franzose/ClosureTable/Generators/Model.php rename to src/Generators/Model.php diff --git a/src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable.php b/src/Generators/stubs/migrations/closuretable.php similarity index 100% rename from src/Franzose/ClosureTable/Generators/stubs/migrations/closuretable.php rename to src/Generators/stubs/migrations/closuretable.php diff --git a/src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php b/src/Generators/stubs/migrations/entity.php similarity index 100% rename from src/Franzose/ClosureTable/Generators/stubs/migrations/entity.php rename to src/Generators/stubs/migrations/entity.php diff --git a/src/Franzose/ClosureTable/Generators/stubs/models/closuretable.php b/src/Generators/stubs/models/closuretable.php similarity index 100% rename from src/Franzose/ClosureTable/Generators/stubs/models/closuretable.php rename to src/Generators/stubs/models/closuretable.php diff --git a/src/Franzose/ClosureTable/Generators/stubs/models/entity.php b/src/Generators/stubs/models/entity.php similarity index 100% rename from src/Franzose/ClosureTable/Generators/stubs/models/entity.php rename to src/Generators/stubs/models/entity.php diff --git a/src/Franzose/ClosureTable/Models/ClosureTable.php b/src/Models/ClosureTable.php similarity index 100% rename from src/Franzose/ClosureTable/Models/ClosureTable.php rename to src/Models/ClosureTable.php diff --git a/src/Franzose/ClosureTable/Models/Entity.php b/src/Models/Entity.php similarity index 100% rename from src/Franzose/ClosureTable/Models/Entity.php rename to src/Models/Entity.php From ef8eccc9b5c9de41022d080ae162a44b0430a086 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 16:28:51 +0700 Subject: [PATCH 057/113] renamed test class --- tests/Generators/{ModelTest.php => ModelTests.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/Generators/{ModelTest.php => ModelTests.php} (96%) diff --git a/tests/Generators/ModelTest.php b/tests/Generators/ModelTests.php similarity index 96% rename from tests/Generators/ModelTest.php rename to tests/Generators/ModelTests.php index f8024ca..fbbcfac 100644 --- a/tests/Generators/ModelTest.php +++ b/tests/Generators/ModelTests.php @@ -6,7 +6,7 @@ use Illuminate\Filesystem\Filesystem; use PHPUnit\Framework\TestCase; -class ModelTest extends TestCase +class ModelTests extends TestCase { public function testGeneration() { From 09f103946b41e74a0c302f594abef4edfc917f44 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 16:37:31 +0700 Subject: [PATCH 058/113] closing database connection at the end of each test --- tests/BaseTestCase.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index f13d693..59adf3a 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -1,6 +1,7 @@ Date: Thu, 9 Apr 2020 16:37:50 +0700 Subject: [PATCH 059/113] updated phpunit.xml --- phpunit.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 0fd022f..54a32f5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,7 +12,9 @@ > - ./tests/ + ./tests/Extensions + ./tests/Generators + ./tests/Models From 0be960baa423e04015f3ac7c4770af06f4c24ede Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 18:20:15 +0700 Subject: [PATCH 060/113] added an example of the testing environment file --- .env.testing.example | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .env.testing.example diff --git a/.env.testing.example b/.env.testing.example new file mode 100644 index 0000000..df91510 --- /dev/null +++ b/.env.testing.example @@ -0,0 +1,7 @@ +DB_DRIVER=mysql +DB_HOST=127.0.0.1 +DB_PORT=5506 +DB_USERNAME=user +DB_PASSWORD=userpass +DB_NAME=closuretabletest +DB_COLLATION=utf8_unicode_ci \ No newline at end of file From b0350c3931327c47ae8f406f44d058a16f4bfd88 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 18:51:48 +0700 Subject: [PATCH 061/113] removed hardcoded IDs so that PostgreSQL's sequences were generated properly --- tests/EntitiesSeeder.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/EntitiesSeeder.php b/tests/EntitiesSeeder.php index 00b6a8d..9c1ca51 100644 --- a/tests/EntitiesSeeder.php +++ b/tests/EntitiesSeeder.php @@ -8,7 +8,7 @@ class EntitiesSeeder extends Seeder { public function run() { - $entitiesSql = 'insert into entities (id, parent_id, title, excerpt, body, position) values(?, ?, ?, ?, ?, ?)'; + $entitiesSql = 'insert into entities (parent_id, title, excerpt, body, position) values(?, ?, ?, ?, ?)'; $closuresSql = 'insert into entities_closure (ancestor, descendant, depth) values(?, ?, ?)'; // 1 @@ -25,13 +25,13 @@ public function run() // 9 > 15 foreach (range(0, 8) as $idx) { - DB::insert($entitiesSql, [$idx + 1, null, 'The title', 'The excerpt', 'The body', $idx]); + DB::insert($entitiesSql, [null, 'The title', 'The excerpt', 'The body', $idx]); DB::insert($closuresSql, [$idx + 1, $idx + 1, 0]); } - DB::insert($entitiesSql, [10, 9, 'The title', 'The excerpt', 'The body', 0]); - DB::insert($entitiesSql, [11, 10, 'The title', 'The excerpt', 'The body', 0]); - DB::insert($entitiesSql, [12, 11, 'The title', 'The excerpt', 'The body', 0]); + DB::insert($entitiesSql, [9, 'The title', 'The excerpt', 'The body', 0]); + DB::insert($entitiesSql, [10, 'The title', 'The excerpt', 'The body', 0]); + DB::insert($entitiesSql, [11, 'The title', 'The excerpt', 'The body', 0]); DB::insert($closuresSql, [10, 10, 0]); DB::insert($closuresSql, [11, 11, 0]); DB::insert($closuresSql, [12, 12, 0]); @@ -44,15 +44,15 @@ public function run() DB::insert($closuresSql, [10, 12, 2]); DB::insert($closuresSql, [9, 12, 3]); - DB::insert($entitiesSql, [13, 9, 'The title', 'The excerpt', 'The body', 1]); + DB::insert($entitiesSql, [9, 'The title', 'The excerpt', 'The body', 1]); DB::insert($closuresSql, [13, 13, 0]); DB::insert($closuresSql, [9, 13, 1]); - DB::insert($entitiesSql, [14, 9, 'The title', 'The excerpt', 'The body', 2]); + DB::insert($entitiesSql, [9, 'The title', 'The excerpt', 'The body', 2]); DB::insert($closuresSql, [14, 14, 0]); DB::insert($closuresSql, [9, 14, 1]); - DB::insert($entitiesSql, [15, 9, 'The title', 'The excerpt', 'The body', 3]); + DB::insert($entitiesSql, [9, 'The title', 'The excerpt', 'The body', 3]); DB::insert($closuresSql, [15, 15, 0]); DB::insert($closuresSql, [9, 15, 1]); } From 21120922f18b8c6f8773f1eafa2aa3ae720fdeeb Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 19:21:31 +0700 Subject: [PATCH 062/113] made the test compatible with PostreSQL --- tests/Models/Entity/PositioningTests.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/Models/Entity/PositioningTests.php b/tests/Models/Entity/PositioningTests.php index 02a1ae9..d152505 100644 --- a/tests/Models/Entity/PositioningTests.php +++ b/tests/Models/Entity/PositioningTests.php @@ -12,10 +12,8 @@ final class PositioningTests extends BaseTestCase { public function testCreate() { - DB::statement('SET foreign_key_checks=0'); - ClosureTable::truncate(); - Entity::truncate(); - DB::statement('SET foreign_key_checks=1'); + DB::statement('DELETE FROM entities'); + DB::statement('DELETE FROM entities_closure'); $entity1 = new Entity; $entity1->save(); @@ -25,7 +23,7 @@ public function testCreate() $entity2->save(); static::assertEquals(1, $entity2->position); - static::assertModelAttribute('position', [1 => 0, 2 => 1]); + static::assertModelAttribute('position', [16 => 0, 17 => 1]); } public function testSavingLoadedEntityShouldNotTriggerReordering() From c9d4a463d8b725c0b1427533d76637955934ff6b Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 19:37:58 +0700 Subject: [PATCH 063/113] changed namespace (PSR-4) --- tests/Models/ClosureTableTestCase.php | 3 ++- tests/Models/Entity/AncestorTests.php | 2 +- tests/Models/Entity/ChildManipulationTests.php | 2 +- tests/Models/Entity/ChildQueryTests.php | 2 +- tests/Models/Entity/ConstructionTests.php | 2 +- tests/Models/Entity/CustomEntity.php | 2 +- tests/Models/Entity/DescendantTests.php | 2 +- tests/Models/Entity/MovementTests.php | 2 +- tests/Models/Entity/ParentRootTests.php | 2 +- tests/Models/Entity/PositioningTests.php | 3 +-- tests/Models/Entity/SiblingManipulationTests.php | 2 +- tests/Models/Entity/SiblingQueryTests.php | 2 +- tests/Models/Entity/TreeTests.php | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/Models/ClosureTableTestCase.php b/tests/Models/ClosureTableTestCase.php index e2bff9d..7fbe3fb 100644 --- a/tests/Models/ClosureTableTestCase.php +++ b/tests/Models/ClosureTableTestCase.php @@ -1,8 +1,9 @@ Date: Thu, 9 Apr 2020 19:50:03 +0700 Subject: [PATCH 064/113] separated SELECT from INSERT to avoid type casting in PostgreSQL --- src/Models/ClosureTable.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Models/ClosureTable.php b/src/Models/ClosureTable.php index 449fd3f..0f3ded3 100644 --- a/src/Models/ClosureTable.php +++ b/src/Models/ClosureTable.php @@ -44,27 +44,39 @@ class ClosureTable extends Eloquent implements ClosureTableInterface * @return void */ public function insertNode($ancestorId, $descendantId) + { + $rows = $this->selectRowsToInsert($ancestorId, $descendantId); + + if (count($rows) > 0) { + $this->insert($rows); + } + } + + private function selectRowsToInsert($ancestorId, $descendantId) { $table = $this->getPrefixedTable(); $ancestor = $this->getAncestorColumn(); $descendant = $this->getDescendantColumn(); $depth = $this->getDepthColumn(); - $query = " - INSERT INTO {$table} ({$ancestor}, {$descendant}, {$depth}) - SELECT tbl.{$ancestor}, ?, tbl.{$depth}+1 + $select = " + SELECT tbl.{$ancestor} AS ancestor, ? AS descendant, tbl.{$depth}+1 AS depth FROM {$table} AS tbl WHERE tbl.{$descendant} = ? UNION ALL - SELECT ?, ?, 0 + SELECT ? AS ancestor, ? AS descendant, 0 AS depth "; - $this->getConnection()->statement($query, [ + $rows = $this->getConnection()->select($select, [ $descendantId, $ancestorId, $descendantId, $descendantId ]); + + return array_map(static function ($row) { + return (array) $row; + }, $rows); } /** From ddb34f338b3a4775c675ec315cbd7539097831b1 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 19:50:33 +0700 Subject: [PATCH 065/113] made a better check for inserted rows --- tests/Models/Entity/MovementTests.php | 33 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tests/Models/Entity/MovementTests.php b/tests/Models/Entity/MovementTests.php index 228656c..87ae96f 100644 --- a/tests/Models/Entity/MovementTests.php +++ b/tests/Models/Entity/MovementTests.php @@ -49,10 +49,35 @@ public function testValidNumberOfRowsInsertedByInsertNode() $descendant = Entity::create(['title' => 'abcde']); $descendant->moveTo(0, $ancestor); - $ancestorRows = ClosureTable::whereDescendant($ancestor->getKey())->count(); - $descendantRows = ClosureTable::whereDescendant($descendant->getKey())->count(); - static::assertEquals(1, $ancestorRows); - static::assertEquals(2, $descendantRows); + $ancestorId = $ancestor->getKey(); + $descendantId = $descendant->getKey(); + $columns = ['ancestor', 'descendant', 'depth']; + $ancestorRows = ClosureTable::where('descendant', '=', $ancestorId)->get($columns); + $descendantRows = ClosureTable::where('descendant', '=', $descendantId)->get($columns); + + static::assertEquals( + [ + 'ancestor' => $ancestorId, + 'descendant' => $ancestorId, + 'depth' => 0 + ], + $ancestorRows->get(0)->toArray() + ); + static::assertEquals( + [ + [ + 'ancestor' => $descendantId, + 'descendant' => $descendantId, + 'depth' => 0 + ], + [ + 'ancestor' => $ancestorId, + 'descendant' => $descendantId, + 'depth' => 1 + ], + ], + $descendantRows->toArray() + ); } public function testMoveNodeToAnotherAncestor() From c396e6496cd2af6d7983fce011f371654908f81d Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 21:23:20 +0700 Subject: [PATCH 066/113] fixed class names --- src/Generators/Model.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Generators/Model.php b/src/Generators/Model.php index 3a90489..8f46e10 100644 --- a/src/Generators/Model.php +++ b/src/Generators/Model.php @@ -33,9 +33,9 @@ public function create(array $options) $this->filesystem->put($path, $this->parseStub($stub, [ 'namespace' => $nsplaceholder, - 'entity_class' => $options['entity'], + 'entity_class' => ucfirst($options['entity']), 'entity_table' => $options['entity-table'], - 'closure_class' => $options['namespace'] . '\\' . $options['closure'], + 'closure_class' => $options['namespace'] . '\\' . ucfirst($options['closure']), ])); // Second, we make closure classes @@ -44,7 +44,7 @@ public function create(array $options) $this->filesystem->put($path, $this->parseStub($stub, [ 'namespace' => $nsplaceholder, - 'closure_class' => $options['closure'], + 'closure_class' => ucfirst($options['closure']), 'closure_table' => $options['closure-table'] ])); From 550cf530de072f35de222f6fab82e7ceb2231128 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 21:41:45 +0700 Subject: [PATCH 067/113] made properties private --- src/Console/MakeCommand.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Console/MakeCommand.php b/src/Console/MakeCommand.php index 334acf9..72e71fc 100644 --- a/src/Console/MakeCommand.php +++ b/src/Console/MakeCommand.php @@ -37,21 +37,22 @@ class MakeCommand extends Command * * @var \Franzose\ClosureTable\Generators\Migration */ - protected $migrator; + private $migrator; /** * Models generator instance. * * @var \Franzose\ClosureTable\Generators\Model */ - protected $modeler; + private $modeler; /** * User input arguments. * * @var array */ - protected $options; + private $options; + /** * @var Composer */ From 0d8676c7e4be11e7c92b529a2ef9720ba11d21f5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 21:42:02 +0700 Subject: [PATCH 068/113] small refactoring --- src/Console/MakeCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/MakeCommand.php b/src/Console/MakeCommand.php index 72e71fc..e735276 100644 --- a/src/Console/MakeCommand.php +++ b/src/Console/MakeCommand.php @@ -158,8 +158,8 @@ protected function prepareOptions() $this->options[$options[2][0]] = $input[2] ?: ExtStr::tableize($input[1]); $this->options[$options[3][0]] = $input[3] ?: $this->options[$options[1][0]] . 'Closure'; $this->options[$options[4][0]] = $input[4] ?: ExtStr::tableize($input[1] . 'Closure'); - $this->options[$options[5][0]] = $input[5] ? $input[5] : './app'; - $this->options[$options[6][0]] = $input[6] ? $input[6] : './database/migrations'; + $this->options[$options[5][0]] = $input[5] ?: './app'; + $this->options[$options[6][0]] = $input[6] ?: './database/migrations'; $this->options[$options[7][0]] = $input[7] ?: false; } } From 9d4a2c57e73d5396a35549097752d34f5fb7b9f5 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 21:42:52 +0700 Subject: [PATCH 069/113] laravel 5.5 compatibility --- src/Console/MakeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/MakeCommand.php b/src/Console/MakeCommand.php index e735276..43fbbde 100644 --- a/src/Console/MakeCommand.php +++ b/src/Console/MakeCommand.php @@ -79,7 +79,7 @@ public function __construct(Migration $migrator, Model $modeler, Composer $compo * * @return void */ - public function fire() + public function handle() { $this->prepareOptions(); $this->writeMigrations(); From 7880f8b1cb43f886ce0368af7b8a64c3922a74d6 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 21:48:29 +0700 Subject: [PATCH 070/113] package auto-discovery for laravel 5.5+ --- composer.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8b0b5a3..7992ecb 100644 --- a/composer.json +++ b/composer.json @@ -31,5 +31,12 @@ "config": { "preferred-install": "dist" }, - "minimum-stability": "stable" + "minimum-stability": "stable", + "extra": { + "laravel": { + "providers": [ + "Franzose\\ClosureTable\\ClosureTableServiceProvider" + ] + } + } } From 7fc7fa02d0f86e551d34188cf8c71439b3e365ea Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 22:05:03 +0700 Subject: [PATCH 071/113] guaranteed specific order of the migrations --- src/Generators/Migration.php | 32 ++++++++++++----------------- tests/Generators/MigrationTests.php | 2 +- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/Generators/Migration.php b/src/Generators/Migration.php index c0247c5..98666ac 100644 --- a/src/Generators/Migration.php +++ b/src/Generators/Migration.php @@ -2,6 +2,7 @@ namespace Franzose\ClosureTable\Generators; use Carbon\Carbon; +use DateTime; use Franzose\ClosureTable\Extensions\Str as ExtStr; /** @@ -11,11 +12,6 @@ */ class Migration extends Generator { - /** - * @var array - */ - private $usedTimestamps = []; - /** * Creates migration files. * @@ -30,7 +26,9 @@ public function create(array $options) $closureClass = $this->getClassName($options['closure-table']); $innoDb = $options['use-innodb'] ? '$table->engine = \'InnoDB\';' : ''; - $paths[] = $path = $this->getPath($options['entity-table'], $options['migrations-path']); + $dateTime = Carbon::now(); + + $paths[] = $path = $this->getPath($dateTime, $options['entity-table'], $options['migrations-path']); $stub = $this->getStub('entity', 'migrations'); $this->filesystem->put($path, $this->parseStub($stub, [ @@ -39,7 +37,9 @@ public function create(array $options) 'innodb' => $innoDb ])); - $paths[] = $path = $this->getPath($options['closure-table'], $options['migrations-path']); + $dateTime->addSecond(); + + $paths[] = $path = $this->getPath($dateTime, $options['closure-table'], $options['migrations-path']); $stub = $this->getStub('closuretable', 'migrations'); $this->filesystem->put($path, $this->parseStub($stub, [ @@ -77,20 +77,14 @@ protected function getClassName($name) /** * Constructs path to migration file in Laravel style. * - * @param $name - * @param $path + * @param DateTime $dateTime + * @param string $name + * @param string $path + * * @return string */ - protected function getPath($name, $path) + protected function getPath(DateTime $dateTime, $name, $path) { - $timestamp = Carbon::now(); - - if (in_array($timestamp, $this->usedTimestamps, true)) { - $timestamp->addSecond(); - } - - $this->usedTimestamps[] = $timestamp; - - return $path . '/' . $timestamp->format('Y_m_d_His') . '_' . $this->getName($name) . '.php'; + return $path . '/' . $dateTime->format('Y_m_d_His') . '_' . $this->getName($name) . '.php'; } } diff --git a/tests/Generators/MigrationTests.php b/tests/Generators/MigrationTests.php index 82f3929..a2b365e 100644 --- a/tests/Generators/MigrationTests.php +++ b/tests/Generators/MigrationTests.php @@ -28,7 +28,7 @@ public function testGenerator($useInnoDb) Carbon::setTestNow(); $entityMigrationPath = __DIR__ . '/2020_04_03_000000_create_entities_table.php'; - $closureMigrationPath = __DIR__ . '/2020_04_03_000000_create_entity_trees_table.php'; + $closureMigrationPath = __DIR__ . '/2020_04_03_000001_create_entity_trees_table.php'; static::assertFileExists($entityMigrationPath); static::assertFileExists($closureMigrationPath); From 93f9407ad248b84a743e9a7d7c12afb080dfb5df Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Thu, 9 Apr 2020 22:32:31 +0700 Subject: [PATCH 072/113] upgraded development dependencies --- composer.json | 4 +- composer.lock | 1031 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 782 insertions(+), 253 deletions(-) diff --git a/composer.json b/composer.json index 7992ecb..e683856 100644 --- a/composer.json +++ b/composer.json @@ -15,8 +15,8 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^5", - "orchestra/testbench": "3.4.12" + "phpunit/phpunit": "^6", + "orchestra/testbench": "^3.5" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 5e3d200..36e9eb9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b1b66425a209df97c7f91a3b6c4907d6", + "content-hash": "c19f4e5954df4ae09a9cd29f1b90bc95", "packages": [], "packages-dev": [ { @@ -130,6 +130,126 @@ ], "time": "2019-10-21T16:45:58+00:00" }, + { + "name": "doctrine/lexer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "time": "2019-10-30T14:39:59+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.17", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "ade6887fd9bd74177769645ab5c474824f8a418a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ade6887fd9bd74177769645ab5c474824f8a418a", + "reference": "ade6887fd9bd74177769645ab5c474824f8a418a", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.10" + }, + "require-dev": { + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "EmailValidator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "time": "2020-02-13T22:36:52+00:00" + }, { "name": "erusev/parsedown", "version": "1.7.4", @@ -228,16 +348,16 @@ }, { "name": "kylekatarnls/update-helper", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/kylekatarnls/update-helper.git", - "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e" + "reference": "429be50660ed8a196e0798e5939760f168ec8ce9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kylekatarnls/update-helper/zipball/5786fa188e0361b9adf9e8199d7280d1b2db165e", - "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e", + "url": "https://api.github.com/repos/kylekatarnls/update-helper/zipball/429be50660ed8a196e0798e5939760f168ec8ce9", + "reference": "429be50660ed8a196e0798e5939760f168ec8ce9", "shasum": "" }, "require": { @@ -269,43 +389,44 @@ } ], "description": "Update helper", - "time": "2019-07-29T11:03:54+00:00" + "time": "2020-04-07T20:44:10+00:00" }, { "name": "laravel/framework", - "version": "v5.4.36", + "version": "v5.5.48", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "1062a22232071c3e8636487c86ec1ae75681bbf9" + "reference": "e3e8d585dcfab5abe6261b060f4df0d48f9924bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/1062a22232071c3e8636487c86ec1ae75681bbf9", - "reference": "1062a22232071c3e8636487c86ec1ae75681bbf9", + "url": "https://api.github.com/repos/laravel/framework/zipball/e3e8d585dcfab5abe6261b060f4df0d48f9924bf", + "reference": "e3e8d585dcfab5abe6261b060f4df0d48f9924bf", "shasum": "" }, "require": { "doctrine/inflector": "~1.1", - "erusev/parsedown": "~1.6", + "erusev/parsedown": "~1.7", "ext-mbstring": "*", "ext-openssl": "*", - "league/flysystem": "~1.0", - "monolog/monolog": "~1.11", + "league/flysystem": "^1.0.8", + "monolog/monolog": "~1.12", "mtdowling/cron-expression": "~1.0", - "nesbot/carbon": "~1.20", - "paragonie/random_compat": "~1.4|~2.0", - "php": ">=5.6.4", + "nesbot/carbon": "^1.26.0", + "php": ">=7.0", + "psr/container": "~1.0", + "psr/simple-cache": "^1.0", "ramsey/uuid": "~3.0", - "swiftmailer/swiftmailer": "~5.4", - "symfony/console": "~3.2", - "symfony/debug": "~3.2", - "symfony/finder": "~3.2", - "symfony/http-foundation": "~3.2", - "symfony/http-kernel": "~3.2", - "symfony/process": "~3.2", - "symfony/routing": "~3.2", - "symfony/var-dumper": "~3.2", + "swiftmailer/swiftmailer": "~6.0", + "symfony/console": "~3.3", + "symfony/debug": "~3.3", + "symfony/finder": "~3.3", + "symfony/http-foundation": "~3.3", + "symfony/http-kernel": "~3.3", + "symfony/process": "~3.3", + "symfony/routing": "~3.3", + "symfony/var-dumper": "~3.3", "tijsverkoyen/css-to-inline-styles": "~2.2", "vlucas/phpdotenv": "~2.2" }, @@ -322,7 +443,6 @@ "illuminate/database": "self.version", "illuminate/encryption": "self.version", "illuminate/events": "self.version", - "illuminate/exception": "self.version", "illuminate/filesystem": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", @@ -339,38 +459,43 @@ "illuminate/translation": "self.version", "illuminate/validation": "self.version", "illuminate/view": "self.version", - "tightenco/collect": "self.version" + "tightenco/collect": "<5.5.33" }, "require-dev": { "aws/aws-sdk-php": "~3.0", "doctrine/dbal": "~2.5", - "mockery/mockery": "~0.9.4", + "filp/whoops": "^2.1.4", + "mockery/mockery": "~1.0", + "orchestra/testbench-core": "3.5.*", "pda/pheanstalk": "~3.0", - "phpunit/phpunit": "~5.7", - "predis/predis": "~1.0", - "symfony/css-selector": "~3.2", - "symfony/dom-crawler": "~3.2" + "phpunit/phpunit": "~6.0", + "predis/predis": "^1.1.1", + "symfony/css-selector": "~3.3", + "symfony/dom-crawler": "~3.3" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).", + "ext-pcntl": "Required to use all features of the queue worker.", + "ext-posix": "Required to use all features of the queue worker.", "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).", "laravel/tinker": "Required to use the tinker console command (~1.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).", "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", "nexmo/client": "Required to use the Nexmo transport (~1.0).", "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", - "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.2).", - "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.2).", - "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.3).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.3).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.5-dev" } }, "autoload": { @@ -398,7 +523,7 @@ "framework", "laravel" ], - "time": "2017-08-30T09:26:16+00:00" + "time": "2019-08-20T15:46:40+00:00" }, { "name": "league/flysystem", @@ -718,39 +843,35 @@ }, { "name": "orchestra/testbench", - "version": "v3.4.12", + "version": "v3.5.5", "source": { "type": "git", "url": "https://github.com/orchestral/testbench.git", - "reference": "1a040537b09fa3e5a6c6a703a1180cf6b29e1f0e" + "reference": "fd032489df469d611a264083e62db96677c9061e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench/zipball/1a040537b09fa3e5a6c6a703a1180cf6b29e1f0e", - "reference": "1a040537b09fa3e5a6c6a703a1180cf6b29e1f0e", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/fd032489df469d611a264083e62db96677c9061e", + "reference": "fd032489df469d611a264083e62db96677c9061e", "shasum": "" }, "require": { - "fzaninotto/faker": "~1.4", - "laravel/framework": "~5.4.36", - "orchestra/testbench-core": "~3.4.6", - "php": ">=5.6.0" + "laravel/framework": "~5.5.34", + "orchestra/testbench-core": "~3.5.9", + "php": ">=7.0", + "phpunit/phpunit": "~6.0" }, "require-dev": { - "mockery/mockery": "^0.9.4 || ~1.0", - "orchestra/database": "~3.4.0", - "phpunit/phpunit": "~5.7" + "mockery/mockery": "~1.0", + "orchestra/database": "~3.5.0" }, "suggest": { - "mockery/mockery": "Allow to use Mockery for testing (^0.9.4).", - "orchestra/database": "Allow to use --realpath migration for testing (~3.4).", - "orchestra/testbench-browser-kit": "Allow to use legacy BrowserKit for testing (~3.4).", - "phpunit/phpunit": "Allow to use PHPUnit for testing (~5.7)." + "orchestra/testbench-browser-kit": "Allow to use legacy BrowserKit for testing (~3.5)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "3.5-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -774,38 +895,38 @@ "orchestral", "testing" ], - "time": "2018-02-20T05:27:50+00:00" + "time": "2018-02-20T05:30:39+00:00" }, { "name": "orchestra/testbench-core", - "version": "v3.4.7", + "version": "v3.5.11", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "6a7ed6b65942c9b1bffc0bf8f0eab442ccb30e5b" + "reference": "a9c3625a5234ea478546fc0711216d6707ca2509" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/6a7ed6b65942c9b1bffc0bf8f0eab442ccb30e5b", - "reference": "6a7ed6b65942c9b1bffc0bf8f0eab442ccb30e5b", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/a9c3625a5234ea478546fc0711216d6707ca2509", + "reference": "a9c3625a5234ea478546fc0711216d6707ca2509", "shasum": "" }, "require": { "fzaninotto/faker": "~1.4", - "php": ">=5.6.0" + "php": ">=7.0" }, "require-dev": { - "laravel/framework": "~5.4.17", - "mockery/mockery": "^0.9.4", - "orchestra/database": "~3.4.0", - "phpunit/phpunit": "~5.7 || ~6.0" + "laravel/framework": "~5.5.0", + "mockery/mockery": "~1.0", + "orchestra/database": "~3.5.0", + "phpunit/phpunit": "~6.0" }, "suggest": { - "laravel/framework": "Required for testing (~5.4.0).", - "mockery/mockery": "Allow to use Mockery for testing (^0.9.4).", - "orchestra/database": "Allow to use --realpath migration for testing (~3.4).", - "orchestra/testbench-browser-kit": "Allow to use legacy BrowserKit for testing (~3.4).", - "orchestra/testbench-dusk": "Allow to use Laravel Dusk for testing (~3.4).", + "laravel/framework": "Required for testing (~5.5.0).", + "mockery/mockery": "Allow to use Mockery for testing (~1.0).", + "orchestra/database": "Allow to use --realpath migration for testing (~3.5).", + "orchestra/testbench-browser-kit": "Allow to use legacy BrowserKit for testing (~3.5).", + "orchestra/testbench-dusk": "Allow to use Laravel Dusk for testing (~3.5).", "phpunit/phpunit": "Allow to use PHPUnit for testing (~6.0)." }, "type": "library", @@ -840,37 +961,33 @@ "orchestral", "testing" ], - "time": "2019-12-10T00:56:53+00:00" + "time": "2019-12-10T01:08:48+00:00" }, { "name": "paragonie/random_compat", - "version": "v2.0.18", + "version": "v9.99.99", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": "^7" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -889,7 +1006,109 @@ "pseudorandom", "random" ], - "time": "2019-01-03T20:59:08+00:00" + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -1107,40 +1326,40 @@ }, { "name": "phpunit/php-code-coverage", - "version": "4.0.8", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.2 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "^1.0 || ^2.0" + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" }, "require-dev": { - "ext-xdebug": "^2.1.4", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-xdebug": "^2.5.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "5.3.x-dev" } }, "autoload": { @@ -1155,7 +1374,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1166,7 +1385,7 @@ "testing", "xunit" ], - "time": "2017-04-02T07:44:40+00:00" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1356,16 +1575,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.7.27", + "version": "6.5.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", - "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", "shasum": "" }, "require": { @@ -1374,33 +1593,35 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^4.0.4", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "^1.2.4", - "sebastian/diff": "^1.4.3", - "sebastian/environment": "^1.3.4 || ^2.0", - "sebastian/exporter": "~2.0", - "sebastian/global-state": "^1.1", - "sebastian/object-enumerator": "~2.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "^1.0.6|^2.0.1", - "symfony/yaml": "~2.1|~3.0|~4.0" + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" }, "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" + "phpunit/php-invoker": "^1.1" }, "bin": [ "phpunit" @@ -1408,7 +1629,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7.x-dev" + "dev-master": "6.5.x-dev" } }, "autoload": { @@ -1434,33 +1655,33 @@ "testing", "xunit" ], - "time": "2018-02-01T05:50:59+00:00" + "time": "2019-02-01T05:22:47+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.4.4", + "version": "5.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", - "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2 || ^2.0" + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" }, "conflict": { - "phpunit/phpunit": "<5.4.0" + "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^5.4" + "phpunit/phpunit": "^6.5.11" }, "suggest": { "ext-soap": "*" @@ -1468,7 +1689,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "5.0.x-dev" } }, "autoload": { @@ -1483,7 +1704,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1494,7 +1715,56 @@ "xunit" ], "abandoned": true, - "time": "2017-06-30T09:13:00+00:00" + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" }, { "name": "psr/log", @@ -1543,6 +1813,54 @@ ], "time": "2020-03-23T09:12:05+00:00" }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, { "name": "ramsey/uuid", "version": "3.9.3", @@ -1677,30 +1995,30 @@ }, { "name": "sebastian/comparator", - "version": "1.2.4", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -1731,38 +2049,38 @@ } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", "equality" ], - "time": "2017-01-29T09:50:25+00:00" + "time": "2018-02-01T13:46:46+00:00" }, { "name": "sebastian/diff", - "version": "1.4.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1789,32 +2107,32 @@ "keywords": [ "diff" ], - "time": "2017-05-22T07:24:03+00:00" + "time": "2017-08-03T08:09:46+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1839,34 +2157,34 @@ "environment", "hhvm" ], - "time": "2016-11-26T07:53:53+00:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", - "version": "2.0.0", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/recursion-context": "^3.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1879,6 +2197,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -1887,17 +2209,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -1906,27 +2224,27 @@ "export", "exporter" ], - "time": "2016-11-19T08:54:04+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-uopz": "*" @@ -1934,7 +2252,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1957,33 +2275,34 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", - "version": "2.0.1", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", "shasum": "" }, "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -2003,32 +2322,77 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-02-18T15:18:39+00:00" + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" }, { "name": "sebastian/recursion-context", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -2056,7 +2420,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-11-19T07:33:16+00:00" + "time": "2017-03-03T06:23:57+00:00" }, { "name": "sebastian/resource-operations", @@ -2145,29 +2509,37 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.12", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "181b89f18a90f8925ef805f950d47a7190e9b950" + "reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950", - "reference": "181b89f18a90f8925ef805f950d47a7190e9b950", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/149cfdf118b169f7840bbe3ef0d4bc795d1780c9", + "reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9", "shasum": "" }, "require": { - "php": ">=5.3.3" + "egulias/email-validator": "~2.0", + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.2" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8" + }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses", + "true/punycode": "Needed to support internationalized email addresses, if ext-intl is not installed" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -2195,7 +2567,7 @@ "mail", "mailer" ], - "time": "2018-07-31T09:26:32+00:00" + "time": "2019-11-12T09:31:26+00:00" }, { "name": "symfony/console", @@ -2757,6 +3129,127 @@ ], "time": "2020-02-27T09:26:54+00:00" }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", + "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "time": "2020-03-09T19:04:49+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2020-03-09T19:04:49+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.15.0", @@ -2931,6 +3424,61 @@ ], "time": "2020-02-27T09:26:54+00:00" }, + { + "name": "symfony/polyfill-php72", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "37b0976c78b94856543260ce09b460a7bc852747" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", + "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-02-27T09:26:54+00:00" + }, { "name": "symfony/polyfill-util", "version": "v1.15.0", @@ -3311,63 +3859,44 @@ "time": "2020-03-17T22:27:36+00:00" }, { - "name": "symfony/yaml", - "version": "v4.4.7", + "name": "theseer/tokenizer", + "version": "1.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "ef166890d821518106da3560086bfcbeb4fadfec" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec", - "reference": "ef166890d821518106da3560086bfcbeb4fadfec", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - }, "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2020-03-30T11:41:10+00:00" + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", From aad2b6fb6934ab15c4fcac15bda808648c7c9ef9 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 15 Apr 2020 22:31:28 +0700 Subject: [PATCH 073/113] removed explicit import of Throwable --- src/Models/Entity.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Models/Entity.php b/src/Models/Entity.php index 39c8ae0..5f804ee 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -8,7 +8,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Arr; use InvalidArgumentException; -use Throwable; /** * Basic entity class. @@ -910,7 +909,7 @@ private function getLatestChildPosition() * * @return Entity * @throws InvalidArgumentException - * @throws Throwable + * @throws \Throwable */ public function addChildren(array $children, $from = null) { @@ -935,7 +934,7 @@ public function addChildren(array $children, $from = null) * @param bool $forceDelete * * @return $this - * @throws Throwable + * @throws \Throwable */ public function removeChild($position = null, $forceDelete = false) { @@ -973,7 +972,7 @@ public function removeChild($position = null, $forceDelete = false) * * @return $this * @throws InvalidArgumentException - * @throws Throwable + * @throws \Throwable */ public function removeChildren($from, $to = null, $forceDelete = false) { From 8fb0517a997c6f17914f43aa2dd71f87e407cc7e Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 16:14:30 +0200 Subject: [PATCH 074/113] Test using Travis CI Link mysql service Making sense of the matrix configuration Update --- .travis.yml | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 74d2e8b..b4a1bd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,48 @@ language: php +services: + - mysql + php: - - 5.4 - - 5.5 - - 5.6 - - hhvm + - 7.0 + - 7.1 + - 7.2 + - 7.3 + - 7.4 + +env: + - LARAVEL_VERSION=5.6.* + - LARAVEL_VERSION=5.7.* + - LARAVEL_VERSION=5.8.* + - LARAVEL_VERSION=5.8.* + - LARAVEL_VERSION=6.* +matrix: + exclude: + - php: 7.2 + env: LARAVEL_VERSION=5.6.* + - php: 7.3 + env: LARAVEL_VERSION=5.6.* + - php: 7.3 + env: LARAVEL_VERSION=5.7.* + - php: 7.3 + env: LARAVEL_VERSION=5.8.* + - php: 7.4 + env: LARAVEL_VERSION=5.6.* + - php: 7.4 + env: LARAVEL_VERSION=5.7.* + - php: 7.4 + env: LARAVEL_VERSION=5.8.* + - php: 7.0 + env: LARAVEL_VERSION=6.* + - php: 7.1 + env: LARAVEL_VERSION=6.* sudo: false +before_install: + # ensure that the specific Laravel version is required + - composer require "illuminate/support:${LARAVEL_VERSION}" --no-update + install: travis_retry composer install --no-interaction --prefer-source before_script: @@ -19,3 +54,6 @@ branches: only: - master - feature/laravel-5 + # version tag, e.g. v1.0.0 + - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ + - /^\d+\.\d+?$/ From 1c326376008fbea2a21dcfad42a1a205e93e2fd6 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 16:22:10 +0200 Subject: [PATCH 075/113] Travis CI --- .travis.yml | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index b4a1bd9..c09d14f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ services: - mysql php: + - 5.6 - 7.0 - 7.1 - 7.2 @@ -14,29 +15,42 @@ env: - LARAVEL_VERSION=5.6.* - LARAVEL_VERSION=5.7.* - LARAVEL_VERSION=5.8.* - - LARAVEL_VERSION=5.8.* - LARAVEL_VERSION=6.* + matrix: exclude: - - php: 7.2 - env: LARAVEL_VERSION=5.6.* - php: 7.3 env: LARAVEL_VERSION=5.6.* - - php: 7.3 - env: LARAVEL_VERSION=5.7.* - - php: 7.3 - env: LARAVEL_VERSION=5.8.* - php: 7.4 env: LARAVEL_VERSION=5.6.* + - php: 7.3 + env: LARAVEL_VERSION=5.7.* - php: 7.4 env: LARAVEL_VERSION=5.7.* + - php: 5.6 + env: LARAVEL_VERSION=5.8.* + - php: 7.0 + env: LARAVEL_VERSION=5.8.* + - php: 7.1 + env: LARAVEL_VERSION=5.8.* + - php: 7.3 + env: LARAVEL_VERSION=5.8.* - php: 7.4 env: LARAVEL_VERSION=5.8.* + - php: 5.6 + env: LARAVEL_VERSION=5.7 + - php: 5.6 + env: LARAVEL_VERSION=5.8 + - php: 5.6 + env: LARAVEL_VERSION=6.* - php: 7.0 env: LARAVEL_VERSION=6.* - php: 7.1 env: LARAVEL_VERSION=6.* +jobs: + fast_finish: true + sudo: false before_install: From df099daacd6de9d77eca344ccd09249bf3857fee Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 16:25:29 +0200 Subject: [PATCH 076/113] branch --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c09d14f..1ce53e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,6 +68,8 @@ branches: only: - master - feature/laravel-5 + - 6.x + - travis-ci-for-6.x # version tag, e.g. v1.0.0 - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ - /^\d+\.\d+?$/ From 11b7c2ec3337c94b2e65266ceec159259a287dc8 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 16:40:25 +0200 Subject: [PATCH 077/113] Test --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1ce53e3..2ecd841 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ php: - 7.4 env: + - LARAVEL_VERSION=5.5.* - LARAVEL_VERSION=5.6.* - LARAVEL_VERSION=5.7.* - LARAVEL_VERSION=5.8.* @@ -19,6 +20,12 @@ env: matrix: exclude: + - php: 7.2 + env: LARAVEL_VERSION=5.5.* + - php: 7.3 + env: LARAVEL_VERSION=5.5.* + - php: 7.4 + env: LARAVEL_VERSION=5.5.* - php: 7.3 env: LARAVEL_VERSION=5.6.* - php: 7.4 @@ -56,6 +63,8 @@ sudo: false before_install: # ensure that the specific Laravel version is required - composer require "illuminate/support:${LARAVEL_VERSION}" --no-update + - cat composer.json + - echo ${LARAVEL_VERSION} install: travis_retry composer install --no-interaction --prefer-source From f6b5e8c4fac1fcd007b75738e532cb84b010b310 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:05:52 +0200 Subject: [PATCH 078/113] Dry run to see which version of Laravel and testbench are used --- .travis.yml | 10 +++++++++- composer.json | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ecd841..ea2b951 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ env: matrix: exclude: + - php: 5.6 + env: LARAVEL_VERSION=5.5.* - php: 7.2 env: LARAVEL_VERSION=5.5.* - php: 7.3 @@ -30,6 +32,12 @@ matrix: env: LARAVEL_VERSION=5.6.* - php: 7.4 env: LARAVEL_VERSION=5.6.* + - php: 5.6 + env: LARAVEL_VERSION=5.7.* + - php: 7.0 + env: LARAVEL_VERSION=5.7.* + - php: 7.1 + env: LARAVEL_VERSION=5.7.* - php: 7.3 env: LARAVEL_VERSION=5.7.* - php: 7.4 @@ -66,7 +74,7 @@ before_install: - cat composer.json - echo ${LARAVEL_VERSION} -install: travis_retry composer install --no-interaction --prefer-source +install: travis_retry composer install --no-interaction --prefer-dist --dry-run before_script: - mysql -e 'create database closuretabletest;' diff --git a/composer.json b/composer.json index e683856..577c389 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ }, "require-dev": { "phpunit/phpunit": "^6", - "orchestra/testbench": "^3.5" + "orchestra/testbench": "^3.5|^4.0|^5.0" }, "autoload": { "psr-4": { From 9dd2279ac4575b4253df5288be094966f7eb754b Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:17:33 +0200 Subject: [PATCH 079/113] Try to force correct version of Laravel --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea2b951..881a76e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,12 +22,16 @@ matrix: exclude: - php: 5.6 env: LARAVEL_VERSION=5.5.* + - php: 7.0 + env: LARAVEL_VERSION=5.5.* - php: 7.2 env: LARAVEL_VERSION=5.5.* - php: 7.3 env: LARAVEL_VERSION=5.5.* - php: 7.4 env: LARAVEL_VERSION=5.5.* + - php: 5.6 + env: LARAVEL_VERSION=5.6.* - php: 7.3 env: LARAVEL_VERSION=5.6.* - php: 7.4 @@ -70,7 +74,7 @@ sudo: false before_install: # ensure that the specific Laravel version is required - - composer require "illuminate/support:${LARAVEL_VERSION}" --no-update + - composer require "laravel/framework:${LARAVEL_VERSION}" --no-update - cat composer.json - echo ${LARAVEL_VERSION} @@ -84,7 +88,6 @@ script: vendor/bin/phpunit branches: only: - master - - feature/laravel-5 - 6.x - travis-ci-for-6.x # version tag, e.g. v1.0.0 From 25f30ca659c4b1b8f71486b4feab1579ff69ca9c Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:23:06 +0200 Subject: [PATCH 080/113] Allow other versions to be selected --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 577c389..8e21aaf 100644 --- a/composer.json +++ b/composer.json @@ -15,8 +15,8 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^6", - "orchestra/testbench": "^3.5|^4.0|^5.0" + "phpunit/phpunit": "^6.0|^7.0|^8.0", + "orchestra/testbench": "^3.5|^3.6|^3.7|^3.8|^4.0|^5.0" }, "autoload": { "psr-4": { From 398a73a846ab2041439a1066f740645e0c24de19 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:24:41 +0200 Subject: [PATCH 081/113] Try to test on single PHP version to get dependencies right --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 881a76e..18e06fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ matrix: env: LARAVEL_VERSION=5.5.* - php: 7.0 env: LARAVEL_VERSION=5.5.* - - php: 7.2 + - php: 7.1 env: LARAVEL_VERSION=5.5.* - php: 7.3 env: LARAVEL_VERSION=5.5.* @@ -32,6 +32,10 @@ matrix: env: LARAVEL_VERSION=5.5.* - php: 5.6 env: LARAVEL_VERSION=5.6.* + - php: 7.0 + env: LARAVEL_VERSION=5.6.* + - php: 7.1 + env: LARAVEL_VERSION=5.6.* - php: 7.3 env: LARAVEL_VERSION=5.6.* - php: 7.4 From dc63bc3ade766d2cebef9081a6357cd09158787d Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:33:50 +0200 Subject: [PATCH 082/113] Update instead of install --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 18e06fc..3564182 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,10 +79,8 @@ sudo: false before_install: # ensure that the specific Laravel version is required - composer require "laravel/framework:${LARAVEL_VERSION}" --no-update - - cat composer.json - - echo ${LARAVEL_VERSION} -install: travis_retry composer install --no-interaction --prefer-dist --dry-run +install: travis_retry composer update --no-interaction --prefer-dist before_script: - mysql -e 'create database closuretabletest;' From bfeab5328a8568a9a4e8c3e3c67676ee286bafc8 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:40:55 +0200 Subject: [PATCH 083/113] Reduce version contraints --- composer.json | 4 ++-- phpunit.xml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 8e21aaf..0301fbe 100644 --- a/composer.json +++ b/composer.json @@ -15,8 +15,8 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^6.0|^7.0|^8.0", - "orchestra/testbench": "^3.5|^3.6|^3.7|^3.8|^4.0|^5.0" + "phpunit/phpunit": "^6.0|^7.0", + "orchestra/testbench": "^3.5|^4.0|^5.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index 54a32f5..8a6f145 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="true" - syntaxCheck="true" > From 5754e3bef757747636439cca5fe3c946a58d9295 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:44:42 +0200 Subject: [PATCH 084/113] PHPUnit 8 is required to test on Laravel 6.x+ --- composer.json | 2 +- tests/BaseTestCase.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 0301fbe..7fb26e3 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^6.0|^7.0", + "phpunit/phpunit": "^6.0|^7.0|^8.0", "orchestra/testbench": "^3.5|^4.0|^5.0" }, "autoload": { diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 59adf3a..d4aa1a3 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -15,7 +15,7 @@ abstract class BaseTestCase extends TestCase { const DATABASE_CONNECTION = 'closuretable'; - public function setUp() + public function setUp() : void { parent::setUp(); @@ -32,7 +32,7 @@ public function setUp() ]); } - public function tearDown() + public function tearDown(): void { // this is to avoid "too many connection" errors DB::disconnect(static::DATABASE_CONNECTION); From 5d3b57071ab983ed29b38315847b3e87ffd65acb Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 17:54:04 +0200 Subject: [PATCH 085/113] Open to all supported versions --- .travis.yml | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3564182..6a518d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,64 +12,44 @@ php: - 7.4 env: + - LARAVEL_VERSION=5.4.* - LARAVEL_VERSION=5.5.* - LARAVEL_VERSION=5.6.* - LARAVEL_VERSION=5.7.* - LARAVEL_VERSION=5.8.* - LARAVEL_VERSION=6.* + - LARAVEL_VERSION=7.* matrix: exclude: - php: 5.6 env: LARAVEL_VERSION=5.5.* - - php: 7.0 - env: LARAVEL_VERSION=5.5.* - - php: 7.1 - env: LARAVEL_VERSION=5.5.* - - php: 7.3 - env: LARAVEL_VERSION=5.5.* - - php: 7.4 - env: LARAVEL_VERSION=5.5.* - php: 5.6 env: LARAVEL_VERSION=5.6.* - php: 7.0 env: LARAVEL_VERSION=5.6.* - - php: 7.1 - env: LARAVEL_VERSION=5.6.* - - php: 7.3 - env: LARAVEL_VERSION=5.6.* - - php: 7.4 - env: LARAVEL_VERSION=5.6.* - php: 5.6 env: LARAVEL_VERSION=5.7.* - php: 7.0 env: LARAVEL_VERSION=5.7.* - - php: 7.1 - env: LARAVEL_VERSION=5.7.* - - php: 7.3 - env: LARAVEL_VERSION=5.7.* - - php: 7.4 - env: LARAVEL_VERSION=5.7.* - php: 5.6 env: LARAVEL_VERSION=5.8.* - php: 7.0 env: LARAVEL_VERSION=5.8.* - php: 7.1 env: LARAVEL_VERSION=5.8.* - - php: 7.3 - env: LARAVEL_VERSION=5.8.* - - php: 7.4 - env: LARAVEL_VERSION=5.8.* - - php: 5.6 - env: LARAVEL_VERSION=5.7 - - php: 5.6 - env: LARAVEL_VERSION=5.8 - php: 5.6 env: LARAVEL_VERSION=6.* - php: 7.0 env: LARAVEL_VERSION=6.* - php: 7.1 env: LARAVEL_VERSION=6.* + - php: 5.6 + env: LARAVEL_VERSION=7.* + - php: 7.0 + env: LARAVEL_VERSION=7.* + - php: 7.1 + env: LARAVEL_VERSION=7.* jobs: fast_finish: true From 7e506c09ed15fb7cf0e08876ca5c4756972358ac Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 18:00:40 +0200 Subject: [PATCH 086/113] Allow test bench v3.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7fb26e3..691d3e1 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ }, "require-dev": { "phpunit/phpunit": "^6.0|^7.0|^8.0", - "orchestra/testbench": "^3.5|^4.0|^5.0" + "orchestra/testbench": "^3.4|^4.0|^5.0" }, "autoload": { "psr-4": { From 15154cee4178b250decc684fb890f4a7a85c4619 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 18:07:13 +0200 Subject: [PATCH 087/113] phpunit 5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 691d3e1..bd560a2 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^5.0|^6.0|^7.0|^8.0", "orchestra/testbench": "^3.4|^4.0|^5.0" }, "autoload": { From 57a4154992c5a2193feef5ba6ab3fe83840212f8 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 19:18:51 +0200 Subject: [PATCH 088/113] Restore compatibility with PHP 5.6 --- .travis.yml | 16 +++++++++++++++- tests/BaseTestCase.php | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6a518d0..ab7f0df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,10 +28,14 @@ matrix: env: LARAVEL_VERSION=5.6.* - php: 7.0 env: LARAVEL_VERSION=5.6.* + - php: 7.4 + env: LARAVEL_VERSION=5.6.* - php: 5.6 env: LARAVEL_VERSION=5.7.* - php: 7.0 env: LARAVEL_VERSION=5.7.* + - php: 7.4 + env: LARAVEL_VERSION=5.7.* - php: 5.6 env: LARAVEL_VERSION=5.8.* - php: 7.0 @@ -50,14 +54,24 @@ matrix: env: LARAVEL_VERSION=7.* - php: 7.1 env: LARAVEL_VERSION=7.* + - php: 7.1 + env: LARAVEL_VERSION=5.4.* + - php: 7.2 + env: LARAVEL_VERSION=5.4.* + - php: 7.3 + env: LARAVEL_VERSION=5.4.* + - php: 7.4 + env: LARAVEL_VERSION=5.4.* jobs: fast_finish: true sudo: false +# ensure that the specific Laravel version is required before_install: - # ensure that the specific Laravel version is required + - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function setUp()', version_compare(phpversion(), '7.1', '<') ? 'public function setUp()' : 'public function setUp(): void',file_get_contents('tests/BaseTestCase.php')));" + - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function tearDown()', version_compare(phpversion(), '7.1', '<') ? 'public function tearDown()' : 'public function tearDown(): void',file_get_contents('tests/BaseTestCase.php')));" - composer require "laravel/framework:${LARAVEL_VERSION}" --no-update install: travis_retry composer update --no-interaction --prefer-dist diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index d4aa1a3..853067d 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -15,7 +15,7 @@ abstract class BaseTestCase extends TestCase { const DATABASE_CONNECTION = 'closuretable'; - public function setUp() : void + public function setUp(): void { parent::setUp(); @@ -32,7 +32,7 @@ public function setUp() : void ]); } - public function tearDown(): void + public function tearDown() { // this is to avoid "too many connection" errors DB::disconnect(static::DATABASE_CONNECTION); From c4c87763c99becb291cbe9fd8290ae62b4aa88e6 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 19:23:53 +0200 Subject: [PATCH 089/113] Check --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab7f0df..8f669c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,8 +70,6 @@ sudo: false # ensure that the specific Laravel version is required before_install: - - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function setUp()', version_compare(phpversion(), '7.1', '<') ? 'public function setUp()' : 'public function setUp(): void',file_get_contents('tests/BaseTestCase.php')));" - - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function tearDown()', version_compare(phpversion(), '7.1', '<') ? 'public function tearDown()' : 'public function tearDown(): void',file_get_contents('tests/BaseTestCase.php')));" - composer require "laravel/framework:${LARAVEL_VERSION}" --no-update install: travis_retry composer update --no-interaction --prefer-dist From 77cb25faea8bf788304ecbd453fc45e3f73b1983 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 19:26:46 +0200 Subject: [PATCH 090/113] wip --- .travis.yml | 2 +- tests/BaseTestCase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8f669c0..60f4863 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,7 +72,7 @@ sudo: false before_install: - composer require "laravel/framework:${LARAVEL_VERSION}" --no-update -install: travis_retry composer update --no-interaction --prefer-dist +install: composer update --no-interaction --prefer-dist before_script: - mysql -e 'create database closuretabletest;' diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 853067d..59adf3a 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -15,7 +15,7 @@ abstract class BaseTestCase extends TestCase { const DATABASE_CONNECTION = 'closuretable'; - public function setUp(): void + public function setUp() { parent::setUp(); From 686b715589219e49268327ef570c2b493dc577c5 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 19:29:20 +0200 Subject: [PATCH 091/113] try to make test case compatible with phpunit 8 on-the-fly --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 60f4863..02fbf1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,7 +77,10 @@ install: composer update --no-interaction --prefer-dist before_script: - mysql -e 'create database closuretabletest;' -script: vendor/bin/phpunit +script: + - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function setUp()', version_compare(phpversion(), '7.1', '<') ? 'public function setUp()' : 'public function setUp(): void',file_get_contents('tests/BaseTestCase.php')));" + - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function tearDown()', version_compare(phpversion(), '7.1', '<') ? 'public function tearDown()' : 'public function tearDown(): void',file_get_contents('tests/BaseTestCase.php')));" + - vendor/bin/phpunit branches: only: From 269b49530ec3d1be39b99f332e8bbbe0e495dffa Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 16 Apr 2020 19:41:17 +0200 Subject: [PATCH 092/113] Alternative approach to set compatibility with PHPunit 8 --- .travis.yml | 3 +-- tests/script-change-testcase-return-type.php | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 tests/script-change-testcase-return-type.php diff --git a/.travis.yml b/.travis.yml index 02fbf1e..d53c56e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,10 +76,9 @@ install: composer update --no-interaction --prefer-dist before_script: - mysql -e 'create database closuretabletest;' + - php ./tests/script-change-testcase-return-type.php script: - - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function setUp()', version_compare(phpversion(), '7.1', '<') ? 'public function setUp()' : 'public function setUp(): void',file_get_contents('tests/BaseTestCase.php')));" - - php -r "file_put_contents('tests/BaseTestCase.php', str_replace('public function tearDown()', version_compare(phpversion(), '7.1', '<') ? 'public function tearDown()' : 'public function tearDown(): void',file_get_contents('tests/BaseTestCase.php')));" - vendor/bin/phpunit branches: diff --git a/tests/script-change-testcase-return-type.php b/tests/script-change-testcase-return-type.php new file mode 100644 index 0000000..a20c7da --- /dev/null +++ b/tests/script-change-testcase-return-type.php @@ -0,0 +1,8 @@ + Date: Fri, 17 Apr 2020 14:15:12 +0700 Subject: [PATCH 093/113] removed private property checks --- tests/Models/Entity/ConstructionTests.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/Models/Entity/ConstructionTests.php b/tests/Models/Entity/ConstructionTests.php index acf8ad8..686150a 100644 --- a/tests/Models/Entity/ConstructionTests.php +++ b/tests/Models/Entity/ConstructionTests.php @@ -35,36 +35,16 @@ public function testEntityShouldUseDefaultClosureTable() static::assertEquals($entity->getTable() . '_closure', $closure->getTable()); } - public function testNewFromBuilder() - { - $entity = new Entity([ - 'parent_id' => 123, - 'position' => 5 - ]); - - $newEntity = $entity->newFromBuilder([ - 'parent_id' => 321, - 'position' => 0 - ]); - - static::assertEquals(321, static::readAttribute($newEntity, 'previousParentId')); - static::assertEquals(0, static::readAttribute($newEntity, 'previousPosition')); - } - public function testCreate() { $entity = new Page(['title' => 'Item 1']); static::assertEquals(null, $entity->position); - static::assertEquals(null, static::readAttribute($entity, 'previousPosition')); static::assertEquals(null, $entity->parent_id); - static::assertEquals(null, static::readAttribute($entity, 'previousParentId')); $entity->save(); static::assertEquals(9, $entity->position); - static::assertEquals(null, static::readAttribute($entity, 'previousPosition')); static::assertEquals(null, $entity->parent_id); - static::assertEquals(null, static::readAttribute($entity, 'previousParentId')); } } From 7a7d414f276b14e8ef268919a0e66c21389fb077 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 17 Apr 2020 17:27:03 +0700 Subject: [PATCH 094/113] removed deprecated EntityInterface from the check --- src/Models/Entity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Entity.php b/src/Models/Entity.php index 5f804ee..aed5c63 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -1756,7 +1756,7 @@ public static function createFromArray(array $tree, EntityInterface $parent = nu */ public function moveTo($position, $ancestor = null) { - $parentId = (!$ancestor instanceof EntityInterface ? $ancestor : $ancestor->getKey()); + $parentId = $ancestor instanceof self ? $ancestor->getKey() : $ancestor; if ($this->parent_id === $parentId && $this->parent_id !== null) { return $this; From 9f61801935a5ee55d2a6c89a3a212d29d49ace2d Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 17 Apr 2020 18:49:04 +0700 Subject: [PATCH 095/113] improved descriptions --- src/Console/MakeCommand.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Console/MakeCommand.php b/src/Console/MakeCommand.php index 1ae3e99..458fe8a 100644 --- a/src/Console/MakeCommand.php +++ b/src/Console/MakeCommand.php @@ -125,14 +125,14 @@ protected function writeModels() protected function getOptions() { return [ - ['namespace', 'ns', InputOption::VALUE_OPTIONAL, 'Namespace for entity and its closure.'], - ['entity', 'e', InputOption::VALUE_REQUIRED, 'Entity class name.'], - ['entity-table', 'et', InputOption::VALUE_OPTIONAL, 'Entity table name.'], - ['closure', 'c', InputOption::VALUE_OPTIONAL, 'Closure class name'], - ['closure-table', 'ct', InputOption::VALUE_OPTIONAL, 'Closure table name.'], - ['models-path', 'mdl', InputOption::VALUE_OPTIONAL, 'Models path.'], - ['migrations-path', 'mgr', InputOption::VALUE_OPTIONAL, 'Migrations path.'], - ['use-innodb', 'i', InputOption::VALUE_OPTIONAL, 'Use InnoDB tables.'], + ['namespace', 'ns', InputOption::VALUE_OPTIONAL, 'Namespace for entity and closure classes'], + ['entity', 'e', InputOption::VALUE_REQUIRED, 'Class name of the entity model'], + ['entity-table', 'et', InputOption::VALUE_OPTIONAL, 'Entity table name'], + ['closure', 'c', InputOption::VALUE_OPTIONAL, 'Class name of the closure (relationships) model'], + ['closure-table', 'ct', InputOption::VALUE_OPTIONAL, 'Closure (relationships) table name'], + ['models-path', 'mdl', InputOption::VALUE_OPTIONAL, 'Directory in which to put generated models'], + ['migrations-path', 'mgr', InputOption::VALUE_OPTIONAL, 'Directory in which to put generated migrations'], + ['use-innodb', 'i', InputOption::VALUE_OPTIONAL, 'Use InnoDB engine (MySQL only)'], ]; } From 59b046a441a774ae359628935a23c7f2034b83e8 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 17 Apr 2020 22:02:56 +0700 Subject: [PATCH 096/113] removed deprecated real_depth column from stub and tests --- .../stubs/migrations/migration-innodb.php | 55 ------------------- src/Generators/stubs/migrations/migration.php | 1 - tests/Generators/expectedMigration.php | 1 - tests/Generators/expectedMigrationInnoDb.php | 1 - 4 files changed, 58 deletions(-) delete mode 100644 src/Generators/stubs/migrations/migration-innodb.php diff --git a/src/Generators/stubs/migrations/migration-innodb.php b/src/Generators/stubs/migrations/migration-innodb.php deleted file mode 100644 index 46a42d4..0000000 --- a/src/Generators/stubs/migrations/migration-innodb.php +++ /dev/null @@ -1,55 +0,0 @@ -engine = 'InnoDB'; - - $table->increments('id'); - $table->integer('parent_id')->unsigned()->nullable(); - $table->integer('position', false, true); - $table->integer('real_depth', false, true); - $table->softDeletes(); - - $table->foreign('parent_id') - ->references('id') - ->on('{{entity_table}}') - ->onDelete('set null'); - }); - - Schema::create('{{closure_table}}', function (Blueprint $table) { - $table->engine = 'InnoDB'; - - $table->increments('closure_id'); - $table->integer('ancestor', false, true); - $table->integer('descendant', false, true); - $table->integer('depth', false, true); - - $table->foreign('ancestor') - ->references('id') - ->on('{{entity_table}}') - ->onDelete('cascade'); - - $table->foreign('descendant') - ->references('id') - ->on('{{entity_table}}') - ->onDelete('cascade'); - }); - } - - public function down() - { - Schema::table('{{closure_table}}', function (Blueprint $table) { - Schema::dropIfExists('{{closure_table}}'); - }); - - Schema::table('{{entity_table}}', function (Blueprint $table) { - Schema::dropIfExists('{{entity_table}}'); - }); - } -} diff --git a/src/Generators/stubs/migrations/migration.php b/src/Generators/stubs/migrations/migration.php index cc98a03..3a944a1 100644 --- a/src/Generators/stubs/migrations/migration.php +++ b/src/Generators/stubs/migrations/migration.php @@ -11,7 +11,6 @@ public function up() $table->increments('id'); $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); - $table->integer('real_depth', false, true); $table->softDeletes(); $table->foreign('parent_id') diff --git a/tests/Generators/expectedMigration.php b/tests/Generators/expectedMigration.php index 4f74ba9..11edb1a 100644 --- a/tests/Generators/expectedMigration.php +++ b/tests/Generators/expectedMigration.php @@ -11,7 +11,6 @@ public function up() $table->increments('id'); $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); - $table->integer('real_depth', false, true); $table->softDeletes(); $table->foreign('parent_id') diff --git a/tests/Generators/expectedMigrationInnoDb.php b/tests/Generators/expectedMigrationInnoDb.php index e4d3c91..9d8fb5d 100644 --- a/tests/Generators/expectedMigrationInnoDb.php +++ b/tests/Generators/expectedMigrationInnoDb.php @@ -11,7 +11,6 @@ public function up() $table->increments('id'); $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); - $table->integer('real_depth', false, true); $table->softDeletes(); $table->foreign('parent_id') From ab98811bee55c1ac69541b3d4cf5115ae4a94c1b Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 17 Apr 2020 23:22:30 +0700 Subject: [PATCH 097/113] wrote tests for the closuretable:make command, fixed some bugs related to it --- phpunit.xml | 1 + src/Console/MakeCommand.php | 49 ++++-- src/Generators/Migration.php | 2 +- src/Generators/Model.php | 16 +- src/Generators/stubs/migrations/migration.php | 6 +- tests/Console/ComposerStub.php | 29 ++++ tests/Console/CustomApplication.php | 10 ++ tests/Console/MakeCommandTests.php | 159 ++++++++++++++++++ tests/Console/app/expectedBar.php | 21 +++ tests/Console/app/expectedBarTree.php | 14 ++ tests/Console/app/expectedFoo.php | 21 +++ tests/Console/app/expectedFooTree.php | 14 ++ .../migrations/expectedBarMigration.php | 48 ++++++ .../migrations/expectedFooMigration.php | 50 ++++++ 14 files changed, 417 insertions(+), 23 deletions(-) create mode 100644 tests/Console/ComposerStub.php create mode 100644 tests/Console/CustomApplication.php create mode 100644 tests/Console/MakeCommandTests.php create mode 100644 tests/Console/app/expectedBar.php create mode 100644 tests/Console/app/expectedBarTree.php create mode 100644 tests/Console/app/expectedFoo.php create mode 100644 tests/Console/app/expectedFooTree.php create mode 100644 tests/Console/database/migrations/expectedBarMigration.php create mode 100644 tests/Console/database/migrations/expectedFooMigration.php diff --git a/phpunit.xml b/phpunit.xml index 8a6f145..66f6b82 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,6 +11,7 @@ > + ./tests/Console ./tests/Extensions ./tests/Generators ./tests/Models diff --git a/src/Console/MakeCommand.php b/src/Console/MakeCommand.php index 458fe8a..cc17286 100644 --- a/src/Console/MakeCommand.php +++ b/src/Console/MakeCommand.php @@ -6,8 +6,8 @@ use Franzose\ClosureTable\Generators\Migration; use Franzose\ClosureTable\Generators\Model; use Illuminate\Console\Command; -use Illuminate\Container\Container; use Illuminate\Support\Composer; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; /** @@ -117,6 +117,13 @@ protected function writeModels() } } + protected function getArguments() + { + return [ + ['entity', InputArgument::REQUIRED, 'Class name of the entity model'] + ]; + } + /** * Gets the console command options. * @@ -126,7 +133,6 @@ protected function getOptions() { return [ ['namespace', 'ns', InputOption::VALUE_OPTIONAL, 'Namespace for entity and closure classes'], - ['entity', 'e', InputOption::VALUE_REQUIRED, 'Class name of the entity model'], ['entity-table', 'et', InputOption::VALUE_OPTIONAL, 'Entity table name'], ['closure', 'c', InputOption::VALUE_OPTIONAL, 'Class name of the closure (relationships) model'], ['closure-table', 'ct', InputOption::VALUE_OPTIONAL, 'Closure (relationships) table name'], @@ -143,22 +149,35 @@ protected function getOptions() */ protected function prepareOptions() { + $entity = $this->argument('entity'); $options = $this->getOptions(); - $input = []; + $input = array_map(function (array $option) { + return $this->option($option[0]); + }, $this->getOptions()); + + $this->options[$options[0][0]] = $input[0] + ?: substr($entity, 0, strrpos($entity, '\\')) + ?: rtrim(app()->getNamespace(), '\\'); + $this->options['entity'] = $this->getEntityModelName($entity); + $this->options[$options[1][0]] = $input[1] ?: ExtStr::tableize($this->options['entity']); + $this->options[$options[2][0]] = $input[2] + ? $this->getEntityModelName($input[2]) + : $this->options['entity'] . 'Closure'; + + $this->options[$options[3][0]] = $input[3] ?: ExtStr::snake($this->options[$options[2][0]]); + $this->options[$options[4][0]] = $input[4] ?: app_path(); + $this->options[$options[5][0]] = $input[5] ?: app()->databasePath('migrations'); + $this->options[$options[6][0]] = $input[6] ?: false; + } - foreach ($options as $option) { - $input[] = $this->option($option[0]); - } + private function getEntityModelName($original) + { + $delimpos = strrpos($original, '\\'); - $lastnsdelim = strrpos($input[1], '\\'); + if ($delimpos === false) { + return $original; + } - $this->options[$options[0][0]] = $input[0] ?: rtrim(Container::getInstance()->getNamespace(), '\\'); - $this->options[$options[1][0]] = substr($input[1], $lastnsdelim); - $this->options[$options[2][0]] = $input[2] ?: ExtStr::tableize($input[1]); - $this->options[$options[3][0]] = $input[3] ?: $this->options[$options[1][0]] . 'Closure'; - $this->options[$options[4][0]] = $input[4] ?: ExtStr::tableize($input[1] . 'Closure'); - $this->options[$options[5][0]] = $input[5] ?: './app'; - $this->options[$options[6][0]] = $input[6] ?: './database/migrations'; - $this->options[$options[7][0]] = $input[7] ?: false; + return substr($original, $delimpos + 1); } } diff --git a/src/Generators/Migration.php b/src/Generators/Migration.php index 8127c69..72bae95 100644 --- a/src/Generators/Migration.php +++ b/src/Generators/Migration.php @@ -21,7 +21,7 @@ public function create(array $options) { $entityClass = $this->getClassName($options['entity-table']); $closureClass = $this->getClassName($options['closure-table']); - $innoDb = $options['use-innodb'] ? '$table->engine = \'InnoDB\';' : ''; + $innoDb = $options['use-innodb'] ? "\n" . ' $table->engine = \'InnoDB\';' : ''; $path = $this->getPath($options['entity-table'], $options['migrations-path']); $stub = $this->getStub('migration', 'migrations'); diff --git a/src/Generators/Model.php b/src/Generators/Model.php index 8f46e10..00122b0 100644 --- a/src/Generators/Model.php +++ b/src/Generators/Model.php @@ -2,6 +2,7 @@ namespace Franzose\ClosureTable\Generators; use Franzose\ClosureTable\Extensions\Str as ExtStr; +use Illuminate\Support\Str; /** * ClosureTable specific models generator class. @@ -30,12 +31,16 @@ public function create(array $options) // First, we make entity classes $paths[] = $path = $this->getPath($qualifiedEntityName, $options['models-path']); $stub = $this->getStub('entity', 'models'); + $closureClass = ucfirst($options['closure']); + $namespaceWithDelimiter = $options['namespace'] . '\\'; $this->filesystem->put($path, $this->parseStub($stub, [ 'namespace' => $nsplaceholder, 'entity_class' => ucfirst($options['entity']), 'entity_table' => $options['entity-table'], - 'closure_class' => $options['namespace'] . '\\' . ucfirst($options['closure']), + 'closure_class' => Str::startsWith($closureClass, $namespaceWithDelimiter) + ? $closureClass + : $namespaceWithDelimiter . $closureClass, ])); // Second, we make closure classes @@ -44,7 +49,7 @@ public function create(array $options) $this->filesystem->put($path, $this->parseStub($stub, [ 'namespace' => $nsplaceholder, - 'closure_class' => ucfirst($options['closure']), + 'closure_class' => $closureClass, 'closure_table' => $options['closure-table'] ])); @@ -60,6 +65,11 @@ public function create(array $options) */ protected function getPath($name, $path) { - return $path . '/' . ExtStr::classify($name) . '.php'; + $delimpos = strrpos($name, '\\'); + $filename = $delimpos === false + ? ExtStr::classify($name) + : substr(ExtStr::classify($name), $delimpos + 1); + + return $path . '/' . $filename . '.php'; } } diff --git a/src/Generators/stubs/migrations/migration.php b/src/Generators/stubs/migrations/migration.php index 3a944a1..b2df114 100644 --- a/src/Generators/stubs/migrations/migration.php +++ b/src/Generators/stubs/migrations/migration.php @@ -17,8 +17,7 @@ public function up() ->references('id') ->on('{{entity_table}}') ->onDelete('set null'); - - {{innodb}} +{{innodb}} }); Schema::create('{{closure_table}}', function (Blueprint $table) { @@ -37,8 +36,7 @@ public function up() ->references('id') ->on('{{entity_table}}') ->onDelete('cascade'); - - {{innodb}} +{{innodb}} }); } diff --git a/tests/Console/ComposerStub.php b/tests/Console/ComposerStub.php new file mode 100644 index 0000000..4b4f7c9 --- /dev/null +++ b/tests/Console/ComposerStub.php @@ -0,0 +1,29 @@ +getBasePath()), static function ($app) { + $app->bind( + LaravelLoadConfiguration::class, + TestbenchLoadConfiguration::class + ); + }); + } + + public function setUp() + { + parent::setUp(); + + $this->app->setBasePath(__DIR__); + $this->app->register(new ClosureTableServiceProvider($this->app)); + $this->app->bind(Composer::class, static function () { + return new ComposerStub(); + }); + + $this->artisan = $this->app->make(Kernel::class); + $this->modelsPath = $this->app->path(); + $this->migrationsPath = $this->app->databasePath('migrations'); + } + + public function testCommandMustRequireEntityName() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Not enough arguments (missing: "entity").'); + + $this->artisan->call('closuretable:make'); + } + + public function testCommandShouldGenerateFilesAtDefaultPaths() + { + Carbon::setTestNow('2020-04-17 00:00:00'); + + $this->artisan->call('closuretable:make', [ + 'entity' => 'Foo', + '--namespace' => 'Foo', + '--entity-table' => 'foo', + '--closure' => 'FooTree', + '--closure-table' => 'foo_tree', + '--use-innodb' => true + ]); + + Carbon::setTestNow(); + + $actualFoo = $this->modelsPath . '/Foo.php'; + $actualFooClosure = $this->modelsPath . '/FooTree.php'; + $expectedFoo = $this->modelsPath . '/expectedFoo.php'; + $expectedFooClosure = $this->modelsPath . '/expectedFooTree.php'; + $expectedMigrationPath = $this->migrationsPath . '/expectedFooMigration.php'; + $actualMigrationPath = $this->migrationsPath . '/2020_04_17_000000_create_foos_table_migration.php'; + + static::assertFileExists($actualFoo); + static::assertFileExists($actualFooClosure); + static::assertFileEquals($expectedFoo, $actualFoo); + static::assertFileEquals($expectedFooClosure, $actualFooClosure); + static::assertFileExists($actualMigrationPath); + static::assertFileEquals($expectedMigrationPath, $actualMigrationPath); + + unlink($actualFoo); + unlink($actualFooClosure); + unlink($actualMigrationPath); + } + + public function testCommandShouldGenerateFilesAtCustomPaths() + { + $filesystem = new Filesystem(); + + $customModelsPath = $this->modelsPath . '/custom'; + $customMigrationsPath = $this->migrationsPath . '/custom'; + + if (!$filesystem->exists($customModelsPath)) { + $filesystem->makeDirectory($customModelsPath); + } + + if (!$filesystem->exists($customMigrationsPath)) { + $filesystem->makeDirectory($customMigrationsPath); + } + + Carbon::setTestNow('2020-04-17 00:00:00'); + + $this->artisan->call('closuretable:make', [ + 'entity' => 'Foo', + '--namespace' => 'Foo', + '--entity-table' => 'foo', + '--closure' => 'FooTree', + '--closure-table' => 'foo_tree', + '--models-path' => $customModelsPath, + '--migrations-path' => $customMigrationsPath + ]); + + Carbon::setTestNow(); + + $models = $filesystem->files($customModelsPath); + $migrations = $filesystem->files($customMigrationsPath); + + static::assertCount(2, $models); + static::assertCount(1, $migrations); + + $filesystem->deleteDirectory($customModelsPath); + $filesystem->deleteDirectory($customMigrationsPath); + } + + public function testCommandShouldHandleNamespacedModelNames() + { + Carbon::setTestNow('2020-04-17 00:00:00'); + + $this->artisan->call('closuretable:make', [ + 'entity' => 'Foo\\Bar', + '--closure' => 'Foo\\BarTree', + ]); + + Carbon::setTestNow(); + + $actualFoo = $this->modelsPath . '/Bar.php'; + $actualFooClosure = $this->modelsPath . '/BarTree.php'; + $expectedFoo = $this->modelsPath . '/expectedBar.php'; + $expectedFooClosure = $this->modelsPath . '/expectedBarTree.php'; + $expectedMigrationPath = $this->migrationsPath . '/expectedBarMigration.php'; + $actualMigrationPath = $this->migrationsPath . '/2020_04_17_000000_create_bars_table_migration.php'; + + static::assertFileExists($actualFoo); + static::assertFileExists($actualFooClosure); + static::assertFileEquals($expectedFoo, $actualFoo); + static::assertFileEquals($expectedFooClosure, $actualFooClosure); + static::assertFileExists($actualMigrationPath); + static::assertFileEquals($expectedMigrationPath, $actualMigrationPath); + + unlink($actualFoo); + unlink($actualFooClosure); + unlink($actualMigrationPath); + } +} diff --git a/tests/Console/app/expectedBar.php b/tests/Console/app/expectedBar.php new file mode 100644 index 0000000..8a62681 --- /dev/null +++ b/tests/Console/app/expectedBar.php @@ -0,0 +1,21 @@ +increments('id'); + $table->integer('parent_id')->unsigned()->nullable(); + $table->integer('position', false, true); + $table->softDeletes(); + + $table->foreign('parent_id') + ->references('id') + ->on('bars') + ->onDelete('set null'); + + }); + + Schema::create('bar_tree', function (Blueprint $table) { + $table->increments('closure_id'); + + $table->integer('ancestor', false, true); + $table->integer('descendant', false, true); + $table->integer('depth', false, true); + + $table->foreign('ancestor') + ->references('id') + ->on('bars') + ->onDelete('cascade'); + + $table->foreign('descendant') + ->references('id') + ->on('bars') + ->onDelete('cascade'); + + }); + } + + public function down() + { + Schema::dropIfExists('bar_tree'); + Schema::dropIfExists('bars'); + } +} diff --git a/tests/Console/database/migrations/expectedFooMigration.php b/tests/Console/database/migrations/expectedFooMigration.php new file mode 100644 index 0000000..c33137f --- /dev/null +++ b/tests/Console/database/migrations/expectedFooMigration.php @@ -0,0 +1,50 @@ +increments('id'); + $table->integer('parent_id')->unsigned()->nullable(); + $table->integer('position', false, true); + $table->softDeletes(); + + $table->foreign('parent_id') + ->references('id') + ->on('foo') + ->onDelete('set null'); + + $table->engine = 'InnoDB'; + }); + + Schema::create('foo_tree', function (Blueprint $table) { + $table->increments('closure_id'); + + $table->integer('ancestor', false, true); + $table->integer('descendant', false, true); + $table->integer('depth', false, true); + + $table->foreign('ancestor') + ->references('id') + ->on('foo') + ->onDelete('cascade'); + + $table->foreign('descendant') + ->references('id') + ->on('foo') + ->onDelete('cascade'); + + $table->engine = 'InnoDB'; + }); + } + + public function down() + { + Schema::dropIfExists('foo_tree'); + Schema::dropIfExists('foo'); + } +} From 92ccc6f05e8cd271ce475518eb2971a0708119de Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Fri, 17 Apr 2020 23:31:38 +0700 Subject: [PATCH 098/113] rewrote how we get namespace --- src/Console/MakeCommand.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Console/MakeCommand.php b/src/Console/MakeCommand.php index cc17286..7b99f1b 100644 --- a/src/Console/MakeCommand.php +++ b/src/Console/MakeCommand.php @@ -155,9 +155,7 @@ protected function prepareOptions() return $this->option($option[0]); }, $this->getOptions()); - $this->options[$options[0][0]] = $input[0] - ?: substr($entity, 0, strrpos($entity, '\\')) - ?: rtrim(app()->getNamespace(), '\\'); + $this->options[$options[0][0]] = $this->getNamespace($entity, $input[0]); $this->options['entity'] = $this->getEntityModelName($entity); $this->options[$options[1][0]] = $input[1] ?: ExtStr::tableize($this->options['entity']); $this->options[$options[2][0]] = $input[2] @@ -170,6 +168,21 @@ protected function prepareOptions() $this->options[$options[6][0]] = $input[6] ?: false; } + private function getNamespace($entity, $original) + { + if (!empty($original)) { + return $original; + } + + $namespace = substr($entity, 0, strrpos($entity, '\\')); + + if (!empty($namespace)) { + return $namespace; + } + + return rtrim(app()->getNamespace(), '\\'); + } + private function getEntityModelName($original) { $delimpos = strrpos($original, '\\'); From c20c7713711582ddaba56a7f8757b9574bc1c4f6 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sat, 18 Apr 2020 00:03:27 +0700 Subject: [PATCH 099/113] updated help messages --- src/Console/MakeCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Console/MakeCommand.php b/src/Console/MakeCommand.php index 7b99f1b..47e9519 100644 --- a/src/Console/MakeCommand.php +++ b/src/Console/MakeCommand.php @@ -132,10 +132,10 @@ protected function getArguments() protected function getOptions() { return [ - ['namespace', 'ns', InputOption::VALUE_OPTIONAL, 'Namespace for entity and closure classes'], - ['entity-table', 'et', InputOption::VALUE_OPTIONAL, 'Entity table name'], + ['namespace', 'ns', InputOption::VALUE_OPTIONAL, 'Namespace for entity and closure classes. Once given, it will override namespaces of the models of entity and closure'], + ['entity-table', 'et', InputOption::VALUE_OPTIONAL, 'Database table name for entity'], ['closure', 'c', InputOption::VALUE_OPTIONAL, 'Class name of the closure (relationships) model'], - ['closure-table', 'ct', InputOption::VALUE_OPTIONAL, 'Closure (relationships) table name'], + ['closure-table', 'ct', InputOption::VALUE_OPTIONAL, 'Database table name for closure (relationships)'], ['models-path', 'mdl', InputOption::VALUE_OPTIONAL, 'Directory in which to put generated models'], ['migrations-path', 'mgr', InputOption::VALUE_OPTIONAL, 'Directory in which to put generated migrations'], ['use-innodb', 'i', InputOption::VALUE_OPTIONAL, 'Use InnoDB engine (MySQL only)'], From 9dbc35d0b591411682715bdeb94bbbe5a57abf8e Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sat, 18 Apr 2020 08:34:39 +0700 Subject: [PATCH 100/113] refactored Travis CI workaround script --- tests/script-change-testcase-return-type.php | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/script-change-testcase-return-type.php b/tests/script-change-testcase-return-type.php index a20c7da..dc39b3f 100644 --- a/tests/script-change-testcase-return-type.php +++ b/tests/script-change-testcase-return-type.php @@ -1,8 +1,26 @@ - Date: Sat, 18 Apr 2020 08:33:23 +0200 Subject: [PATCH 101/113] Improve script that makes base test cases compatible with PHPUnit 8 --- .travis.yml | 2 +- tests/script-change-testcase-return-type.php | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d53c56e..b60cca9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -85,7 +85,7 @@ branches: only: - master - 6.x - - travis-ci-for-6.x + - improve-travis # version tag, e.g. v1.0.0 - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ - /^\d+\.\d+?$/ diff --git a/tests/script-change-testcase-return-type.php b/tests/script-change-testcase-return-type.php index dc39b3f..e456bee 100644 --- a/tests/script-change-testcase-return-type.php +++ b/tests/script-change-testcase-return-type.php @@ -9,12 +9,19 @@ exit(0); } -$filesToPatch = [ - 'tests/BaseTestCase.php', - 'tests/Console/MakeCommandTests.php' -]; +require_once './vendor/autoload.php'; -foreach ($filesToPatch as $path) { +use Symfony\Component\Finder\Finder; + +$finder = new Finder(); +$finder->files() + ->ignoreVCS(false) + ->in(__DIR__) + ->name('*.php') + ->notName('script-change-testcase-return-type.php')->contains('use Orchestra\Testbench\TestCase;'); + +foreach ($finder as $file) { + $absoluteFilePath = $file->getRealPath(); $contents = file_get_contents($path); $contents = str_replace( ['public function setUp()', 'public function tearDown()'], @@ -23,4 +30,4 @@ ); file_put_contents($path, $contents); -} +} \ No newline at end of file From f74f6a2020ebc4c0116cf0b10998df33f9e8a489 Mon Sep 17 00:00:00 2001 From: Alessio Date: Sat, 18 Apr 2020 08:34:03 +0200 Subject: [PATCH 102/113] Update after rebase to use array approach --- tests/script-change-testcase-return-type.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/script-change-testcase-return-type.php b/tests/script-change-testcase-return-type.php index e456bee..63a2d01 100644 --- a/tests/script-change-testcase-return-type.php +++ b/tests/script-change-testcase-return-type.php @@ -18,10 +18,11 @@ ->ignoreVCS(false) ->in(__DIR__) ->name('*.php') - ->notName('script-change-testcase-return-type.php')->contains('use Orchestra\Testbench\TestCase;'); + ->notName('script-change-testcase-return-type.php') + ->contains('use Orchestra\Testbench\TestCase;'); foreach ($finder as $file) { - $absoluteFilePath = $file->getRealPath(); + $path = $file->getRealPath(); $contents = file_get_contents($path); $contents = str_replace( ['public function setUp()', 'public function tearDown()'], From 08a632db38a5584b958856b558618ea87276832e Mon Sep 17 00:00:00 2001 From: Alessio Date: Sat, 18 Apr 2020 13:37:15 +0200 Subject: [PATCH 103/113] Test using MySQL and Postgres --- .travis.yml | 59 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b60cca9..58ef4eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,11 @@ language: php services: - mysql + - postgresql + +cache: + directories: + - .composer php: - 5.6 @@ -13,47 +18,86 @@ php: env: - LARAVEL_VERSION=5.4.* + - LARAVEL_VERSION=5.4.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - LARAVEL_VERSION=5.5.* + - LARAVEL_VERSION=5.5.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - LARAVEL_VERSION=5.6.* + - LARAVEL_VERSION=5.6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - LARAVEL_VERSION=5.7.* + - LARAVEL_VERSION=5.7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - LARAVEL_VERSION=5.8.* + - LARAVEL_VERSION=5.8.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - LARAVEL_VERSION=6.* + - LARAVEL_VERSION=6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - LARAVEL_VERSION=7.* + - LARAVEL_VERSION=7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis matrix: exclude: - php: 5.6 env: LARAVEL_VERSION=5.5.* + - php: 5.6 + env: LARAVEL_VERSION=5.5.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 5.6 env: LARAVEL_VERSION=5.6.* - php: 7.0 env: LARAVEL_VERSION=5.6.* - php: 7.4 env: LARAVEL_VERSION=5.6.* + - php: 5.6 + env: LARAVEL_VERSION=5.6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.0 + env: LARAVEL_VERSION=5.6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.4 + env: LARAVEL_VERSION=5.6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 5.6 env: LARAVEL_VERSION=5.7.* - php: 7.0 env: LARAVEL_VERSION=5.7.* - php: 7.4 env: LARAVEL_VERSION=5.7.* + - php: 5.6 + env: LARAVEL_VERSION=5.7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.0 + env: LARAVEL_VERSION=5.7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.4 + env: LARAVEL_VERSION=5.7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 5.6 env: LARAVEL_VERSION=5.8.* - php: 7.0 env: LARAVEL_VERSION=5.8.* - php: 7.1 env: LARAVEL_VERSION=5.8.* + - php: 5.6 + env: LARAVEL_VERSION=5.8.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.0 + env: LARAVEL_VERSION=5.8.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.1 + env: LARAVEL_VERSION=5.8.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 5.6 env: LARAVEL_VERSION=6.* - php: 7.0 env: LARAVEL_VERSION=6.* - php: 7.1 env: LARAVEL_VERSION=6.* + - php: 5.6 + env: LARAVEL_VERSION=6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.0 + env: LARAVEL_VERSION=6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.1 + env: LARAVEL_VERSION=6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 5.6 env: LARAVEL_VERSION=7.* - php: 7.0 env: LARAVEL_VERSION=7.* - php: 7.1 env: LARAVEL_VERSION=7.* + - php: 5.6 + env: LARAVEL_VERSION=7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.0 + env: LARAVEL_VERSION=7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.1 + env: LARAVEL_VERSION=7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.1 env: LARAVEL_VERSION=5.4.* - php: 7.2 @@ -62,14 +106,18 @@ matrix: env: LARAVEL_VERSION=5.4.* - php: 7.4 env: LARAVEL_VERSION=5.4.* - -jobs: - fast_finish: true - -sudo: false + - php: 7.1 + env: LARAVEL_VERSION=5.4.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.2 + env: LARAVEL_VERSION=5.4.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.3 + env: LARAVEL_VERSION=5.4.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis + - php: 7.4 + env: LARAVEL_VERSION=5.4.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis # ensure that the specific Laravel version is required before_install: + - export COMPOSER_CACHE_DIR=`pwd`/.composer - composer require "laravel/framework:${LARAVEL_VERSION}" --no-update install: composer update --no-interaction --prefer-dist @@ -86,6 +134,7 @@ branches: - master - 6.x - improve-travis + - postgres-testing # version tag, e.g. v1.0.0 - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ - /^\d+\.\d+?$/ From 651cc43a936c025778ea4f799fbf43c2be4ee123 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 21 Apr 2020 20:49:00 +0700 Subject: [PATCH 104/113] made getLatestPosition static to try to fix tests on Laravel 5.4 --- src/Models/Entity.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Models/Entity.php b/src/Models/Entity.php index aed5c63..e73b0ae 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -277,7 +277,7 @@ public static function boot() static::saving(static function (Entity $entity) { if ($entity->isDirty($entity->getPositionColumn())) { - $latest = $entity->getLatestPosition(); + $latest = static::getLatestPosition($entity); if (!$entity->isMoved) { $latest--; @@ -285,7 +285,7 @@ public static function boot() $entity->position = max(0, min($entity->position, $latest)); } elseif (!$entity->exists) { - $entity->position = $entity->getLatestPosition(); + $entity->position = static::getLatestPosition($entity); } }); @@ -1571,7 +1571,7 @@ private function buildSiblingQuery(Builder $builder, $id, callable $positionCall public function addSibling(EntityInterface $sibling, $position = null, $returnSibling = false) { if ($this->exists) { - $position = $position === null ? $this->getLatestPosition() : $position; + $position = $position === null ? static::getLatestPosition($this) : $position; $sibling->moveTo($position, $this->parent_id); @@ -1598,7 +1598,7 @@ public function addSiblings(array $siblings, $from = null) return $this; } - $from = $from === null ? $this->getLatestPosition() : $from; + $from = $from === null ? static::getLatestPosition($this) : $from; $this->transactional(function () use ($siblings, &$from) { foreach ($siblings as $sibling) { @@ -1779,19 +1779,21 @@ public function moveTo($position, $ancestor = null) /** * Gets the next sibling position after the last one. * + * @param Entity $entity + * * @return int */ - private function getLatestPosition() + public static function getLatestPosition(Entity $entity) { - $positionColumn = $this->getPositionColumn(); - $parentIdColumn = $this->getParentIdColumn(); + $positionColumn = $entity->getPositionColumn(); + $parentIdColumn = $entity->getParentIdColumn(); - $entity = $this->select($positionColumn) - ->where($parentIdColumn, '=', $this->parent_id) + $latest = $entity->select($positionColumn) + ->where($parentIdColumn, '=', $entity->parent_id) ->latest($positionColumn) ->first(); - $position = $entity !== null ? $entity->position : -1; + $position = $latest !== null ? $latest->position : -1; return $position + 1; } From 885a45b32fa7429b8d34106e7a1234361bfd9e01 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Tue, 21 Apr 2020 20:53:39 +0700 Subject: [PATCH 105/113] removed "static" keyword to try to fix tests on Laravel 5.4 --- src/Models/Entity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Entity.php b/src/Models/Entity.php index e73b0ae..8c62f68 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -301,7 +301,7 @@ public static function boot() $entity->closure->insertNode($ancestor, $descendant); }); - static::saved(static function (Entity $entity) { + static::saved(function (Entity $entity) { $parentIdChanged = $entity->isDirty($entity->getParentIdColumn()); if ($parentIdChanged || $entity->isDirty($entity->getPositionColumn())) { From 6cf82fce9e683ee43615b469b41c6441dcd3728a Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 22 Apr 2020 21:27:32 +0700 Subject: [PATCH 106/113] made the "reorderSiblings" method public --- src/Models/Entity.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Models/Entity.php b/src/Models/Entity.php index 8c62f68..88381c1 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -301,7 +301,7 @@ public static function boot() $entity->closure->insertNode($ancestor, $descendant); }); - static::saved(function (Entity $entity) { + static::saved(static function (Entity $entity) { $parentIdChanged = $entity->isDirty($entity->getParentIdColumn()); if ($parentIdChanged || $entity->isDirty($entity->getPositionColumn())) { @@ -1801,9 +1801,14 @@ public static function getLatestPosition(Entity $entity) /** * Reorders node's siblings when it is moved to another position or ancestor. * + * This method must not be invoked directly, it's been made public + * to overcome visibility issue on the older PHP versions + * and is the subject to become private again in a future release. + * * @return void + * @internal */ - private function reorderSiblings() + public function reorderSiblings() { $position = $this->getPositionColumn(); From e31802f934862cb86246c1e54d42ddb19aad735d Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Wed, 22 Apr 2020 23:34:10 +0700 Subject: [PATCH 107/113] after a lot of trials and errors, just dropped PHP 5.6 support --- .travis.yml | 25 ------------------------- composer.json | 4 ++-- src/Models/Entity.php | 7 +------ 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58ef4eb..b74a4c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ cache: - .composer php: - - 5.6 - 7.0 - 7.1 - 7.2 @@ -34,66 +33,42 @@ env: matrix: exclude: - - php: 5.6 - env: LARAVEL_VERSION=5.5.* - - php: 5.6 - env: LARAVEL_VERSION=5.5.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - - php: 5.6 - env: LARAVEL_VERSION=5.6.* - php: 7.0 env: LARAVEL_VERSION=5.6.* - php: 7.4 env: LARAVEL_VERSION=5.6.* - - php: 5.6 - env: LARAVEL_VERSION=5.6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.0 env: LARAVEL_VERSION=5.6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.4 env: LARAVEL_VERSION=5.6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - - php: 5.6 - env: LARAVEL_VERSION=5.7.* - php: 7.0 env: LARAVEL_VERSION=5.7.* - php: 7.4 env: LARAVEL_VERSION=5.7.* - - php: 5.6 - env: LARAVEL_VERSION=5.7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.0 env: LARAVEL_VERSION=5.7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.4 env: LARAVEL_VERSION=5.7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - - php: 5.6 - env: LARAVEL_VERSION=5.8.* - php: 7.0 env: LARAVEL_VERSION=5.8.* - php: 7.1 env: LARAVEL_VERSION=5.8.* - - php: 5.6 - env: LARAVEL_VERSION=5.8.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.0 env: LARAVEL_VERSION=5.8.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.1 env: LARAVEL_VERSION=5.8.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - - php: 5.6 - env: LARAVEL_VERSION=6.* - php: 7.0 env: LARAVEL_VERSION=6.* - php: 7.1 env: LARAVEL_VERSION=6.* - - php: 5.6 - env: LARAVEL_VERSION=6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.0 env: LARAVEL_VERSION=6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.1 env: LARAVEL_VERSION=6.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - - php: 5.6 - env: LARAVEL_VERSION=7.* - php: 7.0 env: LARAVEL_VERSION=7.* - php: 7.1 env: LARAVEL_VERSION=7.* - - php: 5.6 - env: LARAVEL_VERSION=7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.0 env: LARAVEL_VERSION=7.* DB_DRIVER=pgsql DB_PORT=5432 DB_USERNAME=travis DB_NAME=travis - php: 7.1 diff --git a/composer.json b/composer.json index bd560a2..acf31d1 100644 --- a/composer.json +++ b/composer.json @@ -12,10 +12,10 @@ } ], "require": { - "php": ">=5.6.0" + "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^5.0|^6.0|^7.0|^8.0", + "phpunit/phpunit": "^6.0|^7.0|^8.0", "orchestra/testbench": "^3.4|^4.0|^5.0" }, "autoload": { diff --git a/src/Models/Entity.php b/src/Models/Entity.php index 88381c1..e73b0ae 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -1801,14 +1801,9 @@ public static function getLatestPosition(Entity $entity) /** * Reorders node's siblings when it is moved to another position or ancestor. * - * This method must not be invoked directly, it's been made public - * to overcome visibility issue on the older PHP versions - * and is the subject to become private again in a future release. - * * @return void - * @internal */ - public function reorderSiblings() + private function reorderSiblings() { $position = $this->getPositionColumn(); From 2542aad9f5660289682e0a169b3acb271b2a5fef Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 3 May 2020 13:17:18 +0700 Subject: [PATCH 108/113] updated README.md --- README.md | 575 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 413 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index 5ee2d00..6b30bea 100644 --- a/README.md +++ b/README.md @@ -3,219 +3,470 @@ [![Latest Stable Version](https://poser.pugx.org/franzose/closure-table/v/stable.png)](https://packagist.org/packages/franzose/closure-table) [![Total Downloads](https://poser.pugx.org/franzose/closure-table/downloads.png)](https://packagist.org/packages/franzose/closure-table) -## Branches -1. L4 supports Laravel 4 -2. L5.1 supports Laravel < 5.2 -3. L5.3 supports Laravel 5.2-5.3 -4. L5.4 supports Laravel 5.4 -5. master is for any actual Laravel version, so be careful - -Hi, this is a database package for Laravel. It's intended to use when you need to operate hierarchical data in database. The package is an implementation of a well-known database design pattern called Closure Table. The package includes generators for models and migrations. +This is a database manipulation package for the Laravel 5.4+ framework. You may want to use it when you need to store and operate hierarchical data in your database. The package is an implementation of a well-known design pattern called [closure table](https://www.slideshare.net/billkarwin/models-for-hierarchical-data). However, in order to simplify and optimize SQL `SELECT` queries, it uses adjacency lists to query direct parent/child relationships. ## Installation -To install the package, put the following in your composer.json: - -```json -"require": { - "franzose/closure-table": "4.*" -} +It's strongly recommended to use [Composer](https://getcomposer.org) to install the package: +```bash +$ composer require franzose/closure-table ``` -And to `app/config/app.php`: +If you use Laravel 5.5+, the package's service provider is automatically registered for you thanks to the [package auto-discovery](https://laravel.com/docs/7.x/packages#package-discovery) feature. Otherwise, you have to manually add it to your `config/app.php`: ```php -'providers' => array( - // ... - 'Franzose\ClosureTable\ClosureTableServiceProvider', - ), -``` + [ + Franzose\ClosureTable\ClosureTableServiceProvider::class + ] +]; +``` +## Setup +In a basic scenario, you can simply run the following command: ```bash -php artisan closuretable:make --entity=page +$ php artisan closuretable:make Node ``` +Where `Node` is the name of the entity model. This is what you get from running the above:
+1. Two models in the `app` directory: `App\Node` and `App\NodeClosure`
+2. A new migration in the `database/migrations` directory + +As you can see, the command requires a single argument, name of the entity model. However, it accepts several options in order to provide some sort of customization: + Option | Alias | Meaning + ----------------| ------| ------- + namespace | ns | Custom namespace for generated models. Keep in mind that the given namespace will override model namespaces: `php artisan closuretable:make Foo\\Node --namespace=Qux --closure=Bar\\NodeTree` will generate `Qux\Node` and `Qux\NodeTree` models. + entity-table | et | Database table name for the entity model + closure | c | Class name for the closure model + closure-table | ct | Database table name for the closure model + models-path | mdl | Directory in which to put generated models + migrations-path | mgr | Directory in which to put generated migrations + use-innodb | i | This flag will tell the generator to set database engine to InnoDB. Useful only if you use MySQL + +## Requirements +You have to keep in mind that, by design of this package, the models/tables have a required minimum of attributes/columns: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Entity
Attribute/ColumnCustomized byMeaning
parent_idEntity::getParentIdColumn()ID of the node's immediate parent, simplifies queries for immediate parent/child nodes.
positionEntity::getPositionColumn()Node position, allows to order nodes of the same depth level
ClosureTable
Attribute/ColumnCustomized byMeaning
id
ancestorClosureTable::getAncestorColumn()Parent (self, immediate, distant) node ID
descendantClosureTable::getDescendantColumn()Child (self, immediate, distant) node ID
depthClosureTable::getDepthColumn()Current nesting level, 0+
+ +## Examples +Okay, let's see what you can do by using ClosureTable. + +### Scopes +Since ClosureTable 6, a lot of query scopes have become available in the Entity model: -All options of the command:
-1. `--namespace`, `-ns` _[optional]_: namespace for classes, set by `--entity` and `--closure` options, helps to avoid namespace duplication in those options
-2. `--entity`, `-e`: entity class name; if namespaced name is used, then the default closure class name will be prepended with that namespace
-3. `--entity-table`, `-et` _[optional]_: entity table name
-4. `--closure`, `-c` _[optional]_: closure class name
-5. `--closure-table` _[optional]_, `-ct`: closure table name
-6. `--models-path`, `-mdl` _[optional]_: custom models path
-7. `--migrations-path`, `-mgr` _[optional]_: custom migrations path
-8. `--use-innodb` and `-i` _[optional]_: InnoDB migrations have been made optional as well with new paramaters. Setting this will enable the InnoDB engine. - -That's almost all, folks! The ‘dummy’ stuff has just been created for you. You will need to add some fields to your entity migration because the created ‘dummy’ includes just **required** `id`, `parent_id`, `position`, and `real depth` columns:
+```php +ancestors() +ancestorsOf($id) +ancestorsWithSelf() +ancestorsWithSelfOf($id) +descendants() +descendantsOf($id) +descendantsWithSelf() +descendantsWithSelfOf($id) +childNode() +childNodeOf($id) +childAt(int $position) +childOf($id, int $position) +firstChild() +firstChildOf($id) +lastChild() +lastChildOf($id) +childrenRange(int $from, int $to = null) +childrenRangeOf($id, int $from, int $to = null) +sibling() +siblingOf($id) +siblings() +siblingsOf($id) +neighbors() +neighborsOf($id) +siblingAt(int $position) +siblingOfAt($id, int $position) +firstSibling() +firstSiblingOf($id) +lastSibling() +lastSiblingOf($id) +prevSibling() +prevSiblingOf($id) +prevSiblings() +prevSiblingsOf($id) +nextSibling() +nextSiblingOf($id) +nextSiblings() +nextSiblingsOf($id) +siblingsRange(int $from, int $to = null) +siblingsRangeOf($id, int $from, int $to = null) +``` -1. **`id`** is a regular autoincremented column
-2. **`parent_id`** column is used to simplify immediate ancestor querying and, for example, to simplify building the whole tree
-3. **`position`** column is used widely by the package to make entities sortable
-4. **`real depth`** column is also used to simplify queries and reduce their number +You can learn how to use query scopes from the [Laravel documentation](https://laravel.com/docs/7.x/eloquent#query-scopes). -By default, entity’s closure table includes the following columns:
-1. **Autoincremented identifier**
-2. **Ancestor column** points on a parent node
-3. **Descendant column** points on a child node
-4. **Depth column** shows a node depth in the tree +### Examples +In the examples, let's assume that we've set up a `Node` model which extends the `Franzose\ClosureTable\Models\Entity` model. -It is by closure table pattern design, so remember that you must not delete these four columns. +**Parent/Root** +```php + 1]), + new Node(['id' => 2]), + new Node(['id' => 3]), + new Node(['id' => 4, 'parent_id' => 1]) +]; + +foreach ($nodes as $node) { + $node->save(); +} -Remember that many things are made customizable, so see ‘Customization’ for more information. +Node::getRoots()->pluck('id')->toArray(); // [1, 2, 3] +Node::find(1)->isRoot(); // true +Node::find(1)->isParent(); // true +Node::find(4)->isRoot(); // false +Node::find(4)->isParent(); // false -## Time of coding -Once your models and their database tables are created, at last, you can start actually coding. Here I will show you ClosureTable's specific approaches. +// make node 4 a root at the fourth position (1 => 0, 2 => 1, 3 => 2, 4 => 3) +$node = Node::find(4)->makeRoot(3); +$node->isRoot(); // true +$node->position; // 3 -### Direct ancestor (parent) +Node::find(4)->moveTo(0, Node::find(2)); // same as Node::find(4)->moveTo(0, 2); +Node::find(2)->getChildren()->pluck('id')->toArray(); // [4] +``` +**Ancestors** ```php -$parent = Page::find(15)->getParent(); + 1]), + new Node(['id' => 2, 'parent_id' => 1]), + new Node(['id' => 3, 'parent_id' => 2]), + new Node(['id' => 4, 'parent_id' => 3]) +]; + +foreach ($nodes as $node) { + $node->save(); +} + +Node::find(4)->getAncestors()->pluck('id')->toArray(); // [1, 2, 3] +Node::find(4)->countAncestors(); // 3 +Node::find(4)->hasAncestors(); // true +Node::find(4)->ancestors()->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3]; +Node::find(4)->ancestorsWithSelf()->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3, 4]; +Node::ancestorsOf(4)->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3]; +Node::ancestorsWithSelfOf(4)->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3, 4]; ``` -### Ancestors +There are several methods that have been deprecated since ClosureTable 6: + + + + + + + + + + + + + +
DeprecatedRecommended
```php -$page = Page::find(15); -$ancestors = $page->getAncestors(); -$ancestors = $page->getAncestorsTree(); // Tree structure -$ancestors = $page->getAncestorsWhere('position', '=', 1); -$hasAncestors = $page->hasAncestors(); -$ancestorsNumber = $page->countAncestors(); +Node::find(4)->getAncestorsTree(); ``` - -### Direct descendants (children) - + ```php -$page = Page::find(15); -$children = $page->getChildren(); -$hasChildren = $page->hasChildren(); -$childrenNumber = $page->countChildren(); - -$newChild = new Page(array( - 'title' => 'The title', - 'excerpt' => 'The excerpt', - 'content' => 'The content of a child' -)); - -$newChild2 = new Page(array( - 'title' => 'The title', - 'excerpt' => 'The excerpt', - 'content' => 'The content of a child' -)); - -$page->addChild($newChild); - -//you can set child position -$page->addChild($newChild, 5); - -//you can get the child -$child = $page->addChild($newChild, null, true); - -$page->addChildren([$newChild, $newChild2]); +// use custom collection directly +Node::find(4)->getAncestors()->toTree(); +``` +
+```php +Node::find(4)->getAncestorsWhere('id', '>', 1); +``` + +```php +// use dedicated query scope +Node::find(4)->ancestors()->where('id', '>', 1)->get(); +``` +
-$page->getChildAt(5); -$page->getFirstChild(); -$page->getLastChild(); -$page->getChildrenRange(0, 2); +**Descendants** +```php + 1]), + new Node(['id' => 2, 'parent_id' => 1]), + new Node(['id' => 3, 'parent_id' => 2]), + new Node(['id' => 4, 'parent_id' => 3]) +]; + +foreach ($nodes as $node) { + $node->save(); +} -$page->removeChild(0); -$page->removeChild(0, true); //force delete -$page->removeChildren(0, 3); -$page->removeChildren(0, 3, true); //force delete +Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3, 4] +Node::find(1)->countDescendants(); // 3 +Node::find(1)->hasDescendants(); // true +Node::find(1)->descendants()->where('id', '<', 4)->get()->pluck('id')->toArray(); // [2, 3]; +Node::find(1)->descendantsWithSelf()->where('id', '<', 4)->get()->pluck('id')->toArray(); // [1, 2, 3]; +Node::descendantsOf(1)->where('id', '<', 4)->get()->pluck('id')->toArray(); // [2, 3]; +Node::descendantsWithSelfOf(1)->where('id', '<', 4)->get()->pluck('id')->toArray(); // [1, 2, 3]; ``` -### Descendants +There are several methods that have been deprecated since ClosureTable 6: + + + + + + + + + + + + + +
DeprecatedRecommended
```php -$page = Page::find(15); -$descendants = $page->getDescendants(); -$descendants = $page->getDescendantsWhere('position', '=', 1); -$descendantsTree = $page->getDescendantsTree(); -$hasDescendants = $page->hasDescendants(); -$descendantsNumber = $page->countDescendants(); +Node::find(4)->getDescendantsTree(); ``` + +```php +// use custom collection directly +Node::find(4)->getDescendants()->toTree(); +``` +
+```php +Node::find(4)->getDescendantsWhere('foo', '=', 'bar'); +``` + +```php +// use dedicated query scope +Node::find(4)->descendants()->where('foo', '=', 'bar')->get(); +``` +
-### Siblings - +**Children** ```php -$page = Page::find(15); -$first = $page->getFirstSibling(); //or $page->getSiblingAt(0); -$last = $page->getLastSibling(); -$atpos = $page->getSiblingAt(5); + 1]), + new Node(['id' => 2, 'parent_id' => 1]), + new Node(['id' => 3, 'parent_id' => 1]), + new Node(['id' => 4, 'parent_id' => 1]), + new Node(['id' => 5, 'parent_id' => 1]), + new Node(['id' => 6, 'parent_id' => 2]), + new Node(['id' => 7, 'parent_id' => 3]) +]; + +foreach ($nodes as $node) { + $node->save(); +} -$prevOne = $page->getPrevSibling(); -$prevAll = $page->getPrevSiblings(); -$hasPrevs = $page->hasPrevSiblings(); -$prevsNumber = $page->countPrevSiblings(); +Node::find(1)->getChildren()->pluck('id')->toArray(); // [2, 3, 4, 5] +Node::find(1)->countChildren(); // 3 +Node::find(1)->hasChildren(); // true -$nextOne = $page->getNextSibling(); -$nextAll = $page->getNextSiblings(); -$hasNext = $page->hasNextSiblings(); -$nextNumber = $page->countNextSiblings(); +// get child at the second position (positions start from zero) +Node::find(1)->getChildAt(1)->id; // 3 -//in both directions -$hasSiblings = $page->hasSiblings(); -$siblingsNumber = $page->countSiblings(); +Node::find(1)->getChildrenRange(1)->pluck('id')->toArray(); // [3, 4, 5] +Node::find(1)->getChildrenRange(0, 2)->pluck('id')->toArray(); // [2, 3, 4] -$sibligns = $page->getSiblingsRange(0, 2); +Node::find(1)->getFirstChild()->id; // 2 +Node::find(1)->getLastChild()->id; // 5 -$page->addSibling(new Page); -$page->addSibling(new Page, 3); //third position +Node::find(6)->countChildren(); // 0 +Node::find(6)->hasChildren(); // false -//add and get the sibling -$sibling = $page->addSibling(new Page, null, true); +Node::find(6)->addChild(new Node(['id' => 7])); -$page->addSiblings([new Page, new Page]); -$page->addSiblings([new Page, new Page], 5); //insert from fifth position -``` +Node::find(1)->addChildren([new Node(['id' => 8]), new Node(['id' => 9])], 2); +Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1, 8 => 2, 9 => 3, 4 => 4, 5 => 5] -### Roots (entities that have no ancestors) +// remove child by its position +Node::find(1)->removeChild(2); +Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1, 9 => 2, 4 => 3, 5 => 4] -```php -$roots = Page::getRoots(); -$isRoot = Page::find(23)->isRoot(); -Page::find(11)->makeRoot(0); //at the moment we always have to set a position when making node a root +Node::find(1)->removeChildren(2, 4); +Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1] ``` -### Entire tree - +**Siblings** ```php -$tree = Page::getTree(); -$treeByCondition = Page::getTreeWhere('position', '>=', 1); -``` + 1]), + new Node(['id' => 2, 'parent_id' => 1]), + new Node(['id' => 3, 'parent_id' => 1]), + new Node(['id' => 4, 'parent_id' => 1]), + new Node(['id' => 5, 'parent_id' => 1]), + new Node(['id' => 6, 'parent_id' => 1]), + new Node(['id' => 7, 'parent_id' => 1]) +]; + +foreach ($nodes as $node) { + $node->save(); +} -You deal with the collection, thus you can control its items as you usually do. Descendants? They are already loaded. +Node::find(7)->getFirstSibling()->id; // 2 +Node::find(7)->getSiblingAt(0); // 2 +Node::find(2)->getLastSibling(); // 7 +Node::find(7)->getPrevSibling()->id; // 6 +Node::find(7)->getPrevSiblings()->pluck('id')->toArray(); // [2, 3, 4, 5, 6] +Node::find(7)->countPrevSiblings(); // 5 +Node::find(7)->hasPrevSiblings(); // true + +Node::find(2)->getNextSibling()->id; // 3 +Node::find(2)->getNextSiblings()->pluck('id')->toArray(); // [3, 4, 5, 6, 7] +Node::find(2)->countNextSiblings(); // 5 +Node::find(2)->hasNextSiblings(); // true + +Node::find(3)->getSiblings()->pluck('id')->toArray(); // [2, 4, 5, 6, 7] +Node::find(3)->getNeighbors()->pluck('id')->toArray(); // [2, 4] +Node::find(3)->countSiblings(); // 5 +Node::find(3)->hasSiblings(); // true + +Node::find(2)->getSiblingsRange(2)->pluck('id')->toArray(); // [4, 5, 6, 7] +Node::find(2)->getSiblingsRange(2, 4)->pluck('id')->toArray(); // [4, 5, 6] + +Node::find(4)->addSibling(new Node(['id' => 8])); +Node::find(4)->getNextSiblings()->pluck('id')->toArray(); // [5, 6, 7, 8] + +Node::find(4)->addSibling(new Node(['id' => 9]), 1); +Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); +// [2 => 0, 9 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7] + +Node::find(8)->addSiblings([new Node(['id' => 10]), new Node(['id' => 11])]); +Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); +// [2 => 0, 9 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7, 10 => 8, 11 => 9] + +Node::find(2)->addSiblings([new Node(['id' => 12]), new Node(['id' => 13])], 3); +Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); +// [2 => 0, 9 => 1, 3 => 2, 12 => 3, 13 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 10 => 10, 11 => 11] +``` +**Tree** ```php -$tree = Page::getTree(); -$page = $tree->find(15); -$children = $page->getChildren(); -$child = $page->getChildAt(3); -$grandchildren = $page->getChildAt(3)->getChildren(); //and so on + 1, + 'children' => [ + [ + 'id' => 2, + 'children' => [ + [ + 'id' => 3, + 'children' => [ + [ + 'id' => 4, + 'children' => [ + [ + 'id' => 5, + 'children' => [ + [ + 'id' => 6, + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] +]); + +Node::find(4)->deleteSubtree(); +Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3, 4] + +Node::find(4)->deleteSubtree(true); +Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3] ``` -### Moving - +There are several methods that have been deprecated since ClosureTable 6: + + + + + + + + + + + + + + + + + +
DeprecatedRecommended
```php -$page = Page::find(25); -$page->moveTo(0, Page::find(14)); -$page->moveTo(0, 14); +Node::getTree(); ``` - -### Deleting subtree -If you don't use foreign keys for some reason, you can delete subtree manually. This will delete the page and all its descendants: - + +No alternative provided +
```php -$page = Page::find(34); -$page->deleteSubtree(); -$page->deleteSubtree(true); //with subtree ancestor -$page->deleteSubtree(false, true); //without subtree ancestor and force delete +Node::getTreeByQuery(...); ``` - -## Customization -You can customize default things in your own classes created by the ClosureTable `artisan` command:
-1. **Entity table name**: change `protected $table` property
-2. **Closure table name**: do the same in your `ClosureTable` (e.g. `PageClosure`)
-3. **Entity's `parent_id`, `position`, and `real depth` column names**: change return values of `getParentIdColumn()`, `getPositionColumn()`, and `getRealDepthColumn()` respectively
-4. **Closure table's `ancestor`, `descendant`, and `depth` columns names**: change return values of `getAncestorColumn()`, `getDescendantColumn()`, and `getDepthColumn()` respectively. +
+No alternative provided +
+```php +Node::getTreeWhere('foo', '=', 'bar'); +``` + +```php +Node::where('foo', '=', 'bar')->get()->toTree(); +``` +
From b23ce0a7e92052946ae966e60ab2d71e183ce1b6 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Sun, 3 May 2020 13:26:35 +0700 Subject: [PATCH 109/113] made some fixes to README.md --- README.md | 131 +++++++++++------------------------------------------- 1 file changed, 25 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 6b30bea..701560a 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ As you can see, the command requires a single argument, name of the entity model You have to keep in mind that, by design of this package, the models/tables have a required minimum of attributes/columns: - + @@ -55,16 +55,16 @@ You have to keep in mind that, by design of this package, the models/tables have - + - + - + @@ -78,17 +78,17 @@ You have to keep in mind that, by design of this package, the models/tables have - + - + - +
EntityEntity
Attribute/Column
parent_idEntity::getParentIdColumn()Entity::getParentIdColumn() ID of the node's immediate parent, simplifies queries for immediate parent/child nodes.
positionEntity::getPositionColumn()Entity::getPositionColumn() Node position, allows to order nodes of the same depth level
ClosureTableClosureTable
Attribute/Column
ancestorClosureTable::getAncestorColumn()ClosureTable::getAncestorColumn() Parent (self, immediate, distant) node ID
descendantClosureTable::getDescendantColumn()ClosureTable::getDescendantColumn() Child (self, immediate, distant) node ID
depthClosureTable::getDepthColumn()ClosureTable::getDepthColumn() Current nesting level, 0+
@@ -201,38 +201,13 @@ Node::ancestorsWithSelfOf(4)->where('id', '>', 1)->get()->pluck('id')->toArray() There are several methods that have been deprecated since ClosureTable 6: - - - - - - - - - - - - - -
DeprecatedRecommended
-```php -Node::find(4)->getAncestorsTree(); -``` - -```php -// use custom collection directly -Node::find(4)->getAncestors()->toTree(); -``` -
-```php -Node::find(4)->getAncestorsWhere('id', '>', 1); -``` - -```php -// use dedicated query scope -Node::find(4)->ancestors()->where('id', '>', 1)->get(); +```diff +-Node::find(4)->getAncestorsTree(); ++Node::find(4)->getAncestors()->toTree(); + +-Node::find(4)->getAncestorsWhere('id', '>', 1); ++Node::find(4)->ancestors()->where('id', '>', 1)->get(); ``` -
**Descendants** ```php @@ -259,38 +234,13 @@ Node::descendantsWithSelfOf(1)->where('id', '<', 4)->get()->pluck('id')->toArray There are several methods that have been deprecated since ClosureTable 6: - - - - - - - - - - - - - -
DeprecatedRecommended
-```php -Node::find(4)->getDescendantsTree(); -``` - -```php -// use custom collection directly -Node::find(4)->getDescendants()->toTree(); -``` -
-```php -Node::find(4)->getDescendantsWhere('foo', '=', 'bar'); -``` - -```php -// use dedicated query scope -Node::find(4)->descendants()->where('foo', '=', 'bar')->get(); +```diff +-Node::find(4)->getDescendantsTree(); ++Node::find(4)->getDescendants()->toTree(); + +-Node::find(4)->getDescendantsWhere('foo', '=', 'bar'); ++Node::find(4)->descendants()->where('foo', '=', 'bar')->get(); ``` -
**Children** ```php @@ -432,41 +382,10 @@ Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3] ``` There are several methods that have been deprecated since ClosureTable 6: - - - - - - - - - - - - - - - - - -
DeprecatedRecommended
-```php -Node::getTree(); -``` - -No alternative provided -
-```php -Node::getTreeByQuery(...); -``` - -No alternative provided -
-```php -Node::getTreeWhere('foo', '=', 'bar'); -``` - -```php -Node::where('foo', '=', 'bar')->get()->toTree(); + +```diff +-Node::getTree(); +-Node::getTreeByQuery(...); +-Node::getTreeWhere('foo', '=', 'bar'); ++Node::where('foo', '=', 'bar')->get()->toTree(); ``` -
From e076ac7f28c340715dbcf2b09db8335a554239b1 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 4 May 2020 10:05:02 +0700 Subject: [PATCH 110/113] removed outdated documentation --- docs/Makefile | 177 ---------------------------- docs/conf.py | 258 ----------------------------------------- docs/create.rst | 43 ------- docs/customization.rst | 12 -- docs/index.rst | 33 ------ docs/installation.rst | 23 ---- docs/make.bat | 242 -------------------------------------- docs/usage.rst | 166 -------------------------- 8 files changed, 954 deletions(-) delete mode 100644 docs/Makefile delete mode 100644 docs/conf.py delete mode 100644 docs/create.rst delete mode 100644 docs/customization.rst delete mode 100644 docs/index.rst delete mode 100644 docs/installation.rst delete mode 100644 docs/make.bat delete mode 100644 docs/usage.rst diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 92d3498..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ClosureTable.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ClosureTable.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/ClosureTable" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ClosureTable" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 15c0ee0..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,258 +0,0 @@ -# -*- coding: utf-8 -*- -# -# ClosureTable documentation build configuration file, created by -# sphinx-quickstart on Sun Apr 06 11:52:08 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'ClosureTable' -copyright = u'2014, Jan Iwanow' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '3.1.0' -# The full version, including alpha/beta/rc tags. -release = '3.1.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'ClosureTabledoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'ClosureTable.tex', u'ClosureTable Documentation', - u'Jan Iwanow', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'closuretable', u'ClosureTable Documentation', - [u'Jan Iwanow'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'ClosureTable', u'ClosureTable Documentation', - u'Jan Iwanow', 'ClosureTable', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/create.rst b/docs/create.rst deleted file mode 100644 index 73a9ac8..0000000 --- a/docs/create.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. index:: - single: Setup your ClosureTable - -Setup your ClosureTable -======================= - -Create models and migrations ----------------------------- - -For example, let's assume you're working on pages. You can just use an ``artisan`` command to create models and migrations automatically without preparing all the stuff by hand. Open terminal and put the following: - -.. code-block:: bash - - php artisan closuretable:make --entity=page - -All options of the command: - -1. ``--namespace``, ``-ns`` *[optional]*: namespace for classes, set by ``--entity`` and ``--closure`` options, helps to avoid namespace duplication in those options -2. ``--entity``, ``-e``: entity class name; if namespaced name is used, then the default closure class name will be prepended with that namespace -3. ``--entity-table``, ``-et`` *[optional]*: entity table name -4. ``--closure``, ``-c`` *[optional]*: closure class name -5. ``--closure-table``, ``-ct`` *[optional]*: closure table name -6. ``--models-path``, ``-mdl`` *[optional]*: custom models path -7. ``--migrations-path``, ``-mgr`` *[optional]*: custom migrations path -8. ``--use-innodb`` and ``-i`` *[optional]*: InnoDB migrations have been made optional as well with new paramaters. Setting this will enable the InnoDB engine. - -That's almost all, folks! The ‘dummy’ stuff has just been created for you. You will need to add some fields to your entity migration because the created ‘dummy’ includes just **required** ``id``, ``parent_id``, ``position``, and ``real depth`` columns: - -1. ``id`` is a regular autoincremented column -2. ``parent_id`` column is used to simplify immediate ancestor querying and, for example, to simplify building the whole tree -3. ``position`` column is used widely by the package to make entities sortable -4. ``real depth`` column is also used to simplify queries and reduce their number - -By default, entity’s closure table includes the following columns: - -1. **Autoincremented identifier** -2. **Ancestor column** points on a parent node -3. **Descendant column** points on a child node -4. **Depth column** shows a node depth in the tree - -It is by closure table pattern design, so remember that you must not delete these four columns. - -Remember that many things are made customizable, so see :doc:`Customization` for more information. \ No newline at end of file diff --git a/docs/customization.rst b/docs/customization.rst deleted file mode 100644 index 2e9fc4a..0000000 --- a/docs/customization.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. index:: - single: Customization - -Customization -============= - -You can customize the default things in your classes created by the ClosureTable ``artisan`` command: - -1. **Entity table name**: change ``protected $table`` property -2. **Closure table name**: do the same in your ``ClosureTable`` (e.g. ``PageClosure``) -3. **Entity's ``parent_id``, ``position``, and ``real depth`` column names**: change return values of ``getParentIdColumn()``, ``getPositionColumn()``, and ``getRealDepthColumn()`` respectively -4. **Closure table's ``ancestor``, ``descendant``, and ``depth`` columns names**: change return values of ``getAncestorColumn()``, ``getDescendantColumn()``, and ``getDepthColumn()`` respectively. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 3052178..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,33 +0,0 @@ -ClosureTable Documentation -========================== - -## NOTE: Master branch is now for Laravel 5.
If you use Laravel 4, please see L4 branch! - -Hi, this is a database package for Laravel. It's intended to use when you need to operate hierarchical data in database. The package is an implementation of a well-known database design pattern called Closure Table. The package includes generators for models and migrations. - -.. image:: https://travis-ci.org/franzose/ClosureTable.png - :target: https://travis-ci.org/franzose/ClosureTable - -.. image:: https://poser.pugx.org/franzose/closure-table/v/stable.png - :target: https://packagist.org/packages/franzose/closure-table - -.. image:: https://poser.pugx.org/franzose/closure-table/downloads.png - :target: https://packagist.org/packages/franzose/closure-table - -Contents -======== - -.. toctree:: - :maxdepth: 2 - - installation - create - usage - customization - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index bffba6c..0000000 --- a/docs/installation.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. index:: - single: Installation - -Installation -============ - -To install the package, put the following in your composer.json: - -.. code-block:: json - - "require": { - "franzose/closure-table": "4.*" - } - - -And to ``app/config/app.php``: - -.. code-block:: php - - 'providers' => array( - // ... - 'Franzose\ClosureTable\ClosureTableServiceProvider', - ), \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 65760cb..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,242 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ClosureTable.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ClosureTable.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index e4b1127..0000000 --- a/docs/usage.rst +++ /dev/null @@ -1,166 +0,0 @@ -.. index:: - single: Usage - -Usage -===== - -Time of coding --------------- - -Once your models and their database tables are created, at last, you can start actually coding. Here I will show you ClosureTable's specific approaches. - -Direct ancestor (parent) ------------------------- - -.. code-block:: php - - $parent = Page::find(15)->getParent(); - -Ancestors ---------- - -.. code-block:: php - - $page = Page::find(15); - $ancestors = $page->getAncestors(); - $ancestors = $page->getAncestorsWhere('position', '=', 1); - $hasAncestors = $page->hasAncestors(); - $ancestorsNumber = $page->countAncestors(); - -Direct descendants (children) ------------------------------ - -.. code-block:: php - - $page = Page::find(15); - $children = $page->getChildren(); - $hasChildren = $page->hasChildren(); - $childrenNumber = $page->countChildren(); - - $newChild = new Page(array( - 'title' => 'The title', - 'excerpt' => 'The excerpt', - 'content' => 'The content of a child' - )); - - $newChild2 = new Page(array( - 'title' => 'The title', - 'excerpt' => 'The excerpt', - 'content' => 'The content of a child' - )); - - $page->addChild($newChild); - - //you can set child position - $page->addChild($newChild, 5); - - //you can get the child - $child = $page->addChild($newChild, null, true); - - $page->addChildren([$newChild, $newChild2]); - - $page->getChildAt(5); - $page->getFirstChild(); - $page->getLastChild(); - $page->getChildrenRange(0, 2); - - $page->removeChild(0); - $page->removeChild(0, true); //force delete - $page->removeChildren(0, 3); - $page->removeChildren(0, 3, true); //force delete - -Descendants ------------ - -.. code-block:: php - - $page = Page::find(15); - $descendants = $page->getDescendants(); - $descendants = $page->getDescendantsWhere('position', '=', 1); - $descendantsTree = $page->getDescendantsTree(); - $hasDescendants = $page->hasDescendants(); - $descendantsNumber = $page->countDescendants(); - -Siblings --------- - -.. code-block:: php - - $page = Page::find(15); - $first = $page->getFirstSibling(); //or $page->getSiblingAt(0); - $last = $page->getLastSibling(); - $atpos = $page->getSiblingAt(5); - - $prevOne = $page->getPrevSibling(); - $prevAll = $page->getPrevSiblings(); - $hasPrevs = $page->hasPrevSiblings(); - $prevsNumber = $page->countPrevSiblings(); - - $nextOne = $page->getNextSibling(); - $nextAll = $page->getNextSiblings(); - $hasNext = $page->hasNextSiblings(); - $nextNumber = $page->countNextSiblings(); - - //in both directions - $hasSiblings = $page->hasSiblings(); - $siblingsNumber = $page->countSiblings(); - - $sibligns = $page->getSiblingsRange(0, 2); - - $page->addSibling(new Page); - $page->addSibling(new Page, 3); //third position - - //add and get the sibling - $sibling = $page->addSibling(new Page, null, true); - - $page->addSiblings([new Page, new Page]); - $page->addSiblings([new Page, new Page], 5); //insert from fifth position - -Roots (entities that have no ancestors) ---------------------------------------- - -.. code-block:: php - - $roots = Page::getRoots(); - $isRoot = Page::find(23)->isRoot(); - Page::find(11)->makeRoot(); - -Entire tree ------------ - -.. code-block:: php - - $tree = Page::getTree(); - $treeByCondition = Page::getTreeWhere('position', '>=', 1); - -You deal with the collection, thus you can control its items as you usually do. Descendants? They are already loaded. - -.. code-block:: php - - $tree = Page::getTree(); - $page = $tree->find(15); - $children = $page->getChildren(); - $child = $page->getChildAt(3); - $grandchildren = $page->getChildAt(3)->getChildren(); //and so on - -Moving ------- - -.. code-block:: php - - $page = Page::find(25); - $page->moveTo(0, Page::find(14)); - $page->moveTo(0, 14); - -Deleting subtree ----------------- - -If you don't use foreign keys for some reason, you can delete subtree manually. This will delete the page and all its descendants: - -.. code-block:: php - - $page = Page::find(34); - $page->deleteSubtree(); - $page->deleteSubtree(true); //with subtree ancestor - $page->deleteSubtree(false, true); //without subtree ancestor and force delete - From 450722a5346d1a36fe55f8c6f97210f7563aff35 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 4 May 2020 10:15:16 +0700 Subject: [PATCH 111/113] updated README.md --- README.md | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 701560a..129959a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,19 @@ This is a database manipulation package for the Laravel 5.4+ framework. You may want to use it when you need to store and operate hierarchical data in your database. The package is an implementation of a well-known design pattern called [closure table](https://www.slideshare.net/billkarwin/models-for-hierarchical-data). However, in order to simplify and optimize SQL `SELECT` queries, it uses adjacency lists to query direct parent/child relationships. +Contents: +- [Installation](#installation) +- [Setup](#setup) +- [Requirements](#requirements) +- Examples → [List of Scopes](#scopes) +- Examples → [Parent/Root](#parentroot) +- Examples → [Ancestors](#ancestors) +- Examples → [Descendants](#descendants) +- Examples → [Children](#children) +- Examples → [Siblings](#siblings) +- Examples → [Tree](#tree) + + ## Installation It's strongly recommended to use [Composer](https://getcomposer.org) to install the package: ```bash @@ -94,7 +107,7 @@ You have to keep in mind that, by design of this package, the models/tables have ## Examples -Okay, let's see what you can do by using ClosureTable. +In the examples, let's assume that we've set up a `Node` model which extends the `Franzose\ClosureTable\Models\Entity` model. ### Scopes Since ClosureTable 6, a lot of query scopes have become available in the Entity model: @@ -144,10 +157,7 @@ siblingsRangeOf($id, int $from, int $to = null) You can learn how to use query scopes from the [Laravel documentation](https://laravel.com/docs/7.x/eloquent#query-scopes). -### Examples -In the examples, let's assume that we've set up a `Node` model which extends the `Franzose\ClosureTable\Models\Entity` model. - -**Parent/Root** +### Parent/Root ```php moveTo(0, Node::find(2)); // same as Node::find(4)->moveTo(0, 2); Node::find(2)->getChildren()->pluck('id')->toArray(); // [4] ``` -**Ancestors** +### Ancestors ```php ancestors()->where('id', '>', 1)->get(); ``` -**Descendants** +### Descendants ```php descendants()->where('foo', '=', 'bar')->get(); ``` -**Children** +### Children ```php removeChildren(2, 4); Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1] ``` -**Siblings** +### Siblings ```php getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 9 => 1, 3 => 2, 12 => 3, 13 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 10 => 10, 11 => 11] ``` -**Tree** +### Tree ```php Date: Mon, 4 May 2020 10:20:37 +0700 Subject: [PATCH 112/113] added an example of the docker-compose.yaml file --- docker-compose.yaml.example | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docker-compose.yaml.example diff --git a/docker-compose.yaml.example b/docker-compose.yaml.example new file mode 100644 index 0000000..81f84d1 --- /dev/null +++ b/docker-compose.yaml.example @@ -0,0 +1,30 @@ +version: '3' +services: + mysql: + image: mariadb:10.3 + volumes: + - ./docker/mysql:/var/lib/mysql + ports: + - 5506:3306 + environment: + MYSQL_DATABASE: closuretabletest + MYSQL_USER: user + MYSQL_PASSWORD: userpass + MYSQL_ROOT_PASSWORD: root + + postgresql: + image: postgres:latest + volumes: + - ./docker/postgresql:/var/lib/postgresql/data + ports: + - 5507:5432 + environment: + POSTGRES_DB: closuretabletest + POSTGRES_USER: user + POSTGRES_PASSWORD: userpass + php: + image: php:5.6-cli + tty: true + command: /bin/sh + volumes: + - ./:/usr/src/myapp From 11ba82358c511d4c0bc53b6b260a6712edc57a13 Mon Sep 17 00:00:00 2001 From: Jan Iwanow Date: Mon, 4 May 2020 10:42:57 +0700 Subject: [PATCH 113/113] added examples of using custom collection methods --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index 129959a..e015c4d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Contents: - Examples → [Children](#children) - Examples → [Siblings](#siblings) - Examples → [Tree](#tree) +- Examples → [Collection Methods](#collection-methods) ## Installation @@ -399,3 +400,40 @@ There are several methods that have been deprecated since ClosureTable 6: -Node::getTreeWhere('foo', '=', 'bar'); +Node::where('foo', '=', 'bar')->get()->toTree(); ``` + +### Collection methods +This library uses an extended collection class which offers some convenient methods: + +```php + 1, + 'children' => [ + ['id' => 2], + ['id' => 3], + ['id' => 4], + ['id' => 5], + [ + 'id' => 6, + 'children' => [ + ['id' => 7], + ['id' => 8], + ] + ], + ] +]); + +/** @var Franzose\ClosureTable\Extensions\Collection $children */ +$children = Node::find(1)->getChildren(); +$children->getChildAt(1)->id; // 3 +$children->getFirstChild()->id; // 2 +$children->getLastChild()->id; // 6 +$children->getRange(1)->pluck('id')->toArray(); // [3, 4, 5, 6] +$children->getRange(1, 3)->pluck('id')->toArray(); // [3, 4, 5] +$children->getNeighbors(2)->pluck('id')->toArray(); // [3, 5] +$children->getPrevSiblings(2)->pluck('id')->toArray(); // [2, 3] +$children->getNextSiblings(2)->pluck('id')->toArray(); // [5, 6] +$children->getChildrenOf(4)->pluck('id')->toArray(); // [7, 8] +$children->hasChildren(4); // true +$tree = $children->toTree(); +``` \ No newline at end of file