diff --git a/.gitattributes b/.gitattributes index 18e14aa..9c6a07f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ /tests export-ignore +/workbench export-ignore +/.github export-ignore diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml new file mode 100644 index 0000000..ef23978 --- /dev/null +++ b/.github/workflows/coding-standards.yml @@ -0,0 +1,20 @@ +name: "Coding Standards" + +on: + pull_request: + branches: + - "*.x" + - "main" + push: + branches: + - "*.x" + - "main" + +jobs: + coding-standards: + name: "Coding Standards" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.3.0" + with: + php-version: '8.2' + composer-options: '--prefer-dist --ignore-platform-req=php' + diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..f2cdaae --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,83 @@ +name: "Continuous Integration" + +on: + pull_request: + branches: + - "*.x" + - "main" + push: + branches: + - "*.x" + - "main" + +jobs: + phpunit: + name: "PHPUnit" + runs-on: "ubuntu-20.04" + + strategy: + fail-fast: false + matrix: + php-version: + - "8.2" + - "8.3" + dependencies: + - "highest" + - "lowest" + optional-dependencies: + - true + - false + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + coverage: "pcov" + ini-values: "zend.assertions=1" + extensions: "pdo_mysql" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + with: + dependency-versions: "${{ matrix.dependencies }}" + composer-options: "--prefer-dist" + + - name: "Show Composer packages" + run: "composer show" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "codecov/codecov-action@v4" + with: + name: "phpunit-${{ matrix.php-version }}-${{ matrix.dependencies }}-${{ matrix.dbal-version }}.coverage" + files: "./coverage.xml" + + upload_coverage: + name: "Upload coverage to Codecov" + runs-on: "ubuntu-20.04" + needs: + - "phpunit" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Download coverage files" + uses: "actions/download-artifact@v4" + with: + path: "reports" + + - name: "Upload to Codecov" + uses: "codecov/codecov-action@v2" + with: + directory: "reports" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..9be77f6 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,35 @@ +name: "Static Analysis" + +on: + pull_request: + push: + +jobs: + static-analysis-phpstan: + name: "Static Analysis with PHPStan" + runs-on: "ubuntu-22.04" + + strategy: + matrix: + php-version: + - "8.2" + - "8.3" + + steps: + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + extensions: "pdo_sqlite" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "${{ matrix.dependencies }}" + + - name: "Run a static analysis with phpstan/phpstan" + run: "vendor/bin/phpstan analyse src --level 1" diff --git a/.gitignore b/.gitignore index de41239..df695e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor +/coverage composer.phar composer.lock .DS_Store @@ -6,3 +7,6 @@ composer.lock .phpunit.result.cache .idea laravel-doctrine-orm.iml +.phpcs-cache +.phpunit.cache + diff --git a/.php_cs b/.php_cs deleted file mode 100644 index c2489d1..0000000 --- a/.php_cs +++ /dev/null @@ -1,45 +0,0 @@ -exclude('vendor') - ->in(__DIR__); - -return Symfony\CS\Config\Config::create() - ->setUsingCache(true) - ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) - ->fixers(array( - 'psr4', - 'encoding', - 'short_tag', - 'blankline_after_open_tag', - 'namespace_no_leading_whitespace', - 'no_blank_lines_after_class_opening', - 'single_array_no_trailing_comma', - 'no_empty_lines_after_phpdocs', - 'concat_with_spaces', - 'eof_ending', - 'ordered_use', - 'extra_empty_lines', - 'single_line_after_imports', - 'trailing_spaces', - 'remove_lines_between_uses', - 'return', - 'indentation', - 'linefeed', - 'braces', - 'visibility', - 'unused_use', - 'whitespacy_lines', - 'php_closing_tag', - 'phpdoc_order', - 'phpdoc_params', - 'phpdoc_trim', - 'phpdoc_scalar', - 'short_array_syntax', - 'align_double_arrow', - 'align_equals', - 'lowercase_constants', - 'lowercase_keywords', - 'multiple_use', - 'line_after_namespace', - ))->finder($finder); diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..9712e40 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2254aca..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: php - -php: - - 7.3 - - 7.4 - -before_script: - - travis_retry composer self-update - - travis_retry composer install --prefer-dist --no-interaction - -sudo: false - -script: phpunit - -matrix: - fast_finish: true diff --git a/LICENSE b/LICENSE index 98d0d48..dc68a79 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) Copyright (c) 2015 Patrick Brouwers +Copyright (c) 2024 Tom H Anderson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 640b1df..8026aa7 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,41 @@ -# Laravel Doctrine Extensions +

+ + +

