Compare commits

...
36 Commits
Author SHA1 Message Date
Markus Podar f2a7f9d84f Improve performance for supporting Laravel 8.77+ cast attributes (#1292)
#1289 calls `getReturnType` for every method on every model, which in
turn calls `getReturnTypeFromDocBlock` which has this code:
```php
$phpDocContext = (new ContextFactory())->createFromReflector($reflection);
```
Extracting the docblock is super slow, always has been. Now that we do
this for every method, this adds up a lot.

Performance on a private commercial project _before_ #1289 was introduced:
```
$ time ./artisan ide-helper:models --write --reset >/dev/null

real	0m2.857s
user	0m1.835s
sys	0m0.129s
```
After #1289 :
```
$ time ./artisan ide-helper:models --write --reset >/dev/null

real	0m54.147s
user	0m47.132s
sys	0m1.047s
```

However, in this case we **do not need** the phpdoc fallback (which is
legitimate and by design for many other cases), because also the Laravel
implementation only works by inspecting the _actual type_, see
https://github.com/laravel/framework/blob/e0c2620b57be6416820ea7ca8e46fd2f71d2fe35/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L570-L575
```php
$returnType = (new ReflectionMethod($this, $method))->getReturnType();

return static::$attributeMutatorCache[get_class($this)][$key] = $returnType &&
            $returnType instanceof ReflectionNamedType &&
            $returnType->getName() === Attribute::class &&
            is_callable($this->{$method}()->get);
```

This side-stepping the phpdoc parsing a) still works correctly and b)
brings us back to the previous performance characteristics:
```
time ./artisan ide-helper:models --write --reset >/dev/null

real	0m2.987s
user	0m1.915s
sys	0m0.120s
```
2022-01-03 21:47:54 +01:00
Simon Jønsson 29dd57e1e2 Support Laravel 8.77 Attributes (#1289)
*  Set properties from model functions returning an Attribute

*  Add test for model Attributes

* 🎨 Fix code style

* 🔖 Update changelog

*  Update test to not require php 8

* 🐛 Fix PHP 7.3 incompatibility

*  Update tests to only run when Illuminate Attribute exists
2022-01-03 10:02:18 +01:00
Maduka Jayalath 6244a931a0 Update README.md (#1290)
Shorten links to texts from artisan commands so it will be easy to copy those commands to use.
2022-01-03 07:40:25 +01:00
Michael Newton ebea96213d Improve casts support (#1262)
* support for all possible `Model::$casts` types

* add testing for casts

* update changelog

* update changelog (again?)
2021-11-14 17:40:15 +01:00
Sergiy Petrov 01be015f86 PHP 8.1 Support (#1274)
* test against php 8.1

* fix tests
2021-10-31 14:55:33 +01:00
Silvan Krähenbühl a091b9c94a Allow casts without property (#1267)
* Allow casts without property

* Update changelog
2021-10-25 13:45:30 +02:00
George Boot dd12b7cdcb Generate cleaner model mixin classes (#1268)
* Generate cleaner model mixin classes

* Fix test
2021-10-25 13:44:43 +02:00
Stephan Tijink ae48e1e467 Update dependency composer/composer to 1.10.23 and 2.1.9 due to security issue (#1271)
* Update composer/composer to 2.0.23

* Update composer/composer to 2.1.9

* Also update composer/composer 1.x
2021-10-25 09:09:49 +02:00
Wim Reckmanandlaravel-ide-helper c733bde1ea Move default models filename to config (#1241)
* Move default models filename to config

* Add default value when getting models helper filename from config

* composer fix-style

Co-authored-by: laravel-ide-helper <[email protected]>
2021-08-10 07:12:56 +02:00
atsu.kg 52a4b64656 Consider foreign key constraints with isRelationNullable (#1231)
* Consider foreign key constraints with isRelationNullable

* test: add belongsTo Variation

* command: composer test-regenerate
2021-08-07 13:31:20 +02:00
Barry vd. Heuvel b268871c16 Fix tests 2021-08-04 11:40:36 +02:00
Ajay Dwivedi 2fc239623b Fix #1173 Use FQN for write-mixin (#1174)
* Fix #1173 Use FQN for write-mixin

* Fix #1173 Use FQN for write-mixin: Update Test cases
2021-08-04 11:26:51 +02:00
37f08320ea Make tests pass on Windows (#1205)
* Make all tests pass cross platform

* Add job to run tests on Windows

* Merge test jobs with conditional Windows step for line endings

* Prefix job name with OS

* Update git lf step if statement

* Exclude all tests for Windows except PHP 8.0

* composer fix-style

Co-authored-by: Barry vd. Heuvel <[email protected]>
Co-authored-by: laravel-ide-helper <[email protected]>
2021-08-04 11:22:48 +02:00
ZeroThe2nd b6871d09a4 Bugfix/custom types cause exception (#1228)
* bugfix/custom-types-cause-exception

- Added use statements for DBAL Exception and Type
- Added @throws tag to getPropertiesFromTable method
- Added Type::addType() to properly register types
- Added Try-Catch around Type::addType() to give users a proper error message, then throw the exception up.

* bugfix/custom-types-cause-exception

- Updated CHANGELOG.MD

* bugfix/custom-types-cause-exception

- Fixed `Type enum_string_boolean already exists.` error from being triggered when multiple models are loaded
2021-08-04 11:18:55 +02:00
Karel Faille 3b75048d12 Support variadic parameters in models command (#1234) 2021-08-04 11:13:23 +02:00
Jan Misker b162347aff fix when no enclosingClass can be found (#1239)
In certain situations no enclosingClass can be found, quick fix is to just return then.
2021-08-04 11:12:34 +02:00
Richard van Baarsen f4e0fc3695 Allow for PhpDoc for macros with union types (#1148)
* Allow for PhpDoc for macros with union types

* Replace null coalescing operator

* Test init PhpDoc for macros with parameter union types

* Allow for PhpDoc for macros with union return types

* Add helper method to Macro class

* Add changelog entry

* Format

* Complement changelog with PR link

* Add missing return

* Fix test for PHP < 8

* Rewrite PHP 8 test using eval()

* Remove obsolete test class

* Suppress Psalm errors for undefined ReflectionUnionType class

* Remove unreachable return statement
2021-07-30 08:54:59 +02:00
Alexander StockandMarkus Podar af84c78dec Use platformName to determine db type (#1212)
* Use current connection platform (driver) instead of default connection name to determine used dbms. Previous solution isn't working, when multiple connections with custom names are used.

* chore: add changelog

Co-authored-by: Markus Podar <[email protected]>
2021-06-20 10:38:04 +02:00
Daniël de WitandMarkus Podar e71f3d5fc0 Check for traits recursively (#1216)
* Check for traits recursively

* Remove autoload bool from class_uses_recursive

* Add test

* Update src/Console/ModelsCommand.php

Co-authored-by: Markus Podar <[email protected]>

* Updated changelog

Co-authored-by: Markus Podar <[email protected]>
2021-06-20 10:18:14 +02:00
Markus Podar df670747c3 composer.json: remove --prefer-lowest (#1217)
IMHO it's impractical to expect this package to have to work with the
"first release of Laravel for any major release" even when there are
already sever more releases: no one is/should stick to that version.

This is a re-submit of https://github.com/barryvdh/laravel-ide-helper/pull/1076

See also:
- https://github.com/barryvdh/laravel-ide-helper/pull/1216#issuecomment-831029022
- https://github.com/barryvdh/laravel-ide-helper/pull/1185#issuecomment-831029353
2021-06-20 10:11:57 +02:00
Barry vd. Heuvel 3a4eee9487 Update composer.json 2021-06-12 10:09:56 +02:00
Barry vd. Heuvel eb8c5729ae Update FUNDING.yml 2021-06-12 10:09:29 +02:00
Markus Podar dc5282e9a4 Merge pull request #1211 from madalinignisca/patch-1
Fix link to DBAL types
2021-04-13 20:37:23 +02:00
Madalin IgniscaandMarkus Podar c82529babd Update config/ide-helper.php
Let's hope users know to switch to the version locked by composer.

Co-authored-by: Markus Podar <[email protected]>
2021-04-13 11:46:59 +02:00
Madalin Ignisca 7cf8a51a8b Fix link to DBAL types
Documentation has been moved, old reference landing on 404.
Pointed to 2.x as for the moment we end up with 2.x because of many other package dependencies.
2021-04-12 06:50:05 +02:00
Markus Podarandlaravel-ide-helper 73b1012b92 chore: prepare 2.10.0 release (#1207)
* chore: prepare 2.10.0 release

* composer fix-style

Co-authored-by: laravel-ide-helper <[email protected]>
2021-04-09 08:17:55 +02:00
Terence MarksandTerence Marks ca94ffd99a Fixing issue where incorrect autoloader unregistered (#1210)
Co-authored-by: Terence Marks <[email protected]>
2021-04-09 07:54:00 +02:00
Markus Podar c1ddd30532 Merge pull request #1198 from jenga201/master
Allowing Methods to be set or unset in ModelHooks
2021-04-03 20:55:37 +02:00
Markus Podar dcbe6c953f chore: update changelog 2021-04-03 00:17:10 +02:00
Markus Podar 867a6fd9b4 Merge branch 'master' into jenga201_master 2021-04-03 00:15:42 +02:00
Rob Porter 39a96c3b95 run composer fix-style 2021-04-02 15:30:15 -04:00
Rob Porter 057efb0ee7 Adding tests to Set / unset methods 2021-04-02 15:25:22 -04:00
Markus Podar 1010cdbd26 chore: prepare changelog for 2.9.3 release (#1203) 2021-04-02 19:55:52 +02:00
wimski 2f61602e7a Support both customized namespace factories as well as default resolvable ones (#1201) 2021-04-02 16:32:13 +02:00
Rob Porter 9fe65e7ed3 Updating readme with a ModelHooks set/unset Method example 2021-04-01 16:04:59 -04:00
Rob Porter 16a4297992 Allowing Methods to be set in ModelHooks 2021-04-01 15:55:14 -04:00
42 changed files with 1090 additions and 122 deletions
+2 -9
View File
@@ -1,12 +1,5 @@
# These are supported funding model platforms # These are supported funding model platforms
github: barryvdh github: barryvdh
patreon: # Replace with a single Patreon username custom: ['https://fruitcake.nl']
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
COMPOSER_NO_INTERACTION: 1 COMPOSER_NO_INTERACTION: 1
strategy: strategy:
matrix: matrix:
php: [8.0, 7.4, 7.3] php: [8.1, 8.0, 7.4, 7.3]
lumen: [8.*] lumen: [8.*]
name: P${{ matrix.php }} - Lumen${{ matrix.lumen }} name: P${{ matrix.php }} - Lumen${{ matrix.lumen }}
steps: steps:
@@ -75,7 +75,7 @@ jobs:
COMPOSER_NO_INTERACTION: 1 COMPOSER_NO_INTERACTION: 1
strategy: strategy:
matrix: matrix:
php: [8.0, 7.4, 7.3] php: [8.1, 8.0, 7.4, 7.3]
laravel: [8.*] laravel: [8.*]
name: P${{ matrix.php }} - Laravel${{ matrix.laravel }} name: P${{ matrix.php }} - Laravel${{ matrix.laravel }}
steps: steps:
+15 -7
View File
@@ -12,23 +12,31 @@ on:
jobs: jobs:
php-tests: php-tests:
runs-on: ubuntu-20.04 runs-on: ${{ matrix.os }}
timeout-minutes: 15 timeout-minutes: 15
env: env:
COMPOSER_NO_INTERACTION: 1 COMPOSER_NO_INTERACTION: 1
strategy: strategy:
matrix: matrix:
php: [8.0, 7.4, 7.3] os: [ubuntu-20.04, windows-2019]
php: [8.1, 8.0, 7.4, 7.3]
laravel: [8.*] laravel: [8.*]
dependency-version: [prefer-lowest, prefer-stable] dependency-version: [prefer-lowest, prefer-stable]
exclude: exclude:
- php: 8.0 - php: 8.0
dependency-version: prefer-lowest dependency-version: prefer-lowest
- os: windows-2019
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} php: 7.4
- os: windows-2019
php: 7.3
steps: steps:
- name: Set git to use LF
if: ${{ matrix.os == 'windows-2019' }}
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -37,14 +45,14 @@ jobs:
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
coverage: none coverage: none
extensions: pdo_sqlite extensions: pdo_sqlite, fileinfo
- name: Install dependencies - name: Install dependencies
run: | run: |
composer remove vimeo/psalm --no-update --dev composer remove vimeo/psalm --no-update --dev
composer remove friendsofphp/php-cs-fixer --no-update --dev composer remove friendsofphp/php-cs-fixer --no-update --dev
composer require "laravel/framework:${{ matrix.laravel }}" --no-update --no-progress composer require "laravel/framework:${{ matrix.laravel }}" --no-update --no-progress
composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress composer update --prefer-dist --no-progress
- name: Execute Unit Tests - name: Execute Unit Tests
run: composer test-ci run: composer test-ci
+42 -1
View File
@@ -2,8 +2,48 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
[Next release](https://github.com/barryvdh/laravel-ide-helper/compare/v2.9.3...master) [Next release](https://github.com/barryvdh/laravel-ide-helper/compare/v2.10.0...master)
-------------- --------------
### Added
- Add support for Laravel 8.77 Attributes [\#1289 / SimonJnsson](https://github.com/barryvdh/laravel-ide-helper/pull/1289)
### Added
- Add support for cast types `decimal:*`, `encrypted:*`, `immutable_date`, `immutable_datetime`, `custom_datetime`, and `immutable_custom_datetime` [#1262 / miken32](https://github.com/barryvdh/laravel-ide-helper/pull/1262)
### Fixed
- Fix recursively searching for `HasFactory` and `Macroable` traits [\#1216 / daniel-de-wit](https://github.com/barryvdh/laravel-ide-helper/pull/1216)
### Fixed
- Use platformName to determine db type when casting boolean types [\#1212 / stockalexander](https://github.com/barryvdh/laravel-ide-helper/pull/1212)
### Added
- Add support of variadic parameters in `ide-helper:models` [\#1234 / shaffe-fr](https://github.com/barryvdh/laravel-ide-helper/pull/1234)
- Add support of custom casts without properties [\#1267 / sparclex](https://github.com/barryvdh/laravel-ide-helper/pull/1267)
### Changed
- Move default models helper filename to config [\#1241 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1241)
2021-06-18, 2.10.1
------------------
### Added
- Added Type registration according to [Custom Mapping Types documentation](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types)
### Fixed
- Fixing issue where configured custom_db_types could cause a DBAL exception to be thrown while running `ide-helper:models`
2021-04-09, 2.10.0
------------------
### Added
- Allowing Methods to be set or unset in ModelHooks [\#1198 / jenga201](https://github.com/barryvdh/laravel-ide-helper/pull/1198)\
Note: the visibility of `\Barryvdh\LaravelIdeHelper\Console\ModelsCommand::setMethod` has been changed to **public**!
### Fixed
- Fixing issue where incorrect autoloader unregistered [\#1210 / tezhm](https://github.com/barryvdh/laravel-ide-helper/pull/1210)
2021-04-02, 2.9.3
-----------------
### Fixed
- Support both customized namespace factories as well as default resolvable ones [\#1201 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1201)
2021-04-01, 2.9.2 2021-04-01, 2.9.2
----------------- -----------------
@@ -21,6 +61,7 @@ All notable changes to this project will be documented in this file.
- Generate PHPDoc for Laravel 8.x factories [\#1074 / ahmed-aliraqi](https://github.com/barryvdh/laravel-ide-helper/pull/1074) - Generate PHPDoc for Laravel 8.x factories [\#1074 / ahmed-aliraqi](https://github.com/barryvdh/laravel-ide-helper/pull/1074)
- Add a comment to a property like table columns [\#1168 / biiiiiigmonster](https://github.com/barryvdh/laravel-ide-helper/pull/1168) - Add a comment to a property like table columns [\#1168 / biiiiiigmonster](https://github.com/barryvdh/laravel-ide-helper/pull/1168)
- Added `post_migrate` hook to run commands after a migration [\#1163 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1163) - Added `post_migrate` hook to run commands after a migration [\#1163 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1163)
- Allow for PhpDoc for macros with union types [\#1148 / riesjart](https://github.com/barryvdh/laravel-ide-helper/pull/1148)
### Fixed ### Fixed
- Error when generating helper for invokable classes [\#1124 / standaniels](https://github.com/barryvdh/laravel-ide-helper/pull/1124) - Error when generating helper for invokable classes [\#1124 / standaniels](https://github.com/barryvdh/laravel-ide-helper/pull/1124)
+6 -3
View File
@@ -68,9 +68,9 @@ If for some reason you want manually control this:
_Check out [this Laracasts video](https://laracasts.com/series/how-to-be-awesome-in-phpstorm/episodes/15) for a quick introduction/explanation!_ _Check out [this Laracasts video](https://laracasts.com/series/how-to-be-awesome-in-phpstorm/episodes/15) for a quick introduction/explanation!_
- [`php artisan ide-helper:generate` - PHPDoc generation for Laravel Facades ](#automatic-phpdoc-generation-for-laravel-facades) - `php artisan ide-helper:generate` - [PHPDoc generation for Laravel Facades ](#automatic-phpdoc-generation-for-laravel-facades)
- [`php artisan ide-helper:models` - PHPDocs for models](#automatic-PHPDocs-for-models) - `php artisan ide-helper:models` - [PHPDocs for models](#automatic-PHPDocs-for-models)
- [`php artisan ide-helper:meta` - PhpStorm Meta file](#phpstorm-meta-for-container-instances) - `php artisan ide-helper:meta` - [PhpStorm Meta file](#phpstorm-meta-for-container-instances)
Note: You do need CodeComplice for Sublime Text: https://github.com/spectacles/CodeComplice Note: You do need CodeComplice for Sublime Text: https://github.com/spectacles/CodeComplice
@@ -166,6 +166,7 @@ php artisan ide-helper:models "App\Models\Post"
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post query() * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post query()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post whereTitle($value) * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post whereTitle($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post forAuthors(\User ...$authors)
* … * …
*/ */
``` ```
@@ -301,6 +302,8 @@ class MyCustomHook implements ModelHookInterface
} }
$command->setProperty('custom', 'string', true, false, 'My custom property'); $command->setProperty('custom', 'string', true, false, 'My custom property');
$command->unsetMethod('method');
$command->setMethod('method', $command->getMethodType($model, '\Some\Class'), ['$param']);
} }
} }
``` ```
+1 -1
View File
@@ -23,7 +23,7 @@
"php": "^7.3 || ^8.0", "php": "^7.3 || ^8.0",
"ext-json": "*", "ext-json": "*",
"barryvdh/reflection-docblock": "^2.0.6", "barryvdh/reflection-docblock": "^2.0.6",
"composer/composer": "^1.6 || ^2", "composer/composer": "^1.10.23 || ^2.1.9",
"doctrine/dbal": "^2.6 || ^3", "doctrine/dbal": "^2.6 || ^3",
"illuminate/console": "^8", "illuminate/console": "^8",
"illuminate/filesystem": "^8", "illuminate/filesystem": "^8",
+12 -1
View File
@@ -13,6 +13,17 @@ return [
'filename' => '_ide_helper.php', 'filename' => '_ide_helper.php',
/*
|--------------------------------------------------------------------------
| Models filename
|--------------------------------------------------------------------------
|
| The default filename for the models helper file
|
*/
'models_filename' => '_ide_helper_models.php',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Where to write the PhpStorm specific meta file | Where to write the PhpStorm specific meta file
@@ -206,7 +217,7 @@ return [
| The value of the array is an array of type mappings. Key is the name of the custom type, | The value of the array is an array of type mappings. Key is the name of the custom type,
| (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in
| our case it is 'json_array'. Doctrine types are listed here: | our case it is 'json_array'. Doctrine types are listed here:
| http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html | https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#types
| |
| So to support jsonb in your models when working with Postgres, just add the following entry to the array below: | So to support jsonb in your models when working with Postgres, just add the following entry to the array below:
| |
+12 -8
View File
@@ -84,7 +84,7 @@ class MetaCommand extends Command
// Needs to run before exception handler is registered // Needs to run before exception handler is registered
$factories = $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : []; $factories = $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : [];
$this->registerClassAutoloadExceptions(); $ourAutoloader = $this->registerClassAutoloadExceptions();
$bindings = []; $bindings = [];
foreach ($this->getAbstracts() as $abstract) { foreach ($this->getAbstracts() as $abstract) {
@@ -106,7 +106,7 @@ class MetaCommand extends Command
} }
} }
$this->unregisterClassAutoloadExceptions(); $this->unregisterClassAutoloadExceptions($ourAutoloader);
$content = $this->view->make('meta', [ $content = $this->view->make('meta', [
'bindings' => $bindings, 'bindings' => $bindings,
@@ -143,12 +143,16 @@ class MetaCommand extends Command
/** /**
* Register an autoloader the throws exceptions when a class is not found. * Register an autoloader the throws exceptions when a class is not found.
*
* @return callable
*/ */
protected function registerClassAutoloadExceptions() protected function registerClassAutoloadExceptions(): callable
{ {
spl_autoload_register(function ($class) { $autoloader = function ($class) {
throw new \ReflectionException("Class '$class' not found."); throw new \ReflectionException("Class '$class' not found.");
}); };
spl_autoload_register($autoloader);
return $autoloader;
} }
/** /**
@@ -167,11 +171,11 @@ class MetaCommand extends Command
/** /**
* Remove our custom autoloader that we pushed onto the autoload stack * Remove our custom autoloader that we pushed onto the autoload stack
*
* @param callable $ourAutoloader
*/ */
private function unregisterClassAutoloadExceptions() private function unregisterClassAutoloadExceptions(callable $ourAutoloader): void
{ {
$autoloadFunctions = spl_autoload_functions();
$ourAutoloader = array_pop($autoloadFunctions);
spl_autoload_unregister($ourAutoloader); spl_autoload_unregister($ourAutoloader);
} }
} }
+151 -24
View File
@@ -17,8 +17,11 @@ use Barryvdh\Reflection\DocBlock\Context;
use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
use Barryvdh\Reflection\DocBlock\Tag; use Barryvdh\Reflection\DocBlock\Tag;
use Composer\Autoload\ClassMapGenerator; use Composer\Autoload\ClassMapGenerator;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Types\Type;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -33,6 +36,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use phpDocumentor\Reflection\Types\ContextFactory; use phpDocumentor\Reflection\Types\ContextFactory;
use ReflectionClass; use ReflectionClass;
@@ -76,7 +80,11 @@ class ModelsCommand extends Command
* @var string * @var string
*/ */
protected $name = 'ide-helper:models'; protected $name = 'ide-helper:models';
protected $filename = '_ide_helper_models.php';
/**
* @var string
*/
protected $filename;
/** /**
* The console command description. * The console command description.
@@ -100,6 +108,10 @@ class ModelsCommand extends Command
* @var bool[string] * @var bool[string]
*/ */
protected $nullableColumns = []; protected $nullableColumns = [];
/**
* @var string[]
*/
protected $foreignKeyConstraintsColumns = [];
/** /**
* During initialization we use Laravels Date Facade to * During initialization we use Laravels Date Facade to
@@ -125,7 +137,8 @@ class ModelsCommand extends Command
*/ */
public function handle() public function handle()
{ {
$filename = $this->option('filename'); $this->filename = $this->laravel['config']->get('ide-helper.models_filename', '_ide_helper_models.php');
$filename = $this->option('filename') ?? $this->filename;
$this->write = $this->option('write'); $this->write = $this->option('write');
$this->write_mixin = $this->option('write-mixin'); $this->write_mixin = $this->option('write-mixin');
$this->dirs = array_merge( $this->dirs = array_merge(
@@ -193,7 +206,7 @@ class ModelsCommand extends Command
protected function getOptions() protected function getOptions()
{ {
return [ return [
['filename', 'F', InputOption::VALUE_OPTIONAL, 'The path to the helper file', $this->filename], ['filename', 'F', InputOption::VALUE_OPTIONAL, 'The path to the helper file'],
['dir', 'D', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, ['dir', 'D', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'The model dir, supports glob patterns', [], ], 'The model dir, supports glob patterns', [], ],
['write', 'W', InputOption::VALUE_NONE, 'Write to Model file'], ['write', 'W', InputOption::VALUE_NONE, 'Write to Model file'],
@@ -344,11 +357,24 @@ class ModelsCommand extends Command
{ {
$casts = $model->getCasts(); $casts = $model->getCasts();
foreach ($casts as $name => $type) { foreach ($casts as $name => $type) {
if (Str::startsWith($type, 'decimal:')) {
$type = 'decimal';
} elseif (Str::startsWith($type, 'custom_datetime:')) {
$type = 'date';
} elseif (Str::startsWith($type, 'immutable_custom_datetime:')) {
$type = 'immutable_date';
} elseif (Str::startsWith($type, 'encrypted:')) {
$type = Str::after($type, ':');
}
switch ($type) { switch ($type) {
case 'encrypted':
$realType = 'mixed';
break;
case 'boolean': case 'boolean':
case 'bool': case 'bool':
$realType = 'boolean'; $realType = 'boolean';
break; break;
case 'decimal':
case 'string': case 'string':
$realType = 'string'; $realType = 'string';
break; break;
@@ -373,6 +399,10 @@ class ModelsCommand extends Command
case 'datetime': case 'datetime':
$realType = $this->dateClass; $realType = $this->dateClass;
break; break;
case 'immutable_date':
case 'immutable_datetime':
$realType = '\Carbon\CarbonImmutable';
break;
case 'collection': case 'collection':
$realType = '\Illuminate\Support\Collection'; $realType = '\Illuminate\Support\Collection';
break; break;
@@ -381,6 +411,7 @@ class ModelsCommand extends Command
// the `$type` until the `:` // the `$type` until the `:`
$type = strtok($type, ':'); $type = strtok($type, ':');
$realType = class_exists($type) ? ('\\' . $type) : 'mixed'; $realType = class_exists($type) ? ('\\' . $type) : 'mixed';
$this->setProperty($name, null, true, true);
break; break;
} }
@@ -415,6 +446,8 @@ class ModelsCommand extends Command
* Load the properties from the database table. * Load the properties from the database table.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
*
* @throws DBALException If custom field failed to register
*/ */
public function getPropertiesFromTable($model) public function getPropertiesFromTable($model)
{ {
@@ -426,6 +459,14 @@ class ModelsCommand extends Command
$platformName = $databasePlatform->getName(); $platformName = $databasePlatform->getName();
$customTypes = $this->laravel['config']->get("ide-helper.custom_db_types.{$platformName}", []); $customTypes = $this->laravel['config']->get("ide-helper.custom_db_types.{$platformName}", []);
foreach ($customTypes as $yourTypeName => $doctrineTypeName) { foreach ($customTypes as $yourTypeName => $doctrineTypeName) {
try {
if (!Type::hasType($yourTypeName)) {
Type::addType($yourTypeName, get_class(Type::getType($doctrineTypeName)));
}
} catch (DBALException $exception) {
$this->error("Failed registering custom db type \"$yourTypeName\" as \"$doctrineTypeName\"");
throw $exception;
}
$databasePlatform->registerDoctrineTypeMapping($yourTypeName, $doctrineTypeName); $databasePlatform->registerDoctrineTypeMapping($yourTypeName, $doctrineTypeName);
} }
@@ -440,6 +481,7 @@ class ModelsCommand extends Command
return; return;
} }
$this->setForeignKeys($schema, $table);
foreach ($columns as $column) { foreach ($columns as $column) {
$name = $column->getName(); $name = $column->getName();
if (in_array($name, $model->getDates())) { if (in_array($name, $model->getDates())) {
@@ -463,7 +505,7 @@ class ModelsCommand extends Command
$type = 'integer'; $type = 'integer';
break; break;
case 'boolean': case 'boolean':
switch (config('database.default')) { switch ($platformName) {
case 'sqlite': case 'sqlite':
case 'mysql': case 'mysql':
$type = 'integer'; $type = 'integer';
@@ -519,6 +561,9 @@ class ModelsCommand extends Command
if ($methods) { if ($methods) {
sort($methods); sort($methods);
foreach ($methods as $method) { foreach ($methods as $method) {
$reflection = new \ReflectionMethod($model, $method);
$type = $this->getReturnTypeFromReflection($reflection);
$isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
if ( if (
Str::startsWith($method, 'get') && Str::endsWith( Str::startsWith($method, 'get') && Str::endsWith(
$method, $method,
@@ -528,12 +573,25 @@ class ModelsCommand extends Command
//Magic get<name>Attribute //Magic get<name>Attribute
$name = Str::snake(substr($method, 3, -9)); $name = Str::snake(substr($method, 3, -9));
if (!empty($name)) { if (!empty($name)) {
$reflection = new \ReflectionMethod($model, $method);
$type = $this->getReturnType($reflection); $type = $this->getReturnType($reflection);
$type = $this->getTypeInModel($model, $type); $type = $this->getTypeInModel($model, $type);
$comment = $this->getCommentFromDocBlock($reflection); $comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, $type, true, null, $comment); $this->setProperty($name, $type, true, null, $comment);
} }
} elseif ($isAttribute) {
$name = Str::snake($method);
$types = $this->getAttributeReturnType($model, $method);
if ($types->has('get')) {
$type = $this->getTypeInModel($model, $types['get']);
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, $type, true, null, $comment);
}
if ($types->has('set')) {
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, null, null, true, $comment);
}
} elseif ( } elseif (
Str::startsWith($method, 'set') && Str::endsWith( Str::startsWith($method, 'set') && Str::endsWith(
$method, $method,
@@ -543,7 +601,6 @@ class ModelsCommand extends Command
//Magic set<name>Attribute //Magic set<name>Attribute
$name = Str::snake(substr($method, 3, -9)); $name = Str::snake(substr($method, 3, -9));
if (!empty($name)) { if (!empty($name)) {
$reflection = new \ReflectionMethod($model, $method);
$comment = $this->getCommentFromDocBlock($reflection); $comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, null, null, true, $comment); $this->setProperty($name, null, null, true, $comment);
} }
@@ -551,7 +608,6 @@ class ModelsCommand extends Command
//Magic set<name>Attribute //Magic set<name>Attribute
$name = Str::camel(substr($method, 5)); $name = Str::camel(substr($method, 5));
if (!empty($name)) { if (!empty($name)) {
$reflection = new \ReflectionMethod($model, $method);
$comment = $this->getCommentFromDocBlock($reflection); $comment = $this->getCommentFromDocBlock($reflection);
$args = $this->getParameters($reflection); $args = $this->getParameters($reflection);
//Remove the first ($query) argument //Remove the first ($query) argument
@@ -582,8 +638,6 @@ class ModelsCommand extends Command
&& !Str::startsWith($method, 'get') && !Str::startsWith($method, 'get')
) { ) {
//Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
$reflection = new \ReflectionMethod($model, $method);
if ($returnType = $reflection->getReturnType()) { if ($returnType = $reflection->getReturnType()) {
$type = $returnType instanceof ReflectionNamedType $type = $returnType instanceof ReflectionNamedType
? $returnType->getName() ? $returnType->getName()
@@ -714,6 +768,11 @@ class ModelsCommand extends Command
$fkProp = $reflectionObj->getProperty('foreignKey'); $fkProp = $reflectionObj->getProperty('foreignKey');
$fkProp->setAccessible(true); $fkProp->setAccessible(true);
if ($relation === 'belongsTo') {
return isset($this->nullableColumns[$fkProp->getValue($relationObj)]) ||
!in_array($fkProp->getValue($relationObj), $this->foreignKeyConstraintsColumns, true);
}
return isset($this->nullableColumns[$fkProp->getValue($relationObj)]); return isset($this->nullableColumns[$fkProp->getValue($relationObj)]);
} }
@@ -749,7 +808,7 @@ class ModelsCommand extends Command
} }
} }
protected function setMethod($name, $type = '', $arguments = [], $comment = '') public function setMethod($name, $type = '', $arguments = [], $comment = '')
{ {
$methods = array_change_key_case($this->methods, CASE_LOWER); $methods = array_change_key_case($this->methods, CASE_LOWER);
@@ -761,6 +820,18 @@ class ModelsCommand extends Command
} }
} }
public function unsetMethod($name)
{
unset($this->methods[strtolower($name)]);
}
public function getMethodType(Model $model, string $classType)
{
$modelName = $this->getClassNameInDestinationFile($model, get_class($model));
$builder = $this->getClassNameInDestinationFile($model, $classType);
return $builder . '|' . $modelName;
}
/** /**
* @param string $class * @param string $class
* @return string * @return string
@@ -879,6 +950,13 @@ class ModelsCommand extends Command
if (!$phpdocMixin->getText()) { if (!$phpdocMixin->getText()) {
$mixinDocComment = preg_replace("/\s\*\s*\n/", '', $mixinDocComment); $mixinDocComment = preg_replace("/\s\*\s*\n/", '', $mixinDocComment);
} }
foreach ($phpdoc->getTagsByName('mixin') as $tag) {
if (Str::startsWith($tag->getContent(), 'IdeHelper')) {
$phpdoc->deleteTag($tag);
}
}
$docComment = $serializer->getDocComment($phpdoc);
} }
if ($this->write) { if ($this->write) {
@@ -900,11 +978,15 @@ class ModelsCommand extends Command
} }
$classname = $this->write_mixin ? $mixinClassName : $classname; $classname = $this->write_mixin ? $mixinClassName : $classname;
$output = "namespace {$namespace}{\n{$docComment}\n\t{$keyword}class {$classname} extends \Eloquent "; $output = "namespace {$namespace}{\n{$docComment}\n\t{$keyword}class {$classname} ";
if ($interfaceNames) { if (!$this->write_mixin) {
$interfaces = implode(', \\', $interfaceNames); $output .= "extends \Eloquent ";
$output .= "implements \\{$interfaces} ";
if ($interfaceNames) {
$interfaces = implode(', \\', $interfaceNames);
$output .= "implements \\{$interfaces} ";
}
} }
return $output . "{}\n}\n\n"; return $output . "{}\n}\n\n";
@@ -923,7 +1005,8 @@ class ModelsCommand extends Command
$paramsWithDefault = []; $paramsWithDefault = [];
/** @var \ReflectionParameter $param */ /** @var \ReflectionParameter $param */
foreach ($method->getParameters() as $param) { foreach ($method->getParameters() as $param) {
$paramStr = '$' . $param->getName(); $paramStr = $param->isVariadic() ? '...$' . $param->getName() : '$' . $param->getName();
if ($paramType = $this->getParamType($method, $param)) { if ($paramType = $this->getParamType($method, $param)) {
$paramStr = $paramType . ' ' . $paramStr; $paramStr = $paramType . ' ' . $paramStr;
} }
@@ -987,6 +1070,36 @@ class ModelsCommand extends Command
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false); return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false);
} }
protected function getAttributeReturnType(Model $model, string $method): Collection
{
/** @var Attribute $attribute */
$attribute = $model->{$method}();
return collect([
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
'set' => $attribute->set ? optional(new \ReflectionFunction($attribute->set))->getReturnType() : null,
])
->filter()
->map(function ($type) {
if ($type instanceof \ReflectionUnionType) {
$types =collect($type->getTypes())
/** @var ReflectionType $reflectionType */
->map(function ($reflectionType) {
return collect($this->extractReflectionTypes($reflectionType));
})
->flatten();
} else {
$types = collect($this->extractReflectionTypes($type));
}
if ($type->allowsNull()) {
$types->push('null');
}
return $types->join('|');
});
}
protected function getReturnType(\ReflectionMethod $reflection): ?string protected function getReturnType(\ReflectionMethod $reflection): ?string
{ {
$type = $this->getReturnTypeFromDocBlock($reflection); $type = $this->getReturnTypeFromDocBlock($reflection);
@@ -1070,7 +1183,7 @@ class ModelsCommand extends Command
*/ */
protected function getSoftDeleteMethods($model) protected function getSoftDeleteMethods($model)
{ {
$traits = class_uses(get_class($model), true); $traits = class_uses_recursive($model);
if (in_array('Illuminate\\Database\\Eloquent\\SoftDeletes', $traits)) { if (in_array('Illuminate\\Database\\Eloquent\\SoftDeletes', $traits)) {
$modelName = $this->getClassNameInDestinationFile($model, get_class($model)); $modelName = $this->getClassNameInDestinationFile($model, get_class($model));
$builder = $this->getClassNameInDestinationFile($model, \Illuminate\Database\Query\Builder::class); $builder = $this->getClassNameInDestinationFile($model, \Illuminate\Database\Query\Builder::class);
@@ -1091,18 +1204,18 @@ class ModelsCommand extends Command
return; return;
} }
$traits = class_uses(get_class($model), true); $modelName = get_class($model);
$traits = class_uses_recursive($modelName);
if (!in_array('Illuminate\\Database\\Eloquent\\Factories\\HasFactory', $traits)) { if (!in_array('Illuminate\\Database\\Eloquent\\Factories\\HasFactory', $traits)) {
return; return;
} }
$modelName = get_class($model);
$modelBaseName = class_basename($modelName);
$factory = "\Database\Factories\\{$modelBaseName}Factory";
if ($modelName::newFactory()) { if ($modelName::newFactory()) {
$factory = get_class($modelName::newFactory()); $factory = get_class($modelName::newFactory());
} else {
$factory = Factory::resolveFactoryName($modelName);
} }
$factory = '\\' . trim($factory, '\\'); $factory = '\\' . trim($factory, '\\');
@@ -1200,7 +1313,7 @@ class ModelsCommand extends Command
; ;
$className = trim($className, '\\'); $className = trim($className, '\\');
$writingToExternalFile = !$this->write; $writingToExternalFile = !$this->write || $this->write_mixin;
$classIsNotInExternalFile = $reflection->getName() !== $className; $classIsNotInExternalFile = $reflection->getName() !== $className;
$forceFQCN = $this->laravel['config']->get('ide-helper.force_fqn', false); $forceFQCN = $this->laravel['config']->get('ide-helper.force_fqn', false);
@@ -1283,7 +1396,7 @@ class ModelsCommand extends Command
$docComment ?? '', $docComment ?? '',
$matches $matches
); );
$type = $matches[1] ?? null; $type = $matches[1] ?? '';
if (strpos($type, '|') !== false) { if (strpos($type, '|') !== false) {
$types = explode('|', $type); $types = explode('|', $type);
@@ -1384,4 +1497,18 @@ class ModelsCommand extends Command
$hookInstance->run($this, $model); $hookInstance->run($this, $model);
} }
} }
/**
* @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema
* @param string $table
* @throws DBALException
*/
protected function setForeignKeys($schema, $table)
{
foreach ($schema->listTableForeignKeys($table) as $foreignKeyConstraint) {
foreach ($foreignKeyConstraint->getLocalColumns() as $columnName) {
$this->foreignKeyConstraintsColumns[] = $columnName;
}
}
}
} }
+1 -1
View File
@@ -296,7 +296,7 @@ class Generator
return !$reflection->isInternal() && $reflection->getName() === $class; return !$reflection->isInternal() && $reflection->getName() === $class;
}) })
->filter(function ($class) { ->filter(function ($class) {
$traits = class_uses($class); $traits = class_uses_recursive($class);
// Filter only classes with the macroable trait // Filter only classes with the macroable trait
return isset($traits[Macroable::class]); return isset($traits[Macroable::class]);
+33 -5
View File
@@ -42,8 +42,16 @@ class Macro extends Method
// Add macro parameters if they are missed in original docblock // Add macro parameters if they are missed in original docblock
if (!$this->phpdoc->hasTag('param')) { if (!$this->phpdoc->hasTag('param')) {
foreach ($method->getParameters() as $parameter) { foreach ($method->getParameters() as $parameter) {
$type = $parameter->hasType() ? $parameter->getType()->getName() : 'mixed'; $reflectionType = $parameter->getType();
$type .= $parameter->hasType() && $parameter->getType()->allowsNull() ? '|null' : '';
$type = $this->concatReflectionTypes($reflectionType);
/** @psalm-suppress UndefinedClass */
if ($reflectionType && !$reflectionType instanceof \ReflectionUnionType && $reflectionType->allowsNull()) {
$type .= '|null';
}
$type = $type ?: 'mixed';
$name = $parameter->isVariadic() ? '...' : ''; $name = $parameter->isVariadic() ? '...' : '';
$name .= '$' . $parameter->getName(); $name .= '$' . $parameter->getName();
@@ -57,14 +65,31 @@ class Macro extends Method
$builder = EloquentBuilder::class; $builder = EloquentBuilder::class;
$return = $method->getReturnType(); $return = $method->getReturnType();
$type = $return->getName(); $type = $this->concatReflectionTypes($return);
$type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : '';
$type .= $return->allowsNull() ? '|null' : ''; /** @psalm-suppress UndefinedClass */
if (!$return instanceof \ReflectionUnionType) {
$type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : '';
$type .= $return->allowsNull() ? '|null' : '';
}
$this->phpdoc->appendTag(Tag::createInstance("@return {$type}")); $this->phpdoc->appendTag(Tag::createInstance("@return {$type}"));
} }
} }
protected function concatReflectionTypes(?\ReflectionType $type): string
{
/** @psalm-suppress UndefinedClass */
$returnTypes = $type instanceof \ReflectionUnionType
? $type->getTypes()
: [$type];
return Collection::make($returnTypes)
->filter()
->map->getName()
->implode('|');
}
protected function addLocationToPhpDoc() protected function addLocationToPhpDoc()
{ {
if ($this->method->name === '__invoke') { if ($this->method->name === '__invoke') {
@@ -73,6 +98,9 @@ class Macro extends Method
$enclosingClass = $this->method->getClosureScopeClass(); $enclosingClass = $this->method->getClosureScopeClass();
} }
if (!$enclosingClass) {
return;
}
/** @var \ReflectionMethod $enclosingMethod */ /** @var \ReflectionMethod $enclosingMethod */
$enclosingMethod = Collection::make($enclosingClass->getMethods()) $enclosingMethod = Collection::make($enclosingClass->getMethods())
->first(function (\ReflectionMethod $method) { ->first(function (\ReflectionMethod $method) {
+6 -3
View File
@@ -45,12 +45,15 @@ class EloquentCommandTest extends TestCase
$this->assertMatchesTxtSnapshot($actualContent); $this->assertMatchesTxtSnapshot($actualContent);
$display = $tester->getDisplay(); $display = $tester->getDisplay();
$this->assertRegExp( $this->assertMatchesRegularExpression(
';Unexpected no document on Illuminate\\\Database\\\Eloquent\\\Model;', ';Unexpected no document on Illuminate\\\Database\\\Eloquent\\\Model;',
$display $display
); );
$this->assertRegExp( $modelClassFilePath = preg_quote(
';Wrote expected docblock to .*/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php;', str_replace('/', DIRECTORY_SEPARATOR, '/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php')
);
$this->assertMatchesRegularExpression(
';Wrote expected docblock to .*' . $modelClassFilePath . ';',
$display $display
); );
} }
@@ -8,6 +8,7 @@ use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
use Barryvdh\LaravelIdeHelper\Tests\TestCase; use Barryvdh\LaravelIdeHelper\Tests\TestCase;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Mockery\MockInterface; use Mockery\MockInterface;
use stdClass;
class MetaCommandTest extends TestCase class MetaCommandTest extends TestCase
{ {
@@ -37,6 +38,38 @@ class MetaCommandTest extends TestCase
self::assertStringContainsString('override(', $this->mockFilesystemOutput); self::assertStringContainsString('override(', $this->mockFilesystemOutput);
} }
public function testUnregisterAutoloader(): void
{
$current = spl_autoload_functions();
$appended = function () {
};
$this->app->bind('registers-autoloader', function () use ($appended) {
spl_autoload_register($appended);
return new stdClass();
});
$this->mockFilesystem();
/** @var Filesystem|MockInterface $mockFileSystem */
$mockFileSystem = $this->app->make(Filesystem::class);
$this->instance('files', $mockFileSystem);
$mockFileSystem
->shouldReceive('getRequire')
->andReturnUsing(function ($__path, $__data) {
return (static function () use ($__path, $__data) {
extract($__data, EXTR_SKIP);
return require $__path;
})();
});
$this->artisan('ide-helper:meta');
self::assertSame(array_merge($current, [$appended]), spl_autoload_functions());
}
/** /**
* Get package providers. * Get package providers.
* *
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class Simple extends Model
{
public function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name === null ? null : ucfirst($name);
}
);
}
/**
* ide-helper does not recognize this method being an Attribute
* because the method has no actual return type;
* phpdoc is ignored here deliberately due to performance reasons and also
* isn't supported by Laravel itself.
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function notAnAttribute()
{
return new Attribute(
function (?string $value): ?string {
return $value;
},
function (?string $value): ?string {
return $value;
}
);
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
class Test extends AbstractModelsCommand
{
protected function setUp(): void
{
parent::setUp();
if (!class_exists('\Illuminate\Database\Eloquent\Casts\Attribute')) {
$this->markTestSkipped('This test requires Laravel 8.77 or newer');
}
}
public function test(): void
{
$command = $this->app->make(ModelsCommand::class);
$tester = $this->runCommand($command, [
'--write' => true,
]);
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
$this->assertMatchesMockedSnapshot();
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models\Simple
*
* @property integer $id
* @property string|null $name
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
* @mixin \Eloquent
*/
class Simple extends Model
{
public function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name === null ? null : ucfirst($name);
}
);
}
/**
* ide-helper does not recognize this method being an Attribute
* because the method has no actual return type;
* phpdoc is ignored here deliberately due to performance reasons and also
* isn't supported by Laravel itself.
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function notAnAttribute()
{
return new Attribute(
function (?string $value): ?string {
return $value;
},
function (?string $value): ?string {
return $value;
}
);
}
}
@@ -18,22 +18,24 @@ class Test extends AbstractModelsCommand
]); ]);
if (PHP_VERSION_ID >= 80000) { if (PHP_VERSION_ID >= 80000) {
$errors = <<<TXT $errors = [
Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicBelongsTo() : Attempt to read property "created_at" on null 'Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicBelongsTo() : Attempt to read property "created_at" on null',
Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasMany() : Attempt to read property "created_at" on null 'Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasMany() : Attempt to read property "created_at" on null',
Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasOne() : Attempt to read property "created_at" on null 'Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasOne() : Attempt to read property "created_at" on null',
TXT; ];
} else { } else {
$errors = <<<TXT $errors = [
Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicBelongsTo() : Trying to get property 'created_at' of non-object "Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicBelongsTo() : Trying to get property 'created_at' of non-object",
Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasMany() : Trying to get property 'created_at' of non-object "Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasMany() : Trying to get property 'created_at' of non-object",
Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasOne() : Trying to get property 'created_at' of non-object "Error resolving relation model of Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DynamicRelations\Models\Dynamic:dynamicHasOne() : Trying to get property 'created_at' of non-object",
TXT; ];
} }
$this->assertSame(0, $tester->getStatusCode()); $this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay()); $this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
$this->assertStringContainsString($errors, $tester->getDisplay()); foreach ($errors as $error) {
$this->assertStringContainsString($error, $tester->getDisplay());
}
$this->assertMatchesMockedSnapshot(); $this->assertMatchesMockedSnapshot();
} }
} }
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\CustomSpace;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\ModelWithCustomNamespace;
use Illuminate\Database\Eloquent\Factories\Factory;
class ModelWithCustomNamespaceFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = ModelWithCustomNamespace::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}
@@ -4,17 +4,17 @@ declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories; namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Getter\Models\Simple; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\ModelWithFactory;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
class SimpleFactory extends Factory class ModelWithFactoryFactory extends Factory
{ {
/** /**
* The name of the factory's corresponding model. * The name of the factory's corresponding model.
* *
* @var string * @var string
*/ */
protected $model = Simple::class; protected $model = ModelWithFactory::class;
/** /**
* Define the model's default state. * Define the model's default state.
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\ModelWithNestedFactory;
use Illuminate\Database\Eloquent\Factories\Factory;
class ModelWithNestedFactoryFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = ModelWithNestedFactory::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}
@@ -4,11 +4,11 @@ declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models; namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\SimpleFactory; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\CustomSpace\ModelWithCustomNamespaceFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Simple extends Model class ModelWithCustomNamespace extends Model
{ {
use HasFactory; use HasFactory;
@@ -19,6 +19,6 @@ class Simple extends Model
*/ */
protected static function newFactory() protected static function newFactory()
{ {
return SimpleFactory::new(); return ModelWithCustomNamespaceFactory::new();
} }
} }
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ModelWithFactory extends Model
{
use HasFactory;
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
class ModelWithNestedFactory extends ModelWithFactory
{
}
@@ -6,7 +6,10 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand; use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
use Closure;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Support\Str;
class Test extends AbstractModelsCommand class Test extends AbstractModelsCommand
{ {
@@ -18,6 +21,8 @@ class Test extends AbstractModelsCommand
); );
} }
Factory::guessFactoryNamesUsing(static::getFactoryNameResolver());
$command = $this->app->make(ModelsCommand::class); $command = $this->app->make(ModelsCommand::class);
$tester = $this->runCommand($command, [ $tester = $this->runCommand($command, [
@@ -29,4 +34,19 @@ class Test extends AbstractModelsCommand
$this->assertStringNotContainsString('not found', $tester->getDisplay()); $this->assertStringNotContainsString('not found', $tester->getDisplay());
$this->assertMatchesMockedSnapshot(); $this->assertMatchesMockedSnapshot();
} }
public static function getFactoryNameResolver(): Closure
{
// This mimics the default resolver, but with adjusted test namespaces.
// Illuminate\Database\Eloquent\Factories\Factory::resolveFactoryName
return function (string $modelName): string {
$appNamespace = 'Barryvdh\\LaravelIdeHelper\\Tests\\Console\\ModelsCommand\\Factories\\';
$modelName = Str::startsWith($modelName, $appNamespace . 'Models\\')
? Str::after($modelName, $appNamespace . 'Models\\')
: Str::after($modelName, $appNamespace);
return $appNamespace . 'Factories\\' . $modelName . 'Factory';
};
}
} }
@@ -4,6 +4,79 @@ declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models; namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\CustomSpace\ModelWithCustomNamespaceFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\ModelWithCustomNamespace
*
* @method static \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\CustomSpace\ModelWithCustomNamespaceFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithCustomNamespace newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithCustomNamespace newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithCustomNamespace query()
* @mixin \Eloquent
*/
class ModelWithCustomNamespace extends Model
{
use HasFactory;
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return ModelWithCustomNamespaceFactory::new();
}
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\ModelWithFactory
*
* @method static \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\ModelWithFactoryFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithFactory newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithFactory newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithFactory query()
* @mixin \Eloquent
*/
class ModelWithFactory extends Model
{
use HasFactory;
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\ModelWithNestedFactory
*
* @method static \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\ModelWithNestedFactoryFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithNestedFactory newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithNestedFactory newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithNestedFactory query()
* @mixin \Eloquent
*/
class ModelWithNestedFactory extends ModelWithFactory
{
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -19,38 +92,3 @@ class ModelWithoutFactory extends Model
{ {
use HasFactory; use HasFactory;
} }
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\SimpleFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\Simple
*
* @property integer $id
* @method static \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\SimpleFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
* @mixin \Eloquent
*/
class Simple extends Model
{
use HasFactory;
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return SimpleFactory::new();
}
}
@@ -183,6 +183,6 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\GeneratePhpdocWi
* @method static \Illuminate\Database\Eloquent\Builder|Post whereYearNullable($value) * @method static \Illuminate\Database\Eloquent\Builder|Post whereYearNullable($value)
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class IdeHelperPost extends \Eloquent {} class IdeHelperPost {}
} }
@@ -34,5 +34,6 @@ class CustomCast extends Model
'extended_casted_property_with_static_return_docblock' => ExtendedSelfCastingCasterWithStaticDocblockReturn::class, 'extended_casted_property_with_static_return_docblock' => ExtendedSelfCastingCasterWithStaticDocblockReturn::class,
'extended_casted_property_with_this_return_docblock' => ExtendedSelfCastingCasterWithThisDocblockReturn::class, 'extended_casted_property_with_this_return_docblock' => ExtendedSelfCastingCasterWithThisDocblockReturn::class,
'casted_property_with_static_return_docblock_and_param' => SelfCastingCasterWithStaticDocblockReturn::class . ':param', 'casted_property_with_static_return_docblock_and_param' => SelfCastingCasterWithStaticDocblockReturn::class . ':param',
'cast_without_property' => CustomCasterWithReturnType::class,
]; ];
} }
@@ -34,6 +34,7 @@ use Illuminate\Database\Eloquent\Model;
* @property ExtendedSelfCastingCasterWithStaticDocblockReturn $extended_casted_property_with_static_return_docblock * @property ExtendedSelfCastingCasterWithStaticDocblockReturn $extended_casted_property_with_static_return_docblock
* @property ExtendedSelfCastingCasterWithThisDocblockReturn $extended_casted_property_with_this_return_docblock * @property ExtendedSelfCastingCasterWithThisDocblockReturn $extended_casted_property_with_this_return_docblock
* @property SelfCastingCasterWithStaticDocblockReturn $casted_property_with_static_return_docblock_and_param * @property SelfCastingCasterWithStaticDocblockReturn $casted_property_with_static_return_docblock_and_param
* @property \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastedProperty $cast_without_property
* @method static \Illuminate\Database\Eloquent\Builder|CustomCast newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|CustomCast newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|CustomCast newQuery() * @method static \Illuminate\Database\Eloquent\Builder|CustomCast newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|CustomCast query() * @method static \Illuminate\Database\Eloquent\Builder|CustomCast query()
@@ -68,5 +69,6 @@ class CustomCast extends Model
'extended_casted_property_with_static_return_docblock' => ExtendedSelfCastingCasterWithStaticDocblockReturn::class, 'extended_casted_property_with_static_return_docblock' => ExtendedSelfCastingCasterWithStaticDocblockReturn::class,
'extended_casted_property_with_this_return_docblock' => ExtendedSelfCastingCasterWithThisDocblockReturn::class, 'extended_casted_property_with_this_return_docblock' => ExtendedSelfCastingCasterWithThisDocblockReturn::class,
'casted_property_with_static_return_docblock_and_param' => SelfCastingCasterWithStaticDocblockReturn::class . ':param', 'casted_property_with_static_return_docblock_and_param' => SelfCastingCasterWithStaticDocblockReturn::class . ':param',
'cast_without_property' => CustomCasterWithReturnType::class,
]; ];
} }
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class CustomMethod implements ModelHookInterface
{
public function run(ModelsCommand $command, Model $model): void
{
$command->setMethod('custom', $command->getMethodType($model, Builder::class), ['$custom']);
}
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface;
use Illuminate\Database\Eloquent\Model;
class UnsetMethod implements ModelHookInterface
{
public function run(ModelsCommand $command, Model $model): void
{
$command->unsetMethod('query');
}
}
@@ -6,7 +6,9 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand; use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks\CustomMethod;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks\CustomProperty; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks\CustomProperty;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks\UnsetMethod;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Mockery; use Mockery;
@@ -24,6 +26,8 @@ class Test extends AbstractModelsCommand
], ],
'model_hooks' => [ 'model_hooks' => [
CustomProperty::class, CustomProperty::class,
CustomMethod::class,
UnsetMethod::class,
], ],
]); ]);
} }
@@ -71,9 +75,9 @@ use Illuminate\Database\Eloquent\Model;
* *
* @property int $id * @property int $id
* @property-read string $custom * @property-read string $custom
* @method static \Illuminate\Database\Eloquent\Builder|Simple custom($custom)
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
* @mixin \Eloquent * @mixin \Eloquent
*/ */
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class BelongsToVariation extends Model
{
public function notNullColumnWithForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'not_null_column_with_foreign_key_constraint');
}
public function notNullColumnWithNoForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'not_null_column_with_no_foreign_key_constraint');
}
public function nullableColumnWithForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'nullable_column_with_foreign_key_constraint');
}
public function nullableColumnWithNoForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'nullable_column_with_no_foreign_key_constraint');
}
}
@@ -4,6 +4,59 @@ declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models; namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models\BelongsToVariation
*
* @property integer $id
* @property integer $not_null_column_with_foreign_key_constraint
* @property integer $not_null_column_with_no_foreign_key_constraint
* @property integer|null $nullable_column_with_foreign_key_constraint
* @property integer|null $nullable_column_with_no_foreign_key_constraint
* @property-read BelongsToVariation $notNullColumnWithForeignKeyConstraint
* @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint
* @property-read BelongsToVariation|null $nullableColumnWithForeignKeyConstraint
* @property-read BelongsToVariation|null $nullableColumnWithNoForeignKeyConstraint
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation query()
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNotNullColumnWithForeignKeyConstraint($value)
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNotNullColumnWithNoForeignKeyConstraint($value)
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNullableColumnWithForeignKeyConstraint($value)
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNullableColumnWithNoForeignKeyConstraint($value)
* @mixin \Eloquent
*/
class BelongsToVariation extends Model
{
public function notNullColumnWithForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'not_null_column_with_foreign_key_constraint');
}
public function notNullColumnWithNoForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'not_null_column_with_no_foreign_key_constraint');
}
public function nullableColumnWithForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'nullable_column_with_foreign_key_constraint');
}
public function nullableColumnWithNoForeignKeyConstraint(): BelongsTo
{
return $this->belongsTo(self::class, 'nullable_column_with_no_foreign_key_constraint');
}
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\ModelsOtherNamespace\AnotherModel; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\ModelsOtherNamespace\AnotherModel;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Traits\HasTestRelations; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Traits\HasTestRelations;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -20,15 +73,15 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany;
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models\Simple * Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models\Simple
* *
* @property integer $id * @property integer $id
* @property-read Simple $relationBelongsTo * @property-read Simple|null $relationBelongsTo
* @property-read AnotherModel $relationBelongsToInAnotherNamespace * @property-read AnotherModel|null $relationBelongsToInAnotherNamespace
* @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationBelongsToMany * @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationBelongsToMany
* @property-read int|null $relation_belongs_to_many_count * @property-read int|null $relation_belongs_to_many_count
* @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationBelongsToManyWithSub * @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationBelongsToManyWithSub
* @property-read int|null $relation_belongs_to_many_with_sub_count * @property-read int|null $relation_belongs_to_many_with_sub_count
* @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationBelongsToManyWithSubAnother * @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationBelongsToManyWithSubAnother
* @property-read int|null $relation_belongs_to_many_with_sub_another_count * @property-read int|null $relation_belongs_to_many_with_sub_another_count
* @property-read AnotherModel $relationBelongsToSameNameAsColumn * @property-read AnotherModel|null $relationBelongsToSameNameAsColumn
* @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationHasMany * @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationHasMany
* @property-read int|null $relation_has_many_count * @property-read int|null $relation_has_many_count
* @property-read Simple|null $relationHasOne * @property-read Simple|null $relationHasOne
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Models;
use Illuminate\Database\Eloquent\Model;
class SimpleCast extends Model
{
protected $casts = [
'cast_to_int' => 'int',
'cast_to_integer' => 'integer',
'cast_to_real' => 'real',
'cast_to_float' => 'float',
'cast_to_double' => 'double',
'cast_to_decimal' => 'decimal:4',
'cast_to_string' => 'string',
'cast_to_bool' => 'bool',
'cast_to_boolean' => 'boolean',
'cast_to_object' => 'object',
'cast_to_array' => 'array',
'cast_to_json' => 'json',
'cast_to_collection' => 'collection',
'cast_to_date' => 'date',
'cast_to_datetime' => 'datetime',
'cast_to_custom_datetime' => 'custom_datetime:Y-m-d H:i:s',
'cast_to_immutable_date' => 'immutable_date',
'cast_to_immutable_custom_datetime' => 'immutable_custom_datetime:Y-m-d H:i:s',
'cast_to_immutable_datetime' => 'immutable_datetime',
'cast_to_timestamp' => 'timestamp',
'cast_to_encrypted' => 'encrypted',
'cast_to_encrypted_array' => 'encrypted:array',
'cast_to_encrypted_collection' => 'encrypted:collection',
'cast_to_encrypted_json' => 'encrypted:json',
'cast_to_encrypted_object' => 'encrypted:object',
];
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
class Test extends AbstractModelsCommand
{
public function test(): void
{
$command = $this->app->make(ModelsCommand::class);
$tester = $this->runCommand($command, [
'--write' => true,
]);
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
$this->assertMatchesMockedSnapshot();
}
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Models\SimpleCast
*
* @property integer $cast_to_int
* @property integer $cast_to_integer
* @property float $cast_to_real
* @property float $cast_to_float
* @property float $cast_to_double
* @property string $cast_to_decimal
* @property string $cast_to_string
* @property boolean $cast_to_bool
* @property boolean $cast_to_boolean
* @property object $cast_to_object
* @property array $cast_to_array
* @property array $cast_to_json
* @property \Illuminate\Support\Collection $cast_to_collection
* @property \Illuminate\Support\Carbon $cast_to_date
* @property \Illuminate\Support\Carbon $cast_to_datetime
* @property \Illuminate\Support\Carbon $cast_to_custom_datetime
* @property \Carbon\CarbonImmutable $cast_to_immutable_date
* @property \Carbon\CarbonImmutable $cast_to_immutable_custom_datetime
* @property \Carbon\CarbonImmutable $cast_to_immutable_datetime
* @property integer $cast_to_timestamp
* @property mixed $cast_to_encrypted
* @property array $cast_to_encrypted_array
* @property \Illuminate\Support\Collection $cast_to_encrypted_collection
* @property array $cast_to_encrypted_json
* @property object $cast_to_encrypted_object
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast query()
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToArray($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToBool($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToBoolean($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToCollection($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToCustomDatetime($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToDate($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToDatetime($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToDecimal($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToDouble($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToEncrypted($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToEncryptedArray($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToEncryptedCollection($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToEncryptedJson($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToEncryptedObject($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToFloat($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToImmutableCustomDatetime($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToImmutableDate($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToImmutableDatetime($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToInt($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToInteger($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToJson($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToObject($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToReal($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToString($value)
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast whereCastToTimestamp($value)
* @mixin \Eloquent
*/
class SimpleCast extends Model
{
protected $casts = [
'cast_to_int' => 'int',
'cast_to_integer' => 'integer',
'cast_to_real' => 'real',
'cast_to_float' => 'float',
'cast_to_double' => 'double',
'cast_to_decimal' => 'decimal:4',
'cast_to_string' => 'string',
'cast_to_bool' => 'bool',
'cast_to_boolean' => 'boolean',
'cast_to_object' => 'object',
'cast_to_array' => 'array',
'cast_to_json' => 'json',
'cast_to_collection' => 'collection',
'cast_to_date' => 'date',
'cast_to_datetime' => 'datetime',
'cast_to_custom_datetime' => 'custom_datetime:Y-m-d H:i:s',
'cast_to_immutable_date' => 'immutable_date',
'cast_to_immutable_custom_datetime' => 'immutable_custom_datetime:Y-m-d H:i:s',
'cast_to_immutable_datetime' => 'immutable_datetime',
'cast_to_timestamp' => 'timestamp',
'cast_to_encrypted' => 'encrypted',
'cast_to_encrypted_array' => 'encrypted:array',
'cast_to_encrypted_collection' => 'encrypted:collection',
'cast_to_encrypted_json' => 'encrypted:json',
'cast_to_encrypted_object' => 'encrypted:object',
];
}
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Variadic\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class Simple extends Model
{
public function scopeWhereVariadic(Builder $query, ...$values): void
{
}
public function scopeWhereTypedVariadic(Builder $query, int ...$values): void
{
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Variadic;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
class Test extends AbstractModelsCommand
{
public function test(): void
{
$command = $this->app->make(ModelsCommand::class);
$tester = $this->runCommand($command, [
'--write' => true,
]);
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
$this->assertMatchesMockedSnapshot();
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Variadic\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Variadic\Models\Simple
*
* @property integer $id
* @method static Builder|Simple newModelQuery()
* @method static Builder|Simple newQuery()
* @method static Builder|Simple query()
* @method static Builder|Simple whereId($value)
* @method static Builder|Simple whereTypedVariadic(int ...$values)
* @method static Builder|Simple whereVariadic(...$values)
* @mixin \Eloquent
*/
class Simple extends Model
{
public function scopeWhereVariadic(Builder $query, ...$values): void
{
}
public function scopeWhereTypedVariadic(Builder $query, int ...$values): void
{
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class BelongsToVariationTable extends Migration
{
public function up(): void
{
Schema::create('belongs_to_variations', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('not_null_column_with_foreign_key_constraint');
$table->integer('not_null_column_with_no_foreign_key_constraint');
$table->integer('nullable_column_with_foreign_key_constraint')->nullable();
$table->integer('nullable_column_with_no_foreign_key_constraint')->nullable();
$table->foreign('not_null_column_with_foreign_key_constraint')->references('id')->on('belongs_to_variations');
$table->foreign('nullable_column_with_foreign_key_constraint')->references('id')->on('belongs_to_variations');
});
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SimpleCastsTable extends Migration
{
public function up(): void
{
Schema::create('simple_casts', static function (Blueprint $table) {
$table->string('cast_to_int');
$table->string('cast_to_integer');
$table->string('cast_to_real');
$table->string('cast_to_float');
$table->string('cast_to_double');
$table->string('cast_to_decimal');
$table->string('cast_to_string');
$table->string('cast_to_bool');
$table->string('cast_to_boolean');
$table->string('cast_to_object');
$table->string('cast_to_array');
$table->string('cast_to_json');
$table->string('cast_to_collection');
$table->string('cast_to_date');
$table->string('cast_to_datetime');
$table->string('cast_to_custom_datetime');
$table->string('cast_to_immutable_date');
$table->string('cast_to_immutable_custom_datetime');
$table->string('cast_to_immutable_datetime');
$table->string('cast_to_timestamp');
$table->string('cast_to_encrypted');
$table->string('cast_to_encrypted_array');
$table->string('cast_to_encrypted_collection');
$table->string('cast_to_encrypted_json');
$table->string('cast_to_encrypted_object');
});
}
}
+23
View File
@@ -188,6 +188,29 @@ class MacroTest extends TestCase
$this->assertEquals('@return \stdClass|null rrrrrrr', $this->tagsToString($phpdoc, 'return')); $this->assertEquals('@return \stdClass|null rrrrrrr', $this->tagsToString($phpdoc, 'return'));
} }
public function testInitPhpDocParamsWithUnionTypes(): void
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped('This test requires PHP 8.0 or higher');
}
$phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP'
return new ReflectionFunction(
/**
* Test docblock.
*/
function (\Stringable|string $a = null): \Stringable|string|null {
return $a;
}
);
PHP));
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock', $phpdoc->getText());
$this->assertEquals('@param \Stringable|string|null $a', $this->tagsToString($phpdoc, 'param'));
$this->assertEquals('@return \Stringable|string|null', $this->tagsToString($phpdoc, 'return'));
}
protected function tagsToString(DocBlock $docBlock, string $name) protected function tagsToString(DocBlock $docBlock, string $name)
{ {
$tags = $docBlock->getTagsByName($name); $tags = $docBlock->getTagsByName($name);