- +Laravel Doctrine Extensions +=========================== -[![GitHub release](https://img.shields.io/github/release/laravel-doctrine/extensions.svg?style=flat-square)](https://packagist.org/packages/laravel-doctrine/extensions) -[![Travis](https://img.shields.io/travis/laravel-doctrine/extensions.svg?style=flat-square)](https://travis-ci.org/laravel-doctrine/extensions) -[![StyleCI](https://styleci.io/repos/39036062/shield)](https://styleci.io/repos/39036062) -[![Scrutinizer](https://img.shields.io/scrutinizer/g/laravel-doctrine/extensions.svg?style=flat-square)](https://github.com/laravel-doctrine/extensions) -[![Packagist](https://img.shields.io/packagist/dm/laravel-doctrine/extensions.svg?style=flat-square)](https://packagist.org/packages/laravel-doctrine/extensions) -[![Packagist](https://img.shields.io/packagist/dt/laravel-doctrine/extensions.svg?style=flat-square)](https://packagist.org/packages/laravel-doctrine/extensions) +[![Build Status](https://github.com/laravel-doctrine/extensions/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/laravel-doctrine/extensions/actions/workflows/continuous-integration.yml?query=branch%3A2.0.x) +[![Code coverage](https://codecov.io/gh/laravel-doctrine/extensions/branch/2.0.x/graph/badge.svg?token=hbyGgWHabZ)](https://codecov.io/gh/laravel-doctrine/extensions) +[![PHPStan](https://img.shields.io/badge/PHPStan-level%201-brightgreen.svg)](https://img.shields.io/badge/PHPStan-level%201-brightgreen.svg) +[![Documentation](https://readthedocs.org/projects/laravel-doctrine-extensions/badge/?version=latest)](https://laravel-doctrine-extensions.readthedocs.io/en/latest/?badge=latest) +[![Packagist downloads](https://img.shields.io/packagist/dm/laravel-doctrine/extensions.svg?style=flat-square)](https://packagist.org/packages/laravel-doctrine/extensions) -*Behavioral and Query/Type Extensions for Laravel Doctrine* +Behavioral and Query/Type Extensions for Laravel Doctrine + +Installation +------------ + +Via composer: + +```bash +composer require laravel-doctrine/extensions +``` + + +Documentation +------------- + +Full documentation at https://laravel-doctrine-extensions.readthedocs.io or in the [docs directory](https://github.com/laravel-doctrine/extensions/tree/2.0.x/docs). + + + +Provides +-------- + +### Gedmo Extensions -**Gedmo Extensions:** * Blameable * IpTraceable * Loggable @@ -23,13 +47,14 @@ * Tree * Uploadable -**Beberlei Extensions:** +### Beberlei Extensions + * Custom datetime functions * Custom numeric functions * Custom string functions -### Installation - -[See the Laravel Doctrine website for instructions](http://www.laraveldoctrine.org/docs/1.0/extensions/installation) +License +------- +See [LICENSE](https://github.com/laravel-doctrine/extensions/blob/master/LICENSE). diff --git a/composer.json b/composer.json index 4416655..7fbe135 100644 --- a/composer.json +++ b/composer.json @@ -1,49 +1,89 @@ { - "name": "laravel-doctrine/extensions", - "description": "Doctrine extensions for Doctrine 2 and Laravel 5", - "license": "MIT", - "keywords": [ - "doctrine", - "gedmo", - "beberlei", - "laravel", - "orm", - "data mapper", - "database" - ], - "authors": [ - { - "name": "Patrick Brouwers", - "email": "patrick@maatwebsite.nl" + "name": "laravel-doctrine/extensions", + "description": "Doctrine extensions for Laravel", + "type": "library", + "license": "MIT", + "keywords": [ + "doctrine", + "gedmo", + "beberlei", + "laravel", + "orm" + ], + "authors": [ + { + "name": "Patrick Brouwers", + "email": "patrick@maatwebsite.nl" + }, + { + "name": "Tom H Anderson", + "email": "tom.h.anderson@gmail.com" + } + ], + "require": { + "php": "^8.2", + "illuminate/auth": "^10.0 || ^11.0 || ^12.0", + "illuminate/config": "^10.0 || ^11.0 || ^12.0", + "illuminate/contracts": "^10.0 || ^11.0 || ^12.0", + "illuminate/support": "^10.0 || ^11.0 || ^12.0", + "illuminate/http": "^10.0 || ^11.0 || ^12.0", + "laravel-doctrine/orm": "^3.1.0" + }, + "require-dev": { + "beberlei/doctrineextensions": "^1.5", + "doctrine/coding-standard": "^12.0", + "gedmo/doctrine-extensions": "^3.17", + "laravel/framework": "^10.0 || ^11.0 || ^12.0", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^1.12 || ^2.1", + "phpunit/phpunit": "^11.4", + "orchestra/testbench": "^9.5 || ^10.0" + }, + "suggest": { + "gedmo/doctrine-extensions": "Behavioral Doctrine2 extensions", + "beberlei/doctrineextensions": "Query/Type Doctrine2 extensions (^1.0)" + }, + "autoload": { + "psr-4": { + "LaravelDoctrine\\Extensions\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaravelDoctrineTest\\Extensions\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/", + "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" + } + }, + "scripts": { + "test": [ + "vendor/bin/parallel-lint src tests", + "vendor/bin/phpcs", + "vendor/bin/phpunit", + "vendor/bin/phpstan analyze src --level 1" + ], + "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage", + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve --ansi" + ], + "lint": [ + "@php vendor/bin/phpstan analyse --verbose --ansi" + ] + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } - ], - "require": { - "php": "^7.3", - "illuminate/auth": "^8.0", - "illuminate/config": "^8.0", - "illuminate/contracts": "^8.0", - "illuminate/support": "^8.0", - "illuminate/http": "^8.0", - "laravel-doctrine/orm": "^1.7" - }, - "suggest": { - "gedmo/doctrine-extensions": "Behavioral Doctrine2 extensions (^2.4)", - "beberlei/doctrineextensions": "Query/Type Doctrine2 extensions (^1.0)" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "mockery/mockery": "^1.3.1", - "gedmo/doctrine-extensions": "^2.4", - "beberlei/doctrineextensions": "^1.0" - }, - "autoload": { - "psr-4": { - "LaravelDoctrine\\Extensions\\": "src/" - } - }, - "autoload-dev": { - "classmap": [ - "tests" - ] - } } diff --git a/docs/banner.png b/docs/banner.png new file mode 100644 index 0000000..91cc76d Binary files /dev/null and b/docs/banner.png differ diff --git a/docs/blameable.rst b/docs/blameable.rst new file mode 100644 index 0000000..eb04d06 --- /dev/null +++ b/docs/blameable.rst @@ -0,0 +1,35 @@ +========= +Blameable +========= + +Blameable behavior will automate the update of username or user reference +fields on your Entities. It works through annotations and can +update fields on creation, update, property subset update, or even on +specific property value change. + +* Automatic predefined user field update on creation, update, property + subset update, and even on record property changes. +* Specific annotations for properties, and no interface required. +* Can react to specific property or relation changes to specific value. +* Can be nested with other behaviors. +* Annotation, Yaml and Xml mapping support for extensions. + + +Installation +============ + +Add ``LaravelDoctrine\Extensions\Blameable\BlameableExtension`` +to ``doctrine.extensions`` config in ``config/doctrine.php``. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..0d75d0b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,68 @@ +import sys, os +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + +lexers['php'] = PhpLexer(startinline=True, linenos=0) +lexers['php-annotations'] = PhpLexer(startinline=True, linenos=0) +primary_domain = 'php' + +extensions = [] +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +project = u'Laravel Doctrine Extensions' +copyright = u'2024 laraveldoctrine.org' +version = '9' +html_title = "Extensions for Laravel and Doctrine" +html_short_title = "Laravel Doctrine Extensions" +html_favicon = 'favicon.ico' + +exclude_patterns = ['_build'] +html_static_path = ['_static'] + +##### Guzzle sphinx theme + +import guzzle_sphinx_theme +html_translator_class = 'guzzle_sphinx_theme.HTMLTranslator' +html_theme_path = guzzle_sphinx_theme.html_theme_path() +html_theme = 'guzzle_sphinx_theme' + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': ['logo-text.html', 'globaltoc.html', 'searchbox.html'] +} + +# Register the theme as an extension to generate a sitemap.xml +extensions.append("guzzle_sphinx_theme") + +# Guzzle theme options (see theme.conf for more information) +html_theme_options = { + + # Set the path to a special layout to include for the homepage + # "index_template": "homepage.html", + + # Allow a separate homepage from the master_doc + # homepage = index + + # Set the name of the project to appear in the nav menu + # "project_nav_name": "Guzzle", + + # Set your Disqus short name to enable comments + # "disqus_comments_shortname": "my_disqus_comments_short_name", + + # Set you GA account ID to enable tracking + # "google_analytics_account": "my_ga_account", + + # Path to a touch icon + # "touch_icon": "", + + # Specify a base_url used to generate sitemap.xml links. If not + # specified, then no sitemap will be built. + "base_url": "https://extensions.docs.laraveldoctrine.org" + + # Allow the "Table of Contents" page to be defined separately from "master_doc" + # tocpage = Contents + + # Allow the project link to be overriden to a custom URL. + # projectlink = http://myproject.url +} diff --git a/docs/custom-functions.rst b/docs/custom-functions.rst new file mode 100644 index 0000000..1ea1ad7 --- /dev/null +++ b/docs/custom-functions.rst @@ -0,0 +1,102 @@ +========================= +Custom Database Functions +========================= + +A set of extensions to Doctrine that add support for additional +queryfunctions available in MySQL, Oracle, and SQLite. + +When ``LaravelDoctrine\Extensions\BeberleiExtensionsServiceProvider::class`` +is included, the following functions will be automatically registered: + +.. list-table:: Query/Type Extensions + :widths: 25 75 + :header-rows: 1 + + * - Database + - Functions + * - MySQL + - ``ACOS``, ``ASCII``, ``ASIN``, ``ATAN``, ``ATAN2``, ``BINARY``, ``CEIL``, + ``CHAR_LENGTH``, ``CONCAT_WS``, ``COS``, ``COT``, ``COUNTIF``, ``CRC32``, + ``DATE``, ``DATE_FORMAT``, ``DATEADD``, ``DATEDIFF``, ``DATESUB``, + ``DAY``, ``DAYNAME``, ``DEGREES``, ``FIELD``, ``FIND_IN_SET``, ``FLOOR``, + ``FROM_UNIXTIME``, ``GROUP_CONCAT``, ``HOUR``, ``IFELSE``, ``IFNULL``, + ``LAST_DAY``, ``MATCH_AGAINST``, ``MD5``, ``MINUTE``, ``MONTH``, + ``MONTHNAME``, ``NULLIF``, ``PI``, ``POWER``, ``QUARTER``, ``RADIANS``, + ``RAND``, ``REGEXP``, ``REPLACE``, ``ROUND``, ``SECOND``, ``SHA1``, + ``SHA2``, ``SIN``, ``SOUNDEX``, ``STD``, ``STRTODATE``, + ``SUBSTRING_INDEX``, ``TAN``, ``TIME``, ``TIMESTAMPADD``, + ``TIMESTAMPDIFF``, ``UUID_SHORT``, ``WEEK``, ``WEEKDAY``, ``YEAR`` + * - Oracle + - ``DAY``, ``MONTH``, ``NVL``, ``TODATE``, ``TRUNC``, ``YEAR`` + * - Sqlite + - ``DATE``, ``MINUTE``, ``HOUR``, ``DAY``, ``WEEK``, ``WEEKDAY``, + ``MONTH``, ``YEAR``, ``STRFTIME*`` + + +Alternativly you can include the separate classes inside `config/doctrine` config file. Example: + +.. code-block:: php + + return [ + + ... + + /* + |-------------------------------------------------------------------------- + | Doctrine custom types + |-------------------------------------------------------------------------- + */ + 'custom_types' => [ + 'carbondate' => DoctrineExtensions\Types\CarbonDateType::class, + 'carbondatetime' => DoctrineExtensions\Types\CarbonDateTimeType::class, + 'carbondatetimetz' => DoctrineExtensions\Types\CarbonDateTimeTzType::class, + 'carbontime' => DoctrineExtensions\Types\CarbonTimeType::class + ], + /* + |-------------------------------------------------------------------------- + | Doctrine custom datetime functions + |-------------------------------------------------------------------------- + */ + 'custom_datetime_functions' => [ + 'DATEADD' => DoctrineExtensions\Query\Mysql\DateAdd::class, + 'DATEDIFF' => DoctrineExtensions\Query\Mysql\DateDiff::class + ], + /* + |-------------------------------------------------------------------------- + | Doctrine custom numeric functions + |-------------------------------------------------------------------------- + */ + 'custom_numeric_functions' => [ + 'ACOS' => DoctrineExtensions\Query\Mysql\Acos::class, + 'ASIN' => DoctrineExtensions\Query\Mysql\Asin::class, + 'ATAN' => DoctrineExtensions\Query\Mysql\Atan::class, + 'ATAN2' => DoctrineExtensions\Query\Mysql\Atan2::class, + 'COS' => DoctrineExtensions\Query\Mysql\Cos::class, + 'COT' => DoctrineExtensions\Query\Mysql\Cot::class, + 'DEGREES' => DoctrineExtensions\Query\Mysql\Degrees::class, + 'RADIANS' => DoctrineExtensions\Query\Mysql\Radians::class, + 'SIN' => DoctrineExtensions\Query\Mysql\Sin::class, + 'TAN' => DoctrineExtensions\Query\Mysql\Ta::class + ], + /* + |-------------------------------------------------------------------------- + | Doctrine custom string functions + |-------------------------------------------------------------------------- + */ + 'custom_string_functions' => [ + 'CHAR_LENGTH' => DoctrineExtensions\Query\Mysql\CharLength::class, + 'CONCAT_WS' => DoctrineExtensions\Query\Mysql\ConcatWs::class, + 'FIELD' => DoctrineExtensions\Query\Mysql\Field::class, + 'FIND_IN_SET' => DoctrineExtensions\Query\Mysql\FindInSet::class, + 'REPLACE' => DoctrineExtensions\Query\Mysql\Replace::class, + 'SOUNDEX' => DoctrineExtensions\Query\Mysql\Soundex::class, + 'STR_TO_DATE' => DoctrineExtensions\Query\Mysql\StrToDate::class + ], + + ]; + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000..a86424e Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/footer.rst b/docs/footer.rst new file mode 100644 index 0000000..2c9dae8 --- /dev/null +++ b/docs/footer.rst @@ -0,0 +1,6 @@ + +---------- + +This is documentation for +`laravel-doctrine/extensions `_. +Please add your ★ star to the project. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..8213d75 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,33 @@ +=========================== +Laravel Doctrine Extensions +=========================== + +.. image:: banner.png + :align: center + :scale: 25 % + +This is the documentation for `laravel-doctrine/extensions `_ + +.. toctree:: + + :caption: Table of Contents + + introduction + installation + blameable + iptraceable + loggable + sluggable + softdeletes + sortable + timestamps + translatable + tree + uploadable + custom-functions + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..6eebf37 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,49 @@ +============ +Installation +============ + +Install this package with composer: + +.. code-block:: bash + + composer require laravel-doctrine/extensions + +This package wraps extensions from `Gedmo `_ +and `Beberlei `_. + + +To include Gedmo extensions install them with composer: + +.. code-block:: bash + + composer require "gedmo/doctrine-extensions" + + +Enable the service provider for them in Laravel: + +.. code-block:: php + + LaravelDoctrine\Extensions\GedmoExtensionsServiceProvider::class + +Also, be sure to enable the extensions in the ``extensions`` section of +``config/doctrine.php``. + + +To include Beberlei (Query/Type) extensions install them: + +.. code-block:: bash + + composer require "beberlei/DoctrineExtensions=^1.0" + + +And then add the Beberlei extensions service provider in `config/app.php`: + +.. code-block:: php + + LaravelDoctrine\Extensions\BeberleiExtensionsServiceProvider::class + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/introduction.rst b/docs/introduction.rst new file mode 100644 index 0000000..16a53f0 --- /dev/null +++ b/docs/introduction.rst @@ -0,0 +1,70 @@ +============ +Introduction +============ + +This package contains extensions for Doctrine that hook into the facilities +of Doctrine and offer new functionality and tools to use Doctrine +more efficiently. This package contains mostly used behaviors which can be +easily attached to the event system of Doctrine to handle records being +flushed in a behavioral way. + +Behavioral extensions (Gedmo) +============================= + +See the `doctrine-extensions documentation `_ +for more information. + +* ``Blameable`` - updates string or reference fields on create, update, and + even property change with a string or object (e.g. user). +* ``IpTraceable`` - inherited from Timestampable, sets IP address instead + of timestamp. +* ``Loggable`` - helps tracking changes and history of objects, also supports + version management. +* ``Sluggable`` - urlizes your specified fields into single unique slug. +* ``SoftDeleteable`` - allows to implicitly remove records. +* ``Sortable`` - makes any document or entity sortable. +* ``Timestampable`` - updates date fields on create, update, and even + property change. +* ``Translatable`` - gives you a handy solution for translating records into + different languages. Easy to setup, easier to use. +* ``Tree`` - this extension automates the tree handling process and adds + some tree specific functions on repository. (closure, nestedset or + materialized path). +* ``Uploadable`` - provides file upload handling in entity fields. + + +Query/Type Extensions (Beberlei) +================================ + +A set of extensions for Doctrine that add support for additional functions +available in MySQL and Oracle. + +.. list-table:: Query/Type Extensions + :widths: 25 75 + :header-rows: 1 + + * - Database + - Functions + * - MySQL + - ``ACOS``, ``ASCII``, ``ASIN``, ``ATAN``, ``ATAN2``, ``BINARY``, ``CEIL``, + ``CHAR_LENGTH``, ``CONCAT_WS``, ``COS``, ``COT``, ``COUNTIF``, ``CRC32``, + ``DATE``, ``DATE_FORMAT``, ``DATEADD``, ``DATEDIFF``, ``DATESUB``, + ``DAY``, ``DAYNAME``, ``DEGREES``, ``FIELD``, ``FIND_IN_SET``, ``FLOOR``, + ``FROM_UNIXTIME``, ``GROUP_CONCAT``, ``HOUR``, ``IFELSE``, ``IFNULL``, + ``LAST_DAY``, ``MATCH_AGAINST``, ``MD5``, ``MINUTE``, ``MONTH``, + ``MONTHNAME``, ``NULLIF``, ``PI``, ``POWER``, ``QUARTER``, ``RADIANS``, + ``RAND``, ``REGEXP``, ``REPLACE``, ``ROUND``, ``SECOND``, ``SHA1``, + ``SHA2``, ``SIN``, ``SOUNDEX``, ``STD``, ``STRTODATE``, + ``SUBSTRING_INDEX``, ``TAN``, ``TIME``, ``TIMESTAMPADD``, + ``TIMESTAMPDIFF``, ``UUID_SHORT``, ``WEEK``, ``WEEKDAY``, ``YEAR`` + * - Oracle + - ``DAY``, ``MONTH``, ``NVL``, ``TODATE``, ``TRUNC``, ``YEAR`` + * - Sqlite + - ``DATE``, ``MINUTE``, ``HOUR``, ``DAY``, ``WEEK``, ``WEEKDAY``, + ``MONTH``, ``YEAR``, ``STRFTIME*`` + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/iptraceable.rst b/docs/iptraceable.rst new file mode 100644 index 0000000..19616f9 --- /dev/null +++ b/docs/iptraceable.rst @@ -0,0 +1,34 @@ +============ +Ip Traceable +============ + +IpTraceable behavior will automate the update of IP trace on your Entities. +It works through annotations and can update fields on creation, update, +property subset update, or even on specific property value change. + +* Automatic predefined ip field update on creation, update, property subset + update, and even on record property changes. +* Specific annotations for properties, and no interface required. +* Can react to specific property or relation changes to specific value. +* Can be nested with other behaviors. +* Annotation, Yaml and Xml mapping support for extensions. + + +Installation +============ + +Add ``LaravelDoctrine\Extensions\IpTraceable\IpTraceableExtension`` +to ``doctrine.extensions`` config in ``config/doctrine.php``. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/loggable.rst b/docs/loggable.rst new file mode 100644 index 0000000..923e7c7 --- /dev/null +++ b/docs/loggable.rst @@ -0,0 +1,30 @@ +======== +Loggable +======== + +Loggable behavior tracks your record changes and is able to manage versions. + +* Automatic storage of log entries in database. +* Can be nested with other behaviors. +* Objects can be reverted to previous versions. +* Annotation, Yaml and Xml mapping support for extensions. + + +Installation +============ + +Add ``LaravelDoctrine\Extensions\Loggable\LoggableExtension`` +to ``doctrine.extensions`` config in ``config/doctrine.php``. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..a1540a8 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +guzzle_sphinx_theme diff --git a/docs/sluggable.rst b/docs/sluggable.rst new file mode 100644 index 0000000..b9e44b6 --- /dev/null +++ b/docs/sluggable.rst @@ -0,0 +1,32 @@ +========= +Sluggable +========= + +Sluggable behavior will build the slug of predefined fields on a given field +which should store the slug + +* Automatic predefined field transformation into slug. +* Slugs can be unique and styled, even with prefixes and/or suffixes. +* Can be nested with other behaviors. +* Annotation, Yaml and Xml mapping support for extensions. +* Multiple slugs, different slugs can link to same fields. + + +Installation +============ + +Add ``LaravelDoctrine\Extensions\Sluggable\SluggableExtension`` +to ``doctrine.extensions`` config in ``config/doctrine.php``. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/softdeletes.rst b/docs/softdeletes.rst new file mode 100644 index 0000000..c3201de --- /dev/null +++ b/docs/softdeletes.rst @@ -0,0 +1,35 @@ +============ +Soft Deletes +============ + +SoftDeleteable behavior allows to "soft delete" objects, filtering them at +SELECT time by marking them as with a timestamp, but not explicitly removing +them from the database. + +* Works with DQL DELETE queries (using a Query Hint). +* All SELECT queries will be filtered, not matter from where they are + executed (Repositories, DQL SELECT queries, etc). +* Can be nested with other behaviors. +* Annotation, Yaml and Xml mapping support for extensions. +* Support for 'timeAware' option: When creating an entity set a date of + deletion in the future and never worry about cleaning up at expiration time. + + +Installation +============ + +Add ``LaravelDoctrine\Extensions\SoftDeletes\SoftDeleteableExtension`` +to ``doctrine.extensions`` config in ``config/doctrine.php``. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/sortable.rst b/docs/sortable.rst new file mode 100644 index 0000000..20d6a89 --- /dev/null +++ b/docs/sortable.rst @@ -0,0 +1,30 @@ +======== +Sortable +======== + +Sortable behavior will maintain a position field for ordering entities. + +* Automatic handling of position index +* Group entity ordering by one or more fields +* Can be nested with other behaviors +* Annotation, Yaml and Xml mapping support for extensions + + +Installation +============ + +Add ``LaravelDoctrine\Extensions\Sortable\SortableExtension`` +to ``doctrine.extensions`` config in ``config/doctrine.php``. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/timestamps.rst b/docs/timestamps.rst new file mode 100644 index 0000000..a790d3b --- /dev/null +++ b/docs/timestamps.rst @@ -0,0 +1,29 @@ +========== +Timestamps +========== + +Timestamps allows you to automatically record the time of certain events +against your entities. This can be used to provide similar behaviour to +the timestamps feature in Laravel's Eloquent ORM. + +Features: + +* Automatic predefined date field update on creation, update, property + subset update, and even on record property changes. +* Specific annotations for properties, and no interface required. +* Can react to specific property or relation changes to specific value. +* Can be nested with other behaviors. +* Annotation, Yaml and Xml mapping support for extensions. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/translatable.rst b/docs/translatable.rst new file mode 100644 index 0000000..1341691 --- /dev/null +++ b/docs/translatable.rst @@ -0,0 +1,31 @@ +============ +Translatable +============ + +Translatable behavior offers a very handy solution for translating specific +record fields in different languages. Further more, it loads the translations +automatically for a locale currently used, which can be set to Translatable +Listener on it`s initialization or later for other cases through the Entity +itself. + +Features: + +* Automatic storage of translations in database. +* Automatic translation of Entity or Document fields then loaded. +* ORM query can use **hint** to translate all records without + issuing additional queries. +* Can be nested with other behaviors. +* Annotation, Yaml and Xml mapping support for extensions. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/tree.rst b/docs/tree.rst new file mode 100644 index 0000000..24dd626 --- /dev/null +++ b/docs/tree.rst @@ -0,0 +1,30 @@ +================ +Tree - Nestedset +================ + +Tree nested behavior will implement the standard Nested-Set behavior on your +Entity. Tree supports different strategies. Currently it supports nested-set, +closure-table and materialized-path. Also this behavior can be nested with +other extensions to translate or generated slugs of your tree nodes. + +* Materialized Path strategy for ORM. +* Closure tree strategy, may be faster in some cases where ordering does + not matter. +* Support for multiple roots in nested-set. +* No need for other managers, implementation is through event listener. +* Synchronization of left, right values is automatic. +* Can support concurrent flush with many objects being persisted and updated. +* Can be nested with other extensions. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/docs/uploadable.rst b/docs/uploadable.rst new file mode 100644 index 0000000..d8b2fb2 --- /dev/null +++ b/docs/uploadable.rst @@ -0,0 +1,28 @@ +========== +Uploadable +========== + +Uploadable behavior provides the tools to manage the persistence of files +with Doctrine, including automatic handling of moving, renaming and +removal of files and other features. + +* Extension moves, removes and renames files according to configuration + automatically. +* Lots of options: Allow overwrite, append a number if file exists, +* filename generators, post-move callbacks, etc. +* It can be extended to work not only with uploaded files, but with files +* coming from any source (an URL, another file in the same server, etc). +* Validation of size and mime type. + + +Further Reading +=============== + +See the `official documentation `_ +for use of this behavior. + + +.. role:: raw-html(raw) + :format: html + +.. include:: footer.rst diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..c14a9c3 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,18 @@ + + + + + + + + + + + + + src + tests + + + + diff --git a/phpunit.xml b/phpunit.xml index 0b5c4a4..165b475 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,26 @@ - + cacheDirectory=".phpunit.cache" + executionOrder="depends,defects" + shortenArraysForExportThreshold="10" + requireCoverageMetadata="false" + beStrictAboutCoverageMetadata="false" + beStrictAboutOutputDuringTests="true" + displayDetailsOnPhpunitDeprecations="true" + failOnPhpunitDeprecation="true" + failOnRisky="true" + failOnWarning="true"> - - ./tests/ + + tests + + + + src + + diff --git a/src/BeberleiExtensionsServiceProvider.php b/src/BeberleiExtensionsServiceProvider.php index ef68927..03909c2 100644 --- a/src/BeberleiExtensionsServiceProvider.php +++ b/src/BeberleiExtensionsServiceProvider.php @@ -1,5 +1,7 @@ Mysql\Date::class, - 'DATE_FORMAT' => Mysql\DateFormat::class, - 'DATEADD' => Mysql\DateAdd::class, - 'DATEDIFF' => Mysql\DateDiff::class, - 'DATESUB' => Mysql\DateSub::class, - 'DAY' => Mysql\Day::class, - 'DAYNAME' => Mysql\DayName::class, - 'FROM_UNIXTIME' => Mysql\FromUnixtime::class, - 'HOUR' => Mysql\Hour::class, - 'LAST_DAY' => Mysql\LastDay::class, - 'MINUTE' => Mysql\Minute::class, - 'MONTH' => Mysql\Month::class, - 'MONTHNAME' => Mysql\MonthName::class, - 'SECOND' => Mysql\Second::class, - 'STRTODATE' => Mysql\StrToDate::class, - 'TIME' => Mysql\Time::class, - 'TIMESTAMPADD' => Mysql\TimestampAdd::class, - 'TIMESTAMPDIFF' => Mysql\TimestampDiff::class, - 'WEEK' => Mysql\Week::class, - 'WEEKDAY' => Mysql\WeekDay::class, - 'YEAR' => Mysql\Year::class + /** @var mixed[] */ + protected array $datetime = [ + 'DATE' => Mysql\Date::class, + 'DATE_FORMAT' => Mysql\DateFormat::class, + 'DATEADD' => Mysql\DateAdd::class, + 'DATEDIFF' => Mysql\DateDiff::class, + 'DATESUB' => Mysql\DateSub::class, + 'DAY' => Mysql\Day::class, + 'DAYNAME' => Mysql\DayName::class, + 'FROM_UNIXTIME' => Mysql\FromUnixtime::class, + 'HOUR' => Mysql\Hour::class, + 'LAST_DAY' => Mysql\LastDay::class, + 'MINUTE' => Mysql\Minute::class, + 'MONTH' => Mysql\Month::class, + 'MONTHNAME' => Mysql\MonthName::class, + 'SECOND' => Mysql\Second::class, + 'STRTODATE' => Mysql\StrToDate::class, + 'TIME' => Mysql\Time::class, + 'TIMESTAMPADD' => Mysql\TimestampAdd::class, + 'TIMESTAMPDIFF' => Mysql\TimestampDiff::class, + 'WEEK' => Mysql\Week::class, + 'WEEKDAY' => Mysql\WeekDay::class, + 'YEAR' => Mysql\Year::class, ]; - /** - * @var array - */ - protected $numeric = [ - 'ACOS' => Mysql\Acos::class, - 'ASIN' => Mysql\Asin::class, - 'ATAN' => Mysql\Atan::class, - 'ATAN2' => Mysql\Atan2::class, - 'BINARY' => Mysql\Binary::class, - 'CEIL' => Mysql\Ceil::class, - 'COS' => Mysql\Cos::class, - 'COT' => Mysql\Cot::class, - 'COUNTIF' => Mysql\CountIf::class, - 'CRC32' => Mysql\Crc32::class, - 'DEGREES' => Mysql\Degrees::class, - 'FLOOR' => Mysql\Floor::class, - 'IFELSE' => Mysql\IfElse::class, - 'IFNULL' => Mysql\IfNull::class, - 'MATCH_AGAINST' => Mysql\MatchAgainst::class, - 'NULLIF' => Mysql\NullIf::class, - 'PI' => Mysql\Pi::class, - 'POWER' => Mysql\Power::class, - 'QUARTER' => Mysql\Quarter::class, - 'RADIANS' => Mysql\Radians::class, - 'RAND' => Mysql\Rand::class, - 'ROUND' => Mysql\Round::class, - 'SIN' => Mysql\Sin::class, - 'STD' => Mysql\Std::class, - 'TAN' => Mysql\Tan::class, - 'UUID_SHORT' => Mysql\UuidShort::class - + /** @var mixed[] */ + protected array $numeric = [ + 'ACOS' => Mysql\Acos::class, + 'ASIN' => Mysql\Asin::class, + 'ATAN' => Mysql\Atan::class, + 'ATAN2' => Mysql\Atan2::class, + 'BINARY' => Mysql\Binary::class, + 'CEIL' => Mysql\Ceil::class, + 'COS' => Mysql\Cos::class, + 'COT' => Mysql\Cot::class, + 'COUNTIF' => Mysql\CountIf::class, + 'CRC32' => Mysql\Crc32::class, + 'DEGREES' => Mysql\Degrees::class, + 'FLOOR' => Mysql\Floor::class, + 'IFELSE' => Mysql\IfElse::class, + 'IFNULL' => Mysql\IfNull::class, + 'MATCH_AGAINST' => Mysql\MatchAgainst::class, + 'NULLIF' => Mysql\NullIf::class, + 'PI' => Mysql\Pi::class, + 'POWER' => Mysql\Power::class, + 'QUARTER' => Mysql\Quarter::class, + 'RADIANS' => Mysql\Radians::class, + 'RAND' => Mysql\Rand::class, + 'ROUND' => Mysql\Round::class, + 'SIN' => Mysql\Sin::class, + 'STD' => Mysql\Std::class, + 'TAN' => Mysql\Tan::class, + 'UUID_SHORT' => Mysql\UuidShort::class, ]; - /** - * @var array - */ - protected $string = [ - 'ASCII' => Mysql\Ascii::class, - 'CHAR_LENGTH' => Mysql\CharLength::class, - 'CONCAT_WS' => Mysql\ConcatWs::class, - 'FIELD' => Mysql\Field::class, - 'FIND_IN_SET' => Mysql\FindInSet::class, - 'GROUP_CONCAT' => Mysql\GroupConcat::class, - 'MD5' => Mysql\Md5::class, - 'REGEXP' => Mysql\Regexp::class, - 'REPLACE' => Mysql\Replace::class, - 'SHA1' => Mysql\Sha1::class, - 'SHA2' => Mysql\Sha2::class, - 'SOUNDEX' => Mysql\Soundex::class, - 'SUBSTRING_INDEX' => Mysql\SubstringIndex::class + /** @var mixed[] */ + protected array $string = [ + 'ASCII' => Mysql\Ascii::class, + 'CHAR_LENGTH' => Mysql\CharLength::class, + 'CONCAT_WS' => Mysql\ConcatWs::class, + 'FIELD' => Mysql\Field::class, + 'FIND_IN_SET' => Mysql\FindInSet::class, + 'GROUP_CONCAT' => Mysql\GroupConcat::class, + 'MD5' => Mysql\Md5::class, + 'REGEXP' => Mysql\Regexp::class, + 'REPLACE' => Mysql\Replace::class, + 'SHA1' => Mysql\Sha1::class, + 'SHA2' => Mysql\Sha2::class, + 'SOUNDEX' => Mysql\Soundex::class, + 'SUBSTRING_INDEX' => Mysql\SubstringIndex::class, ]; /** * Register the metadata - * - * @param DoctrineManager $manager */ - public function boot(DoctrineManager $manager) + public function boot(DoctrineManager $manager): void { - $manager->extendAll(function (Configuration $configuration) { + $manager->extendAll(function (Configuration $configuration): void { $configuration->setCustomDatetimeFunctions($this->datetime); $configuration->setCustomNumericFunctions($this->numeric); $configuration->setCustomStringFunctions($this->string); }); } - - /** - * Register the service provider. - * @return void - */ - public function register() - { - } } diff --git a/src/Blameable/BlameableExtension.php b/src/Blameable/BlameableExtension.php index ca6a81c..6bfc644 100644 --- a/src/Blameable/BlameableExtension.php +++ b/src/Blameable/BlameableExtension.php @@ -1,8 +1,9 @@ addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/GedmoExtension.php b/src/GedmoExtension.php index 8d60291..25df52c 100644 --- a/src/GedmoExtension.php +++ b/src/GedmoExtension.php @@ -1,25 +1,17 @@ setAnnotationReader($reader); - } - $manager->addEventSubscriber($subscriber); } } diff --git a/src/GedmoExtensionsServiceProvider.php b/src/GedmoExtensionsServiceProvider.php index 40a6b8a..bc259aa 100644 --- a/src/GedmoExtensionsServiceProvider.php +++ b/src/GedmoExtensionsServiceProvider.php @@ -1,103 +1,35 @@ app['events']->listen('doctrine.extensions.booting', function () { + $this->app['events']->listen('doctrine.extensions.booting', function (): void { $registry = $this->app->make('registry'); foreach ($registry->getManagers() as $manager) { $chain = $manager->getConfiguration()->getMetadataDriverImpl(); - if ($this->hasAnnotationReader($chain)) { - $this->registerGedmoForAnnotations($chain); - } - - if ($this->hasFluentDriver($chain)) { - $this->registerGedmoForFluent($chain); + if ($this->needsAllMappings()) { + DoctrineExtensions::registerMappingIntoDriverChainORM($chain); + } else { + DoctrineExtensions::registerAbstractMappingIntoDriverChainORM($chain); } } }); } - /** - * @param MappingDriverChain $driver - * @return bool - */ - private function hasAnnotationReader(MappingDriverChain $driver) - { - foreach ($driver->getDrivers() as $driver) { - if ($driver instanceof AnnotationDriver) { - return true; - } - } - - return false; - } - - /** - * @param MappingDriverChain $driver - * @return bool - */ - private function hasFluentDriver(MappingDriverChain $driver) - { - foreach ($driver->getDrivers() as $driver) { - if ($driver instanceof FluentDriver) { - return true; - } - } - - return false; - } - - /** - * @param $chain - */ - private function registerGedmoForAnnotations(MappingDriverChain $chain) - { - if ($this->needsAllMappings()) { - DoctrineExtensions::registerMappingIntoDriverChainORM( - $chain, - $chain->getReader() - ); - } else { - DoctrineExtensions::registerAbstractMappingIntoDriverChainORM( - $chain, - $chain->getReader() - ); - } - } - - /** - * @param MappingDriverChain $chain - */ - private function registerGedmoForFluent(MappingDriverChain $chain) - { - if ($this->needsAllMappings()) { - GedmoExtensions::registerAll($chain); - } else { - GedmoExtensions::registerAbstract($chain); - } - } - - /** - * @return mixed - */ - private function needsAllMappings() + private function needsAllMappings(): mixed { return $this->app->make('config')->get('doctrine.gedmo.all_mappings', false) === true; } diff --git a/src/IpTraceable/IpTraceable.php b/src/IpTraceable/IpTraceable.php deleted file mode 100644 index 1a47f54..0000000 --- a/src/IpTraceable/IpTraceable.php +++ /dev/null @@ -1,69 +0,0 @@ -createdFromIp = $createdFromIp; - - return $this; - } - - /** - * Returns createdFromIp. - * @return string - */ - public function getCreatedFromIp() - { - return $this->createdFromIp; - } - - /** - * Sets updatedFromIp. - * - * @param string $updatedFromIp - * - * @return $this - */ - public function setUpdatedFromIp($updatedFromIp) - { - $this->updatedFromIp = $updatedFromIp; - - return $this; - } - - /** - * Returns updatedFromIp. - * @return string - */ - public function getUpdatedFromIp() - { - return $this->updatedFromIp; - } -} diff --git a/src/IpTraceable/IpTraceableExtension.php b/src/IpTraceable/IpTraceableExtension.php index 4a273cb..01a585c 100644 --- a/src/IpTraceable/IpTraceableExtension.php +++ b/src/IpTraceable/IpTraceableExtension.php @@ -1,8 +1,9 @@ request = $request; } - /** - * @param EventManager $manager - * @param EntityManagerInterface $em - * @param Reader $reader - */ - public function addSubscribers(EventManager $manager, EntityManagerInterface $em, Reader $reader = null) + public function addSubscribers(EventManager $manager, EntityManagerInterface $em): void { $subscriber = new IpTraceableListener(); $subscriber->setIpValue($this->request->getClientIp()); - $this->addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/Loggable/LoggableExtension.php b/src/Loggable/LoggableExtension.php index 2eee948..7ae0623 100644 --- a/src/Loggable/LoggableExtension.php +++ b/src/Loggable/LoggableExtension.php @@ -1,8 +1,9 @@ addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/ResolveUserDecorator.php b/src/ResolveUserDecorator.php index 0b54ba0..350262c 100644 --- a/src/ResolveUserDecorator.php +++ b/src/ResolveUserDecorator.php @@ -1,63 +1,35 @@ wrapped = $wrapped; - $this->userSetterMethod = $userSetterMethod; } /** * Returns an array of events this subscriber wants to listen to. * - * @return array + * @return string[] */ - public function getSubscribedEvents() + public function getSubscribedEvents(): array { return $this->wrapped->getSubscribedEvents(); } - /** - * Set annotation reader class - * since older doctrine versions do not provide an interface - * it must provide these methods: - * getClassAnnotations([reflectionClass]) - * getClassAnnotation([reflectionClass], [name]) - * getPropertyAnnotations([reflectionProperty]) - * getPropertyAnnotation([reflectionProperty], [name]) - * - * @param Reader $reader - annotation reader class - */ - public function setAnnotationReader($reader) - { - $this->wrapped->setAnnotationReader($reader); - } - - /** - * @param string $method - * @param array $params - * @return mixed - */ - public function __call($method, $params) + /** @param mixed[] $params */ + public function __call(string $method, array $params): mixed { if ($method !== 'loadClassMetadata' && $this->getGuard()->check()) { call_user_func([$this->wrapped, $this->userSetterMethod], $this->getGuard()->user()); @@ -70,10 +42,8 @@ public function __call($method, $params) * Get the namespace of extension event subscriber. * used for cache id of extensions also to know where * to find Mapping drivers and event adapters - * - * @return string */ - protected function getNamespace() + protected function getNamespace(): string { return $this->wrapped->getNamespace(); } @@ -81,21 +51,17 @@ protected function getNamespace() /** * Get the class of extension event subscriber. * Used to identify which event subscriber is wrapped by the resolver. - * - * @return string */ - public function getEventSubscriberClass() + public function getEventSubscriberClass(): string { return get_class($this->wrapped); } - + /** - * Get current Auth manager. - * - * @return \Illuminate\Contracts\Auth\Guard - */ - protected function getGuard() + * Get current Auth manager. + */ + protected function getGuard(): Guard { - return app('auth')->guard(); + return app('auth')->guard(); } } diff --git a/src/Sluggable/SluggableExtension.php b/src/Sluggable/SluggableExtension.php index aa51c53..cf0c9e1 100644 --- a/src/Sluggable/SluggableExtension.php +++ b/src/Sluggable/SluggableExtension.php @@ -1,8 +1,9 @@ addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/SoftDeletes/SoftDeleteableExtension.php b/src/SoftDeletes/SoftDeleteableExtension.php index 3caab0c..175b0c5 100644 --- a/src/SoftDeletes/SoftDeleteableExtension.php +++ b/src/SoftDeletes/SoftDeleteableExtension.php @@ -1,8 +1,9 @@ addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return [ - 'soft-deleteable' => SoftDeleteableFilter::class + 'soft-deleteable' => SoftDeleteableFilter::class, ]; } } diff --git a/src/SoftDeletes/SoftDeletes.php b/src/SoftDeletes/SoftDeletes.php deleted file mode 100644 index b598243..0000000 --- a/src/SoftDeletes/SoftDeletes.php +++ /dev/null @@ -1,47 +0,0 @@ -deletedAt; - } - - /** - * @param DateTime|null $deletedAt - */ - public function setDeletedAt(DateTime $deletedAt = null) - { - $this->deletedAt = $deletedAt; - } - - /** - * Restore the soft-deleted state - */ - public function restore() - { - $this->deletedAt = null; - } - - /** - * @return bool - */ - public function isDeleted() - { - return $this->deletedAt && new DateTime('now') >= $this->deletedAt; - } -} diff --git a/src/Sortable/SortableExtension.php b/src/Sortable/SortableExtension.php index 52cadf3..d630739 100644 --- a/src/Sortable/SortableExtension.php +++ b/src/Sortable/SortableExtension.php @@ -1,8 +1,9 @@ addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/Timestamps/TimestampableExtension.php b/src/Timestamps/TimestampableExtension.php index 52472e9..677adad 100644 --- a/src/Timestamps/TimestampableExtension.php +++ b/src/Timestamps/TimestampableExtension.php @@ -1,8 +1,9 @@ addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/Timestamps/Timestamps.php b/src/Timestamps/Timestamps.php deleted file mode 100644 index 1f61631..0000000 --- a/src/Timestamps/Timestamps.php +++ /dev/null @@ -1,56 +0,0 @@ -createdAt; - } - - /** - * @return DateTime - */ - public function getUpdatedAt() - { - return $this->updatedAt; - } - - /** - * @param DateTime $createdAt - */ - public function setCreatedAt(DateTime $createdAt) - { - $this->createdAt = $createdAt; - } - - /** - * @param DateTime $updatedAt - */ - public function setUpdatedAt(DateTime $updatedAt) - { - $this->updatedAt = $updatedAt; - } -} diff --git a/src/Translatable/TranslatableExtension.php b/src/Translatable/TranslatableExtension.php index b3afdbf..e05db5a 100644 --- a/src/Translatable/TranslatableExtension.php +++ b/src/Translatable/TranslatableExtension.php @@ -1,8 +1,9 @@ application = $application; - $this->repository = $repository; - $this->events = $events; } - /** - * @param EventManager $manager - * @param EntityManagerInterface $em - * @param Reader $reader - */ - public function addSubscribers(EventManager $manager, EntityManagerInterface $em, Reader $reader = null) + public function addSubscribers(EventManager $manager, EntityManagerInterface $em): void { - $subscriber = new TranslatableListener; + $subscriber = new TranslatableListener(); $subscriber->setTranslatableLocale($this->application->getLocale()); $subscriber->setDefaultLocale($this->repository->get('app.locale')); - $this->addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); - $this->events->listen('locale.changed', function ($locale) use ($subscriber) { + $this->events->listen('locale.changed', static function ($locale) use ($subscriber): void { $subscriber->setTranslatableLocale($locale); }); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/Tree/NestedSet.php b/src/Tree/NestedSet.php deleted file mode 100644 index 2fc864a..0000000 --- a/src/Tree/NestedSet.php +++ /dev/null @@ -1,101 +0,0 @@ -root; - } - - /** - * @param int $root - */ - public function setRoot($root) - { - $this->root = $root; - } - - /** - * @return int - */ - public function getLevel() - { - return $this->level; - } - - /** - * @param int $level - */ - public function setLevel($level) - { - $this->level = $level; - } - - /** - * @return int - */ - public function getLeft() - { - return $this->left; - } - - /** - * @param int $left - */ - public function setLeft($left) - { - $this->left = $left; - } - - /** - * @return int - */ - public function getRight() - { - return $this->right; - } - - /** - * @param int $right - */ - public function setRight($right) - { - $this->right = $right; - } -} diff --git a/src/Tree/TreeExtension.php b/src/Tree/TreeExtension.php index 6971bd1..cbcbc0e 100644 --- a/src/Tree/TreeExtension.php +++ b/src/Tree/TreeExtension.php @@ -1,8 +1,9 @@ addSubscriber($subscriber, $manager, $reader); + $this->addSubscriber($subscriber, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/Uploadable/UploadableExtension.php b/src/Uploadable/UploadableExtension.php index 70a05d2..1862652 100644 --- a/src/Uploadable/UploadableExtension.php +++ b/src/Uploadable/UploadableExtension.php @@ -1,8 +1,9 @@ listener = $listener; } - /** - * @param EventManager $manager - * @param EntityManagerInterface $em - * @param Reader $reader - */ - public function addSubscribers(EventManager $manager, EntityManagerInterface $em, Reader $reader = null) + public function addSubscribers(EventManager $manager, EntityManagerInterface $em): void { - $this->addSubscriber($this->listener, $manager, $reader); + $this->addSubscriber($this->listener, $manager); } - /** - * @return array - */ - public function getFilters() + /** @return mixed[] */ + public function getFilters(): array { return []; } diff --git a/src/Uploadable/UploadableExtensionServiceProvider.php b/src/Uploadable/UploadableExtensionServiceProvider.php index 8ae1264..6c74c9f 100644 --- a/src/Uploadable/UploadableExtensionServiceProvider.php +++ b/src/Uploadable/UploadableExtensionServiceProvider.php @@ -1,5 +1,7 @@ app->singleton(UploadableListener::class); } diff --git a/src/Uploadable/UploadableFacade.php b/src/Uploadable/UploadableFacade.php index 41b6774..69cbb70 100644 --- a/src/Uploadable/UploadableFacade.php +++ b/src/Uploadable/UploadableFacade.php @@ -1,5 +1,7 @@ app = m::mock(Application::class); - $this->manager = m::mock(DoctrineManager::class); - - $this->provider = new BeberleiExtensionsServiceProvider($this->app); - - // silence the 'This test did not perform any assertions' warning - $this->assertTrue(true); - } - - public function test_custom_functions_can_be_registered() - { - $this->manager->shouldReceive('extendAll')->once(); - - $this->provider->boot($this->manager); - } - - public function tearDown(): void - { - m::close(); - } -} diff --git a/tests/Blameable/BlameableExtensionTest.php b/tests/Blameable/BlameableExtensionTest.php deleted file mode 100644 index c6c79e2..0000000 --- a/tests/Blameable/BlameableExtensionTest.php +++ /dev/null @@ -1,25 +0,0 @@ -addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/tests/Entity/.gitkeep b/tests/Entity/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/ExtensionTestCase.php b/tests/ExtensionTestCase.php deleted file mode 100644 index 077b941..0000000 --- a/tests/ExtensionTestCase.php +++ /dev/null @@ -1,38 +0,0 @@ -evm = m::mock(EventManager::class); - $this->evm->shouldReceive('addEventSubscriber')->once(); - - $this->em = m::mock(EntityManagerInterface::class); - $this->reader = m::mock(Reader::class); - } - public function tearDown(): void - { - m::close(); - } -} diff --git a/tests/Feature/BeberleiExtensionsServiceProviderTest.php b/tests/Feature/BeberleiExtensionsServiceProviderTest.php new file mode 100644 index 0000000..3c57747 --- /dev/null +++ b/tests/Feature/BeberleiExtensionsServiceProviderTest.php @@ -0,0 +1,21 @@ +app->get(DoctrineManager::class); + + $entityManager = app('em'); + + $this->assertTrue(true); + } +} diff --git a/tests/Feature/Gedmo/SubscribeAllTest.php b/tests/Feature/Gedmo/SubscribeAllTest.php new file mode 100644 index 0000000..e5bf55e --- /dev/null +++ b/tests/Feature/Gedmo/SubscribeAllTest.php @@ -0,0 +1,124 @@ +addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterIpTraceable(): void + { + $request = m::mock(Request::class); + + $request->shouldReceive('getClientIp') + ->once()->andReturn('127.0.0.1'); + + $extension = new IpTraceableExtension($request); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterLoggable(): void + { + $extension = new LoggableExtension(); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterTimestampable(): void + { + $extension = new TimestampableExtension(); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterSluggable(): void + { + $extension = new SluggableExtension(); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterSoftDeletes(): void + { + $extension = new SoftDeleteableExtension(); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEquals($extension->getFilters(), ['soft-deleteable' => SoftDeleteableFilter::class]); + } + + public function testCanRegisterSortable(): void + { + $extension = new SortableExtension(); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterTranslatable(): void + { + $extension = app(TranslatableExtension::class); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterTree(): void + { + $extension = new TreeExtension(); + $extension->addSubscribers($this->evm, $this->em); + + $this->assertTrue(true); + $this->assertEmpty($extension->getFilters()); + } + + public function testCanRegisterUploadable(): void + { + $extension = new UploadableExtension(app(UploadableListener::class)); + + $this->assertEmpty($extension->getFilters()); + } +} diff --git a/tests/Feature/Gedmo/Translatable/TranslatableExtensionTest.php b/tests/Feature/Gedmo/Translatable/TranslatableExtensionTest.php new file mode 100644 index 0000000..f688eb5 --- /dev/null +++ b/tests/Feature/Gedmo/Translatable/TranslatableExtensionTest.php @@ -0,0 +1,23 @@ +addSubscribers($this->evm, $this->em); + + $this->app['events']->dispatch('locale.changed', ['en']); + + $this->assertEmpty($extension->getFilters()); + } +} diff --git a/tests/Uploadable/UploadableExtensionServiceProviderTest.php b/tests/Feature/Gedmo/Uploadable/UploadableExtensionServiceProviderTest.php similarity index 76% rename from tests/Uploadable/UploadableExtensionServiceProviderTest.php rename to tests/Feature/Gedmo/Uploadable/UploadableExtensionServiceProviderTest.php index b5cd7ff..9cb6195 100644 --- a/tests/Uploadable/UploadableExtensionServiceProviderTest.php +++ b/tests/Feature/Gedmo/Uploadable/UploadableExtensionServiceProviderTest.php @@ -1,22 +1,20 @@ provider = new UploadableExtensionServiceProvider($this->app); } - public function test_listener_gets_bound_as_singleton() + public function testListenerGetsBoundAsSingleton(): void { $this->app->shouldReceive('singleton') ->once() diff --git a/tests/Feature/Gedmo/Uploadable/UploadableExtensionTest.php b/tests/Feature/Gedmo/Uploadable/UploadableExtensionTest.php new file mode 100644 index 0000000..e83ddb4 --- /dev/null +++ b/tests/Feature/Gedmo/Uploadable/UploadableExtensionTest.php @@ -0,0 +1,34 @@ +assertInstanceOf(UploadableListener::class, UploadableFacade::getFacadeRoot()); + + $listener = m::mock(UploadableListener::class); + + $extension = new UploadableExtension( + app(UploadableListener::class), + ); + + $extension->addSubscribers( + $this->evm, + $this->em, + ); + + $this->assertEmpty($extension->getFilters()); + } +} diff --git a/tests/Feature/GedmoExtensionsServiceProviderAllMappingsTest.php b/tests/Feature/GedmoExtensionsServiceProviderAllMappingsTest.php new file mode 100644 index 0000000..b9be229 --- /dev/null +++ b/tests/Feature/GedmoExtensionsServiceProviderAllMappingsTest.php @@ -0,0 +1,29 @@ +set('doctrine.gedmo.all_mappings', true); + }); + } + + public function testCustomExtensionsCanBeRegistered(): void + { + $this->app['events']->dispatch('doctrine.extensions.booting'); + + $this->assertTrue(true); + } +} diff --git a/tests/Feature/GedmoExtensionsServiceProviderTest.php b/tests/Feature/GedmoExtensionsServiceProviderTest.php new file mode 100644 index 0000000..65c4423 --- /dev/null +++ b/tests/Feature/GedmoExtensionsServiceProviderTest.php @@ -0,0 +1,15 @@ +app['events']->dispatch('doctrine.extensions.booting'); + + $this->assertTrue(true); + } +} diff --git a/tests/Feature/TestCase.php b/tests/Feature/TestCase.php new file mode 100644 index 0000000..f7da531 --- /dev/null +++ b/tests/Feature/TestCase.php @@ -0,0 +1,77 @@ +set('doctrine.managers.default', [ + 'meta' => 'attributes', + 'connection' => 'testbench', + 'paths' => [__DIR__ . '/../Entity'], + 'proxies' => [ + 'auto_generate' => true, + 'path' => __DIR__ . '/../proxy', + 'namespace' => 'Proxy', + ], + ]); + + $config->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + }); + } + + public function setUp(): void + { + parent::setUp(); + + $this->em = $this->app->get(EntityManager::class); + $this->evm = $this->em->getEventManager(); + + (new SchemaTool($this->em))->createSchema($this->em->getMetadataFactory()->getAllMetadata()); + } + + public function tearDown(): void + { + restore_error_handler(); + restore_exception_handler(); + + unset($this->em, $this->evm); + + m::close(); + + parent::tearDown(); + } +} diff --git a/tests/IpTraceable/IpTraceableExtensionTest.php b/tests/IpTraceable/IpTraceableExtensionTest.php deleted file mode 100644 index 95cbe9c..0000000 --- a/tests/IpTraceable/IpTraceableExtensionTest.php +++ /dev/null @@ -1,28 +0,0 @@ -shouldReceive('getClientIp') - ->once()->andReturn('127.0.0.1'); - - $extension = new IpTraceableExtension( - $request - ); - - $extension->addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/tests/IpTraceable/IpTraceableTest.php b/tests/IpTraceable/IpTraceableTest.php deleted file mode 100644 index 81afdce..0000000 --- a/tests/IpTraceable/IpTraceableTest.php +++ /dev/null @@ -1,36 +0,0 @@ -entity = new IpTraceableEntity(); - } - - public function test_can_set_created_from_ip() - { - $this->entity->setCreatedFromIp('CREATED_IP'); - - $this->assertEquals('CREATED_IP', $this->entity->getCreatedFromIp()); - } - - public function test_can_set_updated_from_ip() - { - $this->entity->setUpdatedFromIp('UPDATED_IP'); - - $this->assertEquals('UPDATED_IP', $this->entity->getUpdatedFromIp()); - } -} - -class IpTraceableEntity -{ - use IpTraceable; -} diff --git a/tests/Loggable/LoggableExtensionTest.php b/tests/Loggable/LoggableExtensionTest.php deleted file mode 100644 index 7113fb4..0000000 --- a/tests/Loggable/LoggableExtensionTest.php +++ /dev/null @@ -1,25 +0,0 @@ -addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/tests/Sluggable/SluggableExtensionTest.php b/tests/Sluggable/SluggableExtensionTest.php deleted file mode 100644 index 093d950..0000000 --- a/tests/Sluggable/SluggableExtensionTest.php +++ /dev/null @@ -1,19 +0,0 @@ -addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/tests/SoftDeletes/SoftDeleteableExtensionTest.php b/tests/SoftDeletes/SoftDeleteableExtensionTest.php deleted file mode 100644 index 0c4a0a0..0000000 --- a/tests/SoftDeletes/SoftDeleteableExtensionTest.php +++ /dev/null @@ -1,20 +0,0 @@ -addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertContains(SoftDeleteableFilter::class, $extension->getFilters()); - } -} diff --git a/tests/SoftDeletes/SoftDeletesTest.php b/tests/SoftDeletes/SoftDeletesTest.php deleted file mode 100644 index 034ec4d..0000000 --- a/tests/SoftDeletes/SoftDeletesTest.php +++ /dev/null @@ -1,48 +0,0 @@ -entity = new SoftDeletesEntity(); - } - - public function test_can_set_deleted_at() - { - $date = new DateTime('now'); - - $this->entity->setDeletedAt($date); - - $this->assertEquals($date, $this->entity->getDeletedAt()); - } - - public function test_can_check_if_deleted() - { - $this->assertFalse($this->entity->isDeleted()); - - $this->entity->setDeletedAt(new DateTime('now')); - $this->assertTrue($this->entity->isDeleted()); - } - - public function test_can_restore() - { - $this->entity->setDeletedAt(new DateTime('now')); - $this->assertTrue($this->entity->isDeleted()); - - $this->entity->restore(); - $this->assertFalse($this->entity->isDeleted()); - } -} - -class SoftDeletesEntity -{ - use SoftDeletes; -} diff --git a/tests/Timestamps/TimestampableExtensionTest.php b/tests/Timestamps/TimestampableExtensionTest.php deleted file mode 100644 index 8d43c36..0000000 --- a/tests/Timestamps/TimestampableExtensionTest.php +++ /dev/null @@ -1,19 +0,0 @@ -addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/tests/Timestamps/TimestampsTest.php b/tests/Timestamps/TimestampsTest.php deleted file mode 100644 index 4ef6e37..0000000 --- a/tests/Timestamps/TimestampsTest.php +++ /dev/null @@ -1,40 +0,0 @@ -entity = new TimestampsEntity(); - } - - public function test_can_set_created_at() - { - $date = new DateTime('now'); - - $this->entity->setCreatedAt($date); - - $this->assertEquals($date, $this->entity->getCreatedAt()); - } - - public function test_can_set_updated_at() - { - $date = new DateTime('now'); - - $this->entity->setUpdatedAt($date); - - $this->assertEquals($date, $this->entity->getUpdatedAt()); - } -} - -class TimestampsEntity -{ - use Timestamps; -} diff --git a/tests/Translatable/TranslatableExtensionTest.php b/tests/Translatable/TranslatableExtensionTest.php deleted file mode 100644 index 9857a6e..0000000 --- a/tests/Translatable/TranslatableExtensionTest.php +++ /dev/null @@ -1,36 +0,0 @@ -shouldReceive('getLocale')->once()->andReturn('en'); - - $config = m::mock(Repository::class); - $config->shouldReceive('get') - ->with('app.locale')->once() - ->andReturn('en'); - - $events = m::mock(Dispatcher::class); - $events->shouldReceive('listen') - ->with('locale.changed', m::type('callable')) - ->once(); - - $extension = new TranslatableExtension($app, $config, $events); - - $extension->addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/tests/Tree/NestedSetTest.php b/tests/Tree/NestedSetTest.php deleted file mode 100644 index cdaf60a..0000000 --- a/tests/Tree/NestedSetTest.php +++ /dev/null @@ -1,50 +0,0 @@ -entity = new NestedSetEntity(); - } - - public function test_can_set_root() - { - $this->entity->setRoot(10); - - $this->assertEquals(10, $this->entity->getRoot()); - } - - public function test_can_set_level() - { - $this->entity->setLevel(5); - - $this->assertEquals(5, $this->entity->getLevel()); - } - - public function test_can_set_left() - { - $this->entity->setLeft(3); - - $this->assertEquals(3, $this->entity->getLeft()); - } - - public function test_can_set_right() - { - $this->entity->setRight(2); - - $this->assertEquals(2, $this->entity->getRight()); - } -} - -class NestedSetEntity -{ - use NestedSet; -} diff --git a/tests/Tree/TreeExtensionTest.php b/tests/Tree/TreeExtensionTest.php deleted file mode 100644 index 53650d0..0000000 --- a/tests/Tree/TreeExtensionTest.php +++ /dev/null @@ -1,19 +0,0 @@ -addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/tests/Uploadable/UploadableExtensionTest.php b/tests/Uploadable/UploadableExtensionTest.php deleted file mode 100644 index 831d618..0000000 --- a/tests/Uploadable/UploadableExtensionTest.php +++ /dev/null @@ -1,26 +0,0 @@ -shouldReceive('setAnnotationReader')->with($this->reader)->once(); - - $extension = new UploadableExtension( - $listener - ); - - $extension->addSubscribers( - $this->evm, - $this->em, - $this->reader - ); - - $this->assertEmpty($extension->getFilters()); - } -} diff --git a/workbench/app/Models/.gitkeep b/workbench/app/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php new file mode 100644 index 0000000..1405251 --- /dev/null +++ b/workbench/app/Models/User.php @@ -0,0 +1,45 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; +} diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php new file mode 100644 index 0000000..e8cec9c --- /dev/null +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -0,0 +1,24 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + ) + ->withMiddleware(function (Middleware $middleware) { + // + }) + ->withExceptions(function (Exceptions $exceptions) { + // + })->create(); diff --git a/workbench/bootstrap/providers.php b/workbench/bootstrap/providers.php new file mode 100644 index 0000000..3ac44ad --- /dev/null +++ b/workbench/bootstrap/providers.php @@ -0,0 +1,5 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = User::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/workbench/database/migrations/.gitkeep b/workbench/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..ce9bd15 --- /dev/null +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -0,0 +1,23 @@ +create(); + + UserFactory::new()->create([ + 'name' => 'Test User', + 'email' => 'test@example.com', + ]); + } +} diff --git a/workbench/resources/views/.gitkeep b/workbench/resources/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workbench/routes/console.php b/workbench/routes/console.php new file mode 100644 index 0000000..eff2ed2 --- /dev/null +++ b/workbench/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote')->hourly(); diff --git a/workbench/routes/web.php b/workbench/routes/web.php new file mode 100644 index 0000000..86a06c5 --- /dev/null +++ b/workbench/routes/web.php @@ -0,0 +1,7 @@ +