Compare commits

..
84 Commits
Author SHA1 Message Date
Markus Podar 771770e56c Merge pull request #1304 from mfn/mfn-release
Prepare 2.12 release
2022-01-23 17:50:53 +01:00
Barry vd. Heuvel 88353535f2 Update composer.json 2022-01-23 17:46:42 +01:00
mfn acb2df9dfb normalize composer.json 2022-01-23 16:39:03 +00:00
Markus Podar 7052b4f708 Prepare 2.12 release
### Added
- Add support for custom casts that using `Castable` [#1287 / binotaliu](https://github.com/barryvdh/laravel-ide-helper/pull/1287)
- Added Laravel 9 support [#1297 / rcerljenko](https://github.com/barryvdh/laravel-ide-helper/pull/1297)
2022-01-23 17:38:31 +01:00
Yehuda Katz ac1e27167d Disabled PHPCS (#1301)
PHPCS is a common development tool. It makes sense to disable it in addition to Eclipse/IntelliJ formatting disable
2022-01-20 14:27:58 +01:00
de64d665e0 added laravel 9 support (#1297)
* added laravel 9 support

* added exclude directives to matrix

* fixed matrix

* Update composer.json

* Update run-integration-tests.yml

* Update CHANGELOG.md

Co-authored-by: Ricardo Čerljenko <[email protected]>
Co-authored-by: Barry vd. Heuvel <[email protected]>
2022-01-13 15:38:59 +01:00
Markus Podar 93d987336b Adapt/fix changelog now that a new release was made (#1294)
Triggered by https://github.com/barryvdh/laravel-ide-helper/pull/1287#issuecomment-1008344636
2022-01-09 22:26:59 +01:00
BinotaLIU 744a9cf380 support Castables (#1287) 2022-01-09 18:52:15 +01:00
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
Markus Podar c8ebf563a0 chore: prepare 2.9.2 release (#1197) 2021-04-01 20:56:14 +02:00
Markus Podar 843067b907 Merge pull request #1196 from ahmed-aliraqi/master
Fix ide-helper:models exception if model doesn't have factory
2021-04-01 20:32:08 +02:00
Ahmed Fathy f0f7051012 formatting 2021-04-01 10:57:05 +02:00
Ahmed Fathy e1af93f43b update changelog 2021-03-31 23:10:46 +02:00
Ahmed Fathy b19b1d69ba testing 2021-03-31 23:03:55 +02:00
Ahmed Fathy 92d7f2e706 fix ide-helper:models error if model doesn't have factory 2021-03-29 23:28:18 +02:00
wimskiandlaravel-ide-helper f0c138c4fd Model hooks (#945)
* Model hooks

* composer fix-style

* Fix test for ModelHooks

Co-authored-by: laravel-ide-helper <[email protected]>
2021-03-29 20:49:09 +02:00
Markus Podar b8d5fc3663 Merge pull request #1193 from netpok/skip_generate_in_phpunit
Skip post_migrate when running tests
2021-03-23 17:47:25 +01:00
netpok df994eb767 Merge branch 'master' into skip_generate_in_phpunit 2021-03-23 09:59:38 +01:00
Markus Podar 0cae143470 Merge pull request #1192 from netpok/add_missing_changelog_entry
Add missing changelog entry for #1184
2021-03-22 20:15:17 +01:00
Balázs Váradi c2dec69d64 Remove unused import 2021-03-22 10:03:46 +01:00
Balázs Váradi dedd69e8b4 Remove already resolved issue from psalm baseline 2021-03-22 10:03:12 +01:00
Balázs Váradi 40c02c524d Add changelog entry 2021-03-22 10:02:17 +01:00
Balázs Váradi 04c1139223 Skip post_migrate when running tests 2021-03-22 09:54:32 +01:00
Balázs Váradi 172e430c12 Add missing changelog entry 2021-03-22 09:49:30 +01:00
netpok 244e936b74 Remove dead code and unused helper (#1183)
* Remove unnecessary check

* Remove helper class
2021-03-17 12:21:59 +01:00
netpok f7f7e9e65a Add default values to array_merge (#1184) 2021-03-17 11:56:50 +01:00
Barry vd. Heuvel 1a813703fe Update CHANGELOG.md 2021-03-17 10:08:59 +01:00
Markus Podar d58731cd9b Merge pull request #1178 from barryvdh/mp-release
chore: adapt for release of 2.9.1
2021-03-15 22:09:25 +01:00
Markus Podar b246d9ab23 chore: adapt for release of 2.9.1 2021-03-15 21:00:43 +01:00
Markus Podar 8d8302ff6a chore: add missing changelog entries (#1176)
* chore: mention the fix from https://github.com/barryvdh/laravel-ide-helper/pull/1005

* chore: mention https://github.com/barryvdh/laravel-ide-helper/pull/1163
2021-03-15 20:22:08 +01:00
netpok 202395b50f Auto generate models (#1163)
* Add illuminate/events as suggestion

* Add post migration to config

* Add listener to generate model helper

* Add event listeners if enabled

* Fix config file format

* Add docblock for shouldRun flag

* Allow running multiple commands after migrations

* Simplify config

* Change default value
2021-03-15 18:57:20 +01:00
biiiiiigmonsterandMarkus Podar c5c9b2b951 Add comments like table columns. (#1168)
* add comment with @method & @property-read & property-write in models

* add readme comment

* add readme comment

* add comment tag unit-test

* add comment tag unit-test

* both a getter and a setter has a @comment test

* Update README.md

improve README

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

* Update CHANGELOG.md

Co-authored-by: Markus Podar <[email protected]>
2021-03-15 07:52:11 +01:00
c5e18beff1 Fix Relative class names are not converted to fully-qualified class names (FQCNs) (#1005)
* Fix https://github.com/barryvdh/laravel-ide-helper/issues/627

* Fix codestyle

* Init

* composer fix-style

* Actualize

* Fix

* normalize composer.json

* Fix

* composer fix-style

* Replace NamespaceUses with UsesResolver

* Add UsesResolver tests

* Add MethodTest::testClassAliases

* Fix code style

* Get rid of laravel support helpers

* Make class UsesResolver stateless, update tests for it

* composer fix-style

Co-authored-by: Andrii Savluk <[email protected]>
Co-authored-by: laravel-ide-helper <[email protected]>
Co-authored-by: SavKS <[email protected]>
Co-authored-by: Taras Fomin <[email protected]>
2021-03-15 07:45:23 +01:00
Markus Podar 6c4dcd9049 Merge pull request #1164 from wilsenhc/readme-fix
Fix typo in README
2021-02-27 22:25:34 +01:00
Wilsen Hernández aaade4eab9 Fix typo in README 2021-02-27 11:29:39 -04:00
Markus Podar b21324b20e Merge pull request #1156 from ZaidBarghouthi/fix-zb-extra-quote
Remove extra single quote
2021-02-19 07:23:25 +01:00
Zaid J. Barghouthi db83fb8b6b Remove extra single quote 2021-02-19 07:48:56 +02:00
yaegassy 4f42af1fa1 Apply "@PSR12" in php-cs-fixer (#1150)
* Apply "@PSR12" in php-cs-fixer

* Run fix-style
2021-02-05 13:57:35 +01:00
Markus Podarandlaravel-ide-helper f0959c1184 Update changelog and also automagically apply style fix (#1140)
* changelog: re-order entry after recent merges in master

* composer fix-style

Co-authored-by: laravel-ide-helper <[email protected]>
2021-01-11 09:34:16 +01:00
Fabio Ivona 73c502d5c5 Fix broken ReflectionUnionTypes (#1132)
* Fix exceptions on ReflectionUnionTypes
this commit fixes exceptions thrown on ReflectionUnionType::isBuiltIn() and ReflectionUnionType::getNme() called on php8 union types

* Fix exceptions on ReflectionUnionTypes
this commit fixes exceptions thrown on ReflectionUnionType::isBuiltIn() and ReflectionUnionType::getNme() called on php8 union types

* add check for php8.0

* fix static analysis error

* fix $types variable undefined

* fix failing test

* fix failing test

* fixed style with php-cs-fixer

* add test for union types in parameters and return type

* add test for nullable union types in parameters and return type

* updated CHANGELOG.md
2021-01-11 09:15:22 +01:00
Markus Podarandlaravel-ide-helper 3376522d68 changelog: re-order entry after recent merges in master (#1138)
* changelog: re-order entry after recent merges in master

* composer fix-style

Co-authored-by: laravel-ide-helper <[email protected]>
2021-01-11 09:14:32 +01:00
Markus Podarandlaravel-ide-helper a83df51ad4 tests: fix assertion (#1139)
* tests: fix assertion

* composer fix-style

Co-authored-by: laravel-ide-helper <[email protected]>
2021-01-11 09:14:12 +01:00
Stan Daniëls fd30d1190d Fix issues with parsing invokable macros (#1124)
* Fix issues with parsing invokable macros

* Add macro test

# Conflicts:
#	tests/MacroTest.php
2021-01-09 13:24:28 +01:00
Ahmed Fathy 4f13ba85bd Generate PHPDoc for laravel 8.x factories (#1074)
* Generate PHPDoc for laravel 8.x factories

* formatting

* sorting imports

* fix code style

* fix tests in laravel 7.x and lower

* use markTestSkipped

* fix test failing ~_~

* add  argument

* determine the laravel vertion is 8.2.x or upper

* use version_compare instead of artisan command

* use correct namespace

* formatting

* formatting

* formatting

* update next release notes
2021-01-06 13:38:26 +01:00
Markus Podar 0fee1c47d3 Update changelog / releases (#1134) 2020-12-30 18:12:38 +01:00
Markus Podar 64a6b90258 [2.9] Drop support for: Laravel 6, Laravel 7, PHP 7.2 and add doctrine/dbal ^3 (#1114)
* composer.json: drop support for PHP 7.2/Laravel 6+7 and bump dependencies

* tests: remove framework version tests

They don't apply anymore, we're Laravel 8+ only from now on

* changelog: update for ending support for L6/7 and PHP7.2
2020-12-29 11:11:05 +01:00
Aleksei Lebedev 25cc68c75f Macro::initPhpDoc() will save original docblock if present. (#1116)
* `Macro::initPhpDoc()` will save original docblock if present.

* Added `MacroTest::testInitPhpDocClosureWithoutDocBlock()`.

* Mock class rename.

* Code cleanup.
2020-12-23 12:26:18 +01:00
Aleksei Lebedev 36c4406b3a Alias will grab macros from \Illuminate\Database\Eloquent\Builder too (#1118)
* `Alias` will grab macros from `\Illuminate\Database\Eloquent\Builder` too.

* `@return` will contains `|static` for Eloquent Builder macros which return Eloquent Builder instance.

* `Macro` tests.

* `Alias::detectMethods()` tests.

* Mock classes rename.
2020-12-21 11:15:47 +01:00
Barry vd. Heuvel 9ba2f5c557 Update run-static-analysis.yml (#1131) 2020-12-21 09:34:43 +01:00
69 changed files with 2896 additions and 214 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']
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: 7.4 php-version: 8.0
coverage: none coverage: none
- name: Install dependencies - name: Install dependencies
+8 -13
View File
@@ -18,15 +18,8 @@ jobs:
COMPOSER_NO_INTERACTION: 1 COMPOSER_NO_INTERACTION: 1
strategy: strategy:
matrix: matrix:
php: [8.0, 7.4, 7.3, 7.2] php: [8.1, 8.0, 7.4, 7.3]
lumen: [8.*, 7.*, 6.*] lumen: [8.*]
exclude:
- lumen: 6.*
php: 8.0
- lumen: 7.*
php: 8.0
- lumen: 8.*
php: 7.2
name: P${{ matrix.php }} - Lumen${{ matrix.lumen }} name: P${{ matrix.php }} - Lumen${{ matrix.lumen }}
steps: steps:
- name: Checkout code - name: Checkout code
@@ -82,11 +75,13 @@ jobs:
COMPOSER_NO_INTERACTION: 1 COMPOSER_NO_INTERACTION: 1
strategy: strategy:
matrix: matrix:
php: [7.4, 7.3, 7.2] php: [8.1, 8.0, 7.4, 7.3]
laravel: [8.*, 7.*, 6.*] laravel: [8.*]
exclude: exclude:
- laravel: 8.* - php: 7.4
php: 7.2 laravel: 9.*
- php: 7.3
laravel: 9.*
name: P${{ matrix.php }} - Laravel${{ matrix.laravel }} name: P${{ matrix.php }} - Laravel${{ matrix.laravel }}
steps: steps:
- name: Checkout code - name: Checkout code
@@ -13,6 +13,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.0
coverage: none
extensions: pdo_sqlite
- name: Install dependencies - name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest run: composer install --prefer-dist --no-progress --no-suggest
+20 -10
View File
@@ -12,25 +12,35 @@ 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, 7.2] os: [ubuntu-20.04, windows-2019]
laravel: [8.*, 7.*, 6.*] php: [8.1, 8.0, 7.4, 7.3]
laravel: [8.*, 9.*]
dependency-version: [prefer-lowest, prefer-stable] dependency-version: [prefer-lowest, prefer-stable]
exclude: exclude:
- laravel: 8.*
php: 7.2
- php: 8.0 - php: 8.0
dependency-version: prefer-lowest dependency-version: prefer-lowest
- php: 7.4
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} laravel: 9.*
- php: 7.3
laravel: 9.*
- os: windows-2019
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
@@ -39,14 +49,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
+2 -4
View File
@@ -2,12 +2,10 @@
// Share common rules between non-test and test files // Share common rules between non-test and test files
return [ return [
// PSR12 from https://github.com/FriendsOfPHP/PHP-CS-Fixer/pull/4943 '@PSR12' => true,
'@PSR2' => true,
'blank_line_after_opening_tag' => true, 'blank_line_after_opening_tag' => true,
'braces' => [ 'braces' => [
// Not-yet-implemented 'allow_single_line_anonymous_class_with_empty_body' => true,
// 'allow_single_line_anonymous_class_with_empty_body' => true,
], ],
'compact_nullable_typehint' => true, 'compact_nullable_typehint' => true,
'declare_equal_normalize' => true, 'declare_equal_normalize' => true,
+78 -1
View File
@@ -2,9 +2,86 @@
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.8.1...master) [Next release](https://github.com/barryvdh/laravel-ide-helper/compare/v2.12.0...master)
-------------- --------------
2022-01-23, 2.12.0
------------------
### Added
- Add support for custom casts that using `Castable` [#1287 / binotaliu](https://github.com/barryvdh/laravel-ide-helper/pull/1287)
- Added Laravel 9 support [#1297 / rcerljenko](https://github.com/barryvdh/laravel-ide-helper/pull/1297)
2022-01-03, 2.11.0
------------------
### Added
- Add support for Laravel 8.77 Attributes [\#1289 / SimonJnsson](https://github.com/barryvdh/laravel-ide-helper/pull/1289)
- 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)
- 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)
### Fixed
- Fix recursively searching for `HasFactory` and `Macroable` traits [\#1216 / daniel-de-wit](https://github.com/barryvdh/laravel-ide-helper/pull/1216)
- Use platformName to determine db type when casting boolean types [\#1212 / stockalexander](https://github.com/barryvdh/laravel-ide-helper/pull/1212)
### 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) [\#1228 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1241)
### Fixed
- Fixing issue where configured custom_db_types could cause a DBAL exception to be thrown while running `ide-helper:models` [\#1228 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1241)
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
-----------------
### Added
- Model hooks for adding custom information from external sources to model classes through the ModelsCommand [\#945 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/945)
### Fixed
- Fix ide-helper:models exception if model doesn't have factory [\#1196 / ahmed-aliraqi](https://github.com/barryvdh/laravel-ide-helper/pull/1196)
- Running tests triggering post_migrate hooks [\#1193 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1193)
- Array_merge error when config is cached prior to package install [\#1184 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1184)
2021-03-15, 2.9.1
-----------------
### Added
- 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)
- 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
- Error when generating helper for invokable classes [\#1124 / standaniels](https://github.com/barryvdh/laravel-ide-helper/pull/1124)
- Fix broken ReflectionUnionTypes [\#1132 / def-studio](https://github.com/barryvdh/laravel-ide-helper/pull/1132)
- Relative class names are not converted to fully-qualified class names [\#1005 / SavKS](https://github.com/barryvdh/laravel-ide-helper/pull/1005)
2020-12-30, 2.9.0
-----------------
### Changed
- Dropped support for Laravel 6 and Laravel 7, as well as support for PHP 7.2 and added support for doctrine/dbal:^3 [\#1114 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1114)
### Fixed
- `Macro::initPhpDoc()` will save original docblock if present [\#1116 / LastDragon-ru](https://github.com/barryvdh/laravel-ide-helper/pull/1116)
- `Alias` will grab macros from `\Illuminate\Database\Eloquent\Builder` too [\#1118 / LastDragon-ru](https://github.com/barryvdh/laravel-ide-helper/pull/1118)
2020-12-08, 2.8.2
-----------------
### Added ### Added
- Fix phpdoc generate for custom cast with parameter [\#986 / artelkr](https://github.com/barryvdh/laravel-ide-helper/pull/986) - Fix phpdoc generate for custom cast with parameter [\#986 / artelkr](https://github.com/barryvdh/laravel-ide-helper/pull/986)
- Created a possibility to add custom relation type [\#987 / efinder2](https://github.com/barryvdh/laravel-ide-helper/pull/987) - Created a possibility to add custom relation type [\#987 / efinder2](https://github.com/barryvdh/laravel-ide-helper/pull/987)
+77 -4
View File
@@ -14,6 +14,9 @@ Generation is done based on the files in your project, so they are always up-to-
- [Usage](#usage) - [Usage](#usage)
- [Automatic PHPDoc generation for Laravel Facades](#automatic-phpdoc-generation-for-laravel-facades) - [Automatic PHPDoc generation for Laravel Facades](#automatic-phpdoc-generation-for-laravel-facades)
- [Automatic PHPDocs for models](#automatic-phpdocs-for-models) - [Automatic PHPDocs for models](#automatic-phpdocs-for-models)
- [Model Directories](#model-directories)
- [Ignore Models](#ignore-models)
- [Model Hooks](#model-hooks)
- [Automatic PHPDocs generation for Laravel Fluent methods](#automatic-phpdocs-generation-for-laravel-fluent-methods) - [Automatic PHPDocs generation for Laravel Fluent methods](#automatic-phpdocs-generation-for-laravel-fluent-methods)
- [Auto-completion for factory builders](#auto-completion-for-factory-builders) - [Auto-completion for factory builders](#auto-completion-for-factory-builders)
- [PhpStorm Meta for Container instances](#phpstorm-meta-for-container-instances) - [PhpStorm Meta for Container instances](#phpstorm-meta-for-container-instances)
@@ -65,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
@@ -163,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)
* … * …
*/ */
``` ```
@@ -175,6 +179,8 @@ With the `--write-mixin (-M)` option
*/ */
``` ```
#### Model Directories
By default, models in `app/models` are scanned. The optional argument tells what models to use (also outside app/models). By default, models in `app/models` are scanned. The optional argument tells what models to use (also outside app/models).
```bash ```bash
@@ -189,6 +195,8 @@ php artisan ide-helper:models --dir="path/to/models" --dir="app/src/Model"
You can publish the config file (`php artisan vendor:publish`) and set the default directories. You can publish the config file (`php artisan vendor:publish`) and set the default directories.
#### Ignore Models
Models can be ignored using the `--ignore (-I)` option Models can be ignored using the `--ignore (-I)` option
```bash ```bash
@@ -206,7 +214,7 @@ Or can be ignored by setting the `ignored_models` config
#### Magic `where*` methods #### Magic `where*` methods
Eloquent allows calling `where<Attribute>` on your modes, e.g. `Post::whereTitle(…)` and automatically translates this to e.g. `Post::where('title', '=', '…')`. Eloquent allows calling `where<Attribute>` on your models, e.g. `Post::whereTitle(…)` and automatically translates this to e.g. `Post::where('title', '=', '…')`.
If for some reason it's undesired to have them generated (one for each column), you can disable this via config `write_model_magic_where` and setting it to `false`. If for some reason it's undesired to have them generated (one for each column), you can disable this via config `write_model_magic_where` and setting it to `false`.
@@ -216,6 +224,33 @@ You may use the [`::withCount`](https://laravel.com/docs/master/eloquent-relatio
By default, these attributes are generated in the phpdoc. You can turn them off by setting the config `write_model_relation_count_properties` to `false`. By default, these attributes are generated in the phpdoc. You can turn them off by setting the config `write_model_relation_count_properties` to `false`.
#### Support `@comment` based on DocBlock
In order to better support IDEs, relations and getters/setters can also add a comment to a property like table columns. Therefore a custom docblock `@comment` is used:
```php
class Users extends Model
{
/**
* @comment Get User's full name
*
* @return string
*/
public function getFullNameAttribute(): string
{
return $this->first_name . ' ' .$this->last_name ;
}
}
// => after generate models
/**
* App\Models\Users
*
* @property-read string $full_name Get User's full name
* …
*/
```
#### Dedicated Eloquent Builder methods #### Dedicated Eloquent Builder methods
A new method to the eloquent models was added called `newEloquentBuilder` [Reference](https://timacdonald.me/dedicated-eloquent-model-query-builders/) where we can A new method to the eloquent models was added called `newEloquentBuilder` [Reference](https://timacdonald.me/dedicated-eloquent-model-query-builders/) where we can
@@ -243,6 +278,44 @@ For those special cases, you can map them via the config `custom_db_types`. Exam
], ],
``` ```
#### Model Hooks
If you need additional information on your model from sources that are not handled by default, you can hook in to the
generation process with model hooks to add extra information on the fly.
Simply create a class that implements `ModelHookInterface` and add it to the `model_hooks` array in the config:
```php
'model_hooks' => [
MyCustomHook::class,
],
```
The `run` method will be called during generation for every model and receives the current running `ModelsCommand` and the current `Model`, e.g.:
```php
class MyCustomHook implements ModelHookInterface
{
public function run(ModelsCommand $command, Model $model): void
{
if (! $model instanceof MyModel) {
return;
}
$command->setProperty('custom', 'string', true, false, 'My custom property');
$command->unsetMethod('method');
$command->setMethod('method', $command->getMethodType($model, '\Some\Class'), ['$param']);
}
}
```
```php
/**
* MyModel
*
* @property integer $id
* @property-read string $custom
```
### Automatic PHPDocs generation for Laravel Fluent methods ### Automatic PHPDocs generation for Laravel Fluent methods
If you need PHPDocs support for Fluent methods in migration, for example If you need PHPDocs support for Fluent methods in migration, for example
+30 -26
View File
@@ -1,6 +1,7 @@
{ {
"name": "barryvdh/laravel-ide-helper", "name": "barryvdh/laravel-ide-helper",
"description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.",
"license": "MIT",
"keywords": [ "keywords": [
"laravel", "laravel",
"autocomplete", "autocomplete",
@@ -12,7 +13,6 @@
"codeintel", "codeintel",
"phpdoc" "phpdoc"
], ],
"license": "MIT",
"authors": [ "authors": [
{ {
"name": "Barry vd. Heuvel", "name": "Barry vd. Heuvel",
@@ -20,40 +20,33 @@
} }
], ],
"require": { "require": {
"php": ">=7.2", "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.3", "doctrine/dbal": "^2.6 || ^3",
"illuminate/console": "^6 || ^7 || ^8", "illuminate/console": "^8 || ^9",
"illuminate/filesystem": "^6 || ^7 || ^8", "illuminate/filesystem": "^8 || ^9",
"illuminate/support": "^6 || ^7 || ^8", "illuminate/support": "^8 || ^9",
"nikic/php-parser": "^4.7",
"phpdocumentor/type-resolver": "^1.1.0" "phpdocumentor/type-resolver": "^1.1.0"
}, },
"require-dev": { "require-dev": {
"ext-pdo_sqlite": "*", "ext-pdo_sqlite": "*",
"friendsofphp/php-cs-fixer": "^2", "friendsofphp/php-cs-fixer": "^2",
"illuminate/config": "^6 || ^7 || ^8", "illuminate/config": "^8 || ^9",
"illuminate/view": "^6 || ^7 || ^8", "illuminate/view": "^8 || ^9",
"mockery/mockery": "^1.3.3", "mockery/mockery": "^1.4",
"orchestra/testbench": "^4 || ^5 || ^6", "orchestra/testbench": "^6 || ^7",
"phpunit/phpunit": "^8.5 || ^9", "phpunit/phpunit": "^8.5 || ^9",
"spatie/phpunit-snapshot-assertions": "^1.4 || ^2.2 || ^3 || ^4", "spatie/phpunit-snapshot-assertions": "^3 || ^4",
"vimeo/psalm": "^3.12" "vimeo/psalm": "^3.12"
}, },
"config": { "suggest": {
"sort-packages": true "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9)."
},
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
},
"laravel": {
"providers": [
"Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider"
]
}
}, },
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Barryvdh\\LaravelIdeHelper\\": "src" "Barryvdh\\LaravelIdeHelper\\": "src"
@@ -64,8 +57,19 @@
"Barryvdh\\LaravelIdeHelper\\Tests\\": "tests" "Barryvdh\\LaravelIdeHelper\\Tests\\": "tests"
} }
}, },
"minimum-stability": "dev", "config": {
"prefer-stable": true, "sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.12-dev"
},
"laravel": {
"providers": [
"Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider"
]
}
},
"scripts": { "scripts": {
"analyze": "psalm", "analyze": "psalm",
"check-style": [ "check-style": [
+39 -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
@@ -144,6 +155,21 @@ return [
], ],
/*
|--------------------------------------------------------------------------
| Models hooks
|--------------------------------------------------------------------------
|
| Define which hook classes you want to run for models to add custom information
|
| Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface.
|
*/
'model_hooks' => [
// App\Support\IdeHelper\MyModelHook::class
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Extra classes | Extra classes
@@ -191,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:
| |
@@ -278,4 +304,16 @@ return [
*/ */
'additional_relation_types' => [], 'additional_relation_types' => [],
/*
|--------------------------------------------------------------------------
| Run artisan commands after migrations to generate model helpers
|--------------------------------------------------------------------------
|
| The specified commands should run after migrations are finished running.
|
*/
'post_migrate' => [
// 'ide-helper:models --nowrite',
],
]; ];
-5
View File
@@ -17,9 +17,4 @@
<code>\Storage</code> <code>\Storage</code>
</UndefinedClass> </UndefinedClass>
</file> </file>
<file src="src/IdeHelperServiceProvider.php">
<TooFewArguments occurrences="1">
<code>new PhpEngine()</code>
</TooFewArguments>
</file>
</files> </files>
+1
View File
@@ -9,6 +9,7 @@
?> ?>
// @formatter:off // @formatter:off
// phpcs:ignoreFile
/** /**
* A helper file for Laravel, to provide autocomplete information to your IDE * A helper file for Laravel, to provide autocomplete information to your IDE
+13 -8
View File
@@ -17,6 +17,7 @@ use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
use Barryvdh\Reflection\DocBlock\Tag\MethodTag; use Barryvdh\Reflection\DocBlock\Tag\MethodTag;
use Closure; use Closure;
use Illuminate\Config\Repository as ConfigRepository; use Illuminate\Config\Repository as ConfigRepository;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;
use ReflectionClass; use ReflectionClass;
@@ -39,6 +40,7 @@ class Alias
protected $magicMethods = []; protected $magicMethods = [];
protected $interfaces = []; protected $interfaces = [];
protected $phpdoc = null; protected $phpdoc = null;
protected $classAliases = [];
/** @var ConfigRepository */ /** @var ConfigRepository */
protected $config; protected $config;
@@ -77,10 +79,11 @@ class Alias
$this->detectExtendsNamespace(); $this->detectExtendsNamespace();
if (!empty($this->namespace)) { if (!empty($this->namespace)) {
//Create a DocBlock and serializer instance $this->classAliases = (new UsesResolver())->loadFromClass($this->root);
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace));
}
//Create a DocBlock and serializer instance
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace, $this->classAliases));
}
if ($facade === '\Illuminate\Database\Eloquent\Model') { if ($facade === '\Illuminate\Database\Eloquent\Model') {
$this->usedMethods = ['decrement', 'increment']; $this->usedMethods = ['decrement', 'increment'];
@@ -329,7 +332,7 @@ class Alias
if (!in_array($magic, $this->usedMethods)) { if (!in_array($magic, $this->usedMethods)) {
if ($class !== $this->root) { if ($class !== $this->root) {
$this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces); $this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces, $this->classAliases);
} }
$this->usedMethods[] = $magic; $this->usedMethods[] = $magic;
} }
@@ -343,7 +346,6 @@ class Alias
*/ */
protected function detectMethods() protected function detectMethods()
{ {
foreach ($this->classes as $class) { foreach ($this->classes as $class) {
$reflection = new \ReflectionClass($class); $reflection = new \ReflectionClass($class);
@@ -359,7 +361,8 @@ class Alias
$this->alias, $this->alias,
$reflection, $reflection,
$method->name, $method->name,
$this->interfaces $this->interfaces,
$this->classAliases
); );
} }
$this->usedMethods[] = $method->name; $this->usedMethods[] = $method->name;
@@ -368,8 +371,9 @@ class Alias
} }
// Check if the class is macroable // Check if the class is macroable
// (Eloquent\Builder is also macroable but doesn't use Macroable trait)
$traits = collect($reflection->getTraitNames()); $traits = collect($reflection->getTraitNames());
if ($traits->contains('Illuminate\Support\Traits\Macroable')) { if ($traits->contains('Illuminate\Support\Traits\Macroable') || $class === EloquentBuilder::class) {
$properties = $reflection->getStaticProperties(); $properties = $reflection->getStaticProperties();
$macros = isset($properties['macros']) ? $properties['macros'] : []; $macros = isset($properties['macros']) ? $properties['macros'] : [];
foreach ($macros as $macro_name => $macro_func) { foreach ($macros as $macro_name => $macro_func) {
@@ -380,7 +384,8 @@ class Alias
$this->alias, $this->alias,
$reflection, $reflection,
$macro_name, $macro_name,
$this->interfaces $this->interfaces,
$this->classAliases
); );
$this->usedMethods[] = $macro_name; $this->usedMethods[] = $macro_name;
} }
+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);
} }
} }
+334 -50
View File
@@ -11,13 +11,19 @@
namespace Barryvdh\LaravelIdeHelper\Console; namespace Barryvdh\LaravelIdeHelper\Console;
use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface;
use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Context; 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\Castable;
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\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -31,10 +37,13 @@ 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;
use ReflectionNamedType;
use ReflectionObject; use ReflectionObject;
use ReflectionType;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@@ -72,7 +81,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.
@@ -96,6 +109,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
@@ -121,7 +138,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(
@@ -189,7 +207,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'],
@@ -209,8 +227,6 @@ class ModelsCommand extends Command
protected function generateDocs($loadModels, $ignore = '') protected function generateDocs($loadModels, $ignore = '')
{ {
$output = "<?php $output = "<?php
// @formatter:off // @formatter:off
@@ -277,6 +293,10 @@ class ModelsCommand extends Command
$this->getPropertiesFromMethods($model); $this->getPropertiesFromMethods($model);
$this->getSoftDeleteMethods($model); $this->getSoftDeleteMethods($model);
$this->getCollectionMethods($model); $this->getCollectionMethods($model);
$this->getFactoryMethods($model);
$this->runModelHooks($model);
$output .= $this->createPhpDocs($name); $output .= $this->createPhpDocs($name);
$ignore[] = $name; $ignore[] = $name;
$this->nullableColumns = []; $this->nullableColumns = [];
@@ -309,9 +329,8 @@ class ModelsCommand extends Command
$dirs = glob($dir, GLOB_ONLYDIR); $dirs = glob($dir, GLOB_ONLYDIR);
foreach ($dirs as $dir) { foreach ($dirs as $dir) {
if (!is_dir($dir)) { if (!is_dir($dir)) {
$this->error("Cannot locate directory '{'$dir}'"); $this->error("Cannot locate directory '{$dir}'");
continue; continue;
} }
@@ -335,15 +354,31 @@ class ModelsCommand extends Command
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
*/ */
protected function castPropertiesType($model) public function castPropertiesType($model)
{ {
$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, ':');
}
$params = [];
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;
@@ -368,6 +403,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;
@@ -376,6 +415,10 @@ 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);
$params = strtok(':');
$params = $params ? explode(',', $params) : [];
break; break;
} }
@@ -383,6 +426,7 @@ class ModelsCommand extends Command
continue; continue;
} }
$realType = $this->checkForCastableCasts($realType, $params);
$realType = $this->checkForCustomLaravelCasts($realType); $realType = $this->checkForCustomLaravelCasts($realType);
$realType = $this->getTypeOverride($realType); $realType = $this->getTypeOverride($realType);
$this->properties[$name]['type'] = $this->getTypeInModel($model, $realType); $this->properties[$name]['type'] = $this->getTypeInModel($model, $realType);
@@ -410,8 +454,10 @@ 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
*/ */
protected function getPropertiesFromTable($model) public function getPropertiesFromTable($model)
{ {
$table = $model->getConnection()->getTablePrefix() . $model->getTable(); $table = $model->getConnection()->getTablePrefix() . $model->getTable();
$schema = $model->getConnection()->getDoctrineSchemaManager(); $schema = $model->getConnection()->getDoctrineSchemaManager();
@@ -421,6 +467,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);
} }
@@ -435,6 +489,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())) {
@@ -458,7 +513,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';
@@ -508,12 +563,15 @@ class ModelsCommand extends Command
/** /**
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
*/ */
protected function getPropertiesFromMethods($model) public function getPropertiesFromMethods($model)
{ {
$methods = get_class_methods($model); $methods = get_class_methods($model);
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,
@@ -523,10 +581,24 @@ 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);
$this->setProperty($name, $type, true, null); $comment = $this->getCommentFromDocBlock($reflection);
$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(
@@ -537,13 +609,14 @@ 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)) {
$this->setProperty($name, null, null, true); $comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, null, null, true, $comment);
} }
} elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery') { } elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery') {
//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);
$args = $this->getParameters($reflection); $args = $this->getParameters($reflection);
//Remove the first ($query) argument //Remove the first ($query) argument
array_shift($args); array_shift($args);
@@ -555,7 +628,7 @@ class ModelsCommand extends Command
$reflection->getDeclaringClass(), $reflection->getDeclaringClass(),
$reflection->getDeclaringClass()->getName() $reflection->getDeclaringClass()->getName()
); );
$this->setMethod($name, $builder . '|' . $modelName, $args); $this->setMethod($name, $builder . '|' . $modelName, $args, $comment);
} }
} elseif (in_array($method, ['query', 'newQuery', 'newModelQuery'])) { } elseif (in_array($method, ['query', 'newQuery', 'newModelQuery'])) {
$builder = $this->getClassNameInDestinationFile($model, get_class($model->newModelQuery())); $builder = $this->getClassNameInDestinationFile($model, get_class($model->newModelQuery()));
@@ -573,10 +646,8 @@ 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()
: (string)$returnType; : (string)$returnType;
} else { } else {
@@ -607,6 +678,7 @@ class ModelsCommand extends Command
continue; continue;
} }
$comment = $this->getCommentFromDocBlock($reflection);
// Adding constraints requires reading model properties which // Adding constraints requires reading model properties which
// can cause errors. Since we don't need constraints we can // can cause errors. Since we don't need constraints we can
// disable them when we fetch the relation to avoid errors. // disable them when we fetch the relation to avoid errors.
@@ -638,7 +710,8 @@ class ModelsCommand extends Command
$method, $method,
$collectionClassNameInModel . '|' . $relatedModel . '[]', $collectionClassNameInModel . '|' . $relatedModel . '[]',
true, true,
null null,
$comment
); );
if ($this->write_model_relation_count_properties) { if ($this->write_model_relation_count_properties) {
$this->setProperty( $this->setProperty(
@@ -646,6 +719,7 @@ class ModelsCommand extends Command
'int|null', 'int|null',
true, true,
false false
// What kind of comments should be added to the relation count here?
); );
} }
} elseif ($relation === 'morphTo') { } elseif ($relation === 'morphTo') {
@@ -654,7 +728,8 @@ class ModelsCommand extends Command
$method, $method,
$this->getClassNameInDestinationFile($model, Model::class) . '|\Eloquent', $this->getClassNameInDestinationFile($model, Model::class) . '|\Eloquent',
true, true,
null null,
$comment
); );
} else { } else {
//Single model is returned //Single model is returned
@@ -663,7 +738,7 @@ class ModelsCommand extends Command
$relatedModel, $relatedModel,
true, true,
null, null,
'', $comment,
$this->isRelationNullable($relation, $relationObj) $this->isRelationNullable($relation, $relationObj)
); );
} }
@@ -701,6 +776,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)]);
} }
@@ -712,7 +792,7 @@ class ModelsCommand extends Command
* @param string|null $comment * @param string|null $comment
* @param bool $nullable * @param bool $nullable
*/ */
protected function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false) public function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false)
{ {
if (!isset($this->properties[$name])) { if (!isset($this->properties[$name])) {
$this->properties[$name] = []; $this->properties[$name] = [];
@@ -736,7 +816,7 @@ class ModelsCommand extends Command
} }
} }
protected function setMethod($name, $type = '', $arguments = []) 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);
@@ -744,16 +824,28 @@ class ModelsCommand extends Command
$this->methods[$name] = []; $this->methods[$name] = [];
$this->methods[$name]['type'] = $type; $this->methods[$name]['type'] = $type;
$this->methods[$name]['arguments'] = $arguments; $this->methods[$name]['arguments'] = $arguments;
$this->methods[$name]['comment'] = $comment;
} }
} }
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
*/ */
protected function createPhpDocs($class) protected function createPhpDocs($class)
{ {
$reflection = new ReflectionClass($class); $reflection = new ReflectionClass($class);
$namespace = $reflection->getNamespaceName(); $namespace = $reflection->getNamespaceName();
$classname = $reflection->getShortName(); $classname = $reflection->getShortName();
@@ -820,7 +912,11 @@ class ModelsCommand extends Command
continue; continue;
} }
$arguments = implode(', ', $method['arguments']); $arguments = implode(', ', $method['arguments']);
$tag = Tag::createInstance("@method static {$method['type']} {$name}({$arguments})", $phpdoc); $tagLine = "@method static {$method['type']} {$name}({$arguments})";
if ($method['comment'] !== '') {
$tagLine .= " {$method['comment']}";
}
$tag = Tag::createInstance($tagLine, $phpdoc);
$phpdoc->appendTag($tag); $phpdoc->appendTag($tag);
} }
@@ -862,6 +958,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) {
@@ -883,11 +986,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";
@@ -906,7 +1013,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;
} }
@@ -970,6 +1078,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);
@@ -980,6 +1118,30 @@ class ModelsCommand extends Command
return $this->getReturnTypeFromReflection($reflection); return $this->getReturnTypeFromReflection($reflection);
} }
/**
* Get method comment based on it DocBlock comment
*
* @param \ReflectionMethod $reflection
*
* @return null|string
*/
protected function getCommentFromDocBlock(\ReflectionMethod $reflection)
{
$phpDocContext = (new ContextFactory())->createFromReflector($reflection);
$context = new Context(
$phpDocContext->getNamespace(),
$phpDocContext->getNamespaceAliases()
);
$comment = '';
$phpdoc = new DocBlock($reflection, $context);
if ($phpdoc->hasTag('comment')) {
$comment = $phpdoc->getTagsByName('comment')[0]->getContent();
}
return $comment;
}
/** /**
* Get method return type based on it DocBlock comment * Get method return type based on it DocBlock comment
* *
@@ -987,9 +1149,9 @@ class ModelsCommand extends Command
* *
* @return null|string * @return null|string
*/ */
protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection) protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection, \Reflector $reflectorForContext = null)
{ {
$phpDocContext = (new ContextFactory())->createFromReflector($reflection); $phpDocContext = (new ContextFactory())->createFromReflector($reflectorForContext ?? $reflection);
$context = new Context( $context = new Context(
$phpDocContext->getNamespace(), $phpDocContext->getNamespace(),
$phpDocContext->getNamespaceAliases() $phpDocContext->getNamespaceAliases()
@@ -1011,16 +1173,12 @@ class ModelsCommand extends Command
return null; return null;
} }
$type = $returnType instanceof \ReflectionNamedType $types = $this->extractReflectionTypes($returnType);
? $returnType->getName()
: (string)$returnType;
if (!$returnType->isBuiltin()) { $type = implode('|', $types);
$type = '\\' . $type;
}
if ($returnType->allowsNull()) { if ($returnType->allowsNull()) {
$type .= '|null'; $type .='|null';
} }
return $type; return $type;
@@ -1033,7 +1191,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);
@@ -1043,6 +1201,40 @@ class ModelsCommand extends Command
} }
} }
/**
* Generate factory method from "HasFactory" trait.
*
* @param \Illuminate\Database\Eloquent\Model $model
*/
protected function getFactoryMethods($model)
{
if (!class_exists(Factory::class)) {
return;
}
$modelName = get_class($model);
$traits = class_uses_recursive($modelName);
if (!in_array('Illuminate\\Database\\Eloquent\\Factories\\HasFactory', $traits)) {
return;
}
if ($modelName::newFactory()) {
$factory = get_class($modelName::newFactory());
} else {
$factory = Factory::resolveFactoryName($modelName);
}
$factory = '\\' . trim($factory, '\\');
if (!class_exists($factory)) {
return;
}
$this->setMethod('factory', $factory, ['...$parameters']);
}
/** /**
* Generates methods that return collections * Generates methods that return collections
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
@@ -1076,6 +1268,32 @@ class ModelsCommand extends Command
return $keyword; return $keyword;
} }
protected function checkForCastableCasts(string $type, array $params = []): string
{
if (!class_exists($type) || !interface_exists(Castable::class)) {
return $type;
}
$reflection = new \ReflectionClass($type);
if (!$reflection->implementsInterface(Castable::class)) {
return $type;
}
$cast = call_user_func([$type, 'castUsing'], $params);
if (is_string($cast) && !is_object($cast)) {
return $cast;
}
$castReflection = new ReflectionObject($cast);
$methodReflection = $castReflection->getMethod('get');
return $this->getReturnTypeFromReflection($methodReflection) ??
$this->getReturnTypeFromDocBlock($methodReflection, $reflection);
}
/** /**
* @param string $type * @param string $type
* @return string|null * @return string|null
@@ -1101,7 +1319,7 @@ class ModelsCommand extends Command
$reflectionType = $this->getReturnTypeFromDocBlock($methodReflection); $reflectionType = $this->getReturnTypeFromDocBlock($methodReflection);
} }
if($reflectionType === 'static' || $reflectionType === '$this') { if ($reflectionType === 'static' || $reflectionType === '$this') {
$reflectionType = $type; $reflectionType = $type;
} }
@@ -1129,7 +1347,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);
@@ -1186,17 +1404,19 @@ class ModelsCommand extends Command
protected function getParamType(\ReflectionMethod $method, \ReflectionParameter $parameter): ?string protected function getParamType(\ReflectionMethod $method, \ReflectionParameter $parameter): ?string
{ {
if ($paramType = $parameter->getType()) { if ($paramType = $parameter->getType()) {
$parameterName = $paramType->getName(); $types = $this->extractReflectionTypes($paramType);
if (!$paramType->isBuiltin()) { $type = implode('|', $types);
$parameterName = '\\' . $parameterName;
}
if ($paramType->allowsNull()) { if ($paramType->allowsNull()) {
return '?' . $parameterName; if (count($types)==1) {
$type = '?' . $type;
} else {
$type .='|null';
}
} }
return $parameterName; return $type;
} }
$docComment = $method->getDocComment(); $docComment = $method->getDocComment();
@@ -1210,7 +1430,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);
@@ -1261,4 +1481,68 @@ class ModelsCommand extends Command
// then we have found the type of the variable if not we return null // then we have found the type of the variable if not we return null
return $type; return $type;
} }
protected function extractReflectionTypes(ReflectionType $reflection_type)
{
if ($reflection_type instanceof ReflectionNamedType) {
$types[] = $this->getReflectionNamedType($reflection_type);
} else {
$types = [];
foreach ($reflection_type->getTypes() as $named_type) {
if ($named_type->getName()==='null') {
continue;
}
$types[] = $this->getReflectionNamedType($named_type);
}
}
return $types;
}
protected function getReflectionNamedType(ReflectionNamedType $paramType): string
{
$parameterName = $paramType->getName();
if (!$paramType->isBuiltin()) {
$parameterName = '\\' . $parameterName;
}
return $parameterName;
}
/**
* @param \Illuminate\Database\Eloquent\Model $model
* @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \RuntimeException
*/
protected function runModelHooks($model): void
{
$hooks = $this->laravel['config']->get('ide-helper.model_hooks', []);
foreach ($hooks as $hook) {
$hookInstance = $this->laravel->make($hook);
if (!$hookInstance instanceof ModelHookInterface) {
throw new \RuntimeException(
'Your IDE helper model hook must implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface'
);
}
$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;
}
}
}
} }
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Barryvdh\LaravelIdeHelper\Contracts;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Illuminate\Database\Eloquent\Model;
interface ModelHookInterface
{
public function run(ModelsCommand $command, Model $model): void;
}
+4 -4
View File
@@ -54,9 +54,9 @@ class Generator
// Find the drivers to add to the extra/interfaces // Find the drivers to add to the extra/interfaces
$this->detectDrivers(); $this->detectDrivers();
$this->extra = array_merge($this->extra, $this->config->get('ide-helper.extra')); $this->extra = array_merge($this->extra, $this->config->get('ide-helper.extra'), []);
$this->magic = array_merge($this->magic, $this->config->get('ide-helper.magic')); $this->magic = array_merge($this->magic, $this->config->get('ide-helper.magic'), []);
$this->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces')); $this->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces'), []);
// Make all interface classes absolute // Make all interface classes absolute
foreach ($this->interfaces as &$interface) { foreach ($this->interfaces as &$interface) {
$interface = '\\' . ltrim($interface, '\\'); $interface = '\\' . ltrim($interface, '\\');
@@ -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]);
-13
View File
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper;
class Helpers
{
public static function isLaravel(): bool
{
return class_exists('Illuminate\Foundation\Application');
}
}
+10 -5
View File
@@ -15,8 +15,10 @@ use Barryvdh\LaravelIdeHelper\Console\EloquentCommand;
use Barryvdh\LaravelIdeHelper\Console\GeneratorCommand; use Barryvdh\LaravelIdeHelper\Console\GeneratorCommand;
use Barryvdh\LaravelIdeHelper\Console\MetaCommand; use Barryvdh\LaravelIdeHelper\Console\MetaCommand;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand; use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Listeners\GenerateModelHelper;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Contracts\Support\DeferrableProvider; use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Foundation\Application; use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\View\Engines\EngineResolver; use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Engines\PhpEngine; use Illuminate\View\Engines\PhpEngine;
@@ -32,6 +34,13 @@ class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProv
*/ */
public function boot() public function boot()
{ {
if (!$this->app->runningUnitTests() && $this->app['config']->get('ide-helper.post_migrate', [])) {
$this->app['events']->listen(CommandFinished::class, GenerateModelHelper::class);
$this->app['events']->listen(MigrationsEnded::class, function () {
GenerateModelHelper::$shouldRun = true;
});
}
if ($this->app->has('view')) { if ($this->app->has('view')) {
$viewPath = __DIR__ . '/../resources/views'; $viewPath = __DIR__ . '/../resources/views';
$this->loadViewsFrom($viewPath, 'ide-helper'); $this->loadViewsFrom($viewPath, 'ide-helper');
@@ -110,10 +119,6 @@ class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProv
{ {
$resolver = new EngineResolver(); $resolver = new EngineResolver();
$resolver->register('php', function () { $resolver->register('php', function () {
if (Helpers::isLaravel() && (int) Application::VERSION < 8) {
return new PhpEngine();
}
return new PhpEngine($this->app['files']); return new PhpEngine($this->app['files']);
}); });
$finder = new FileViewFinder($this->app['files'], [__DIR__ . '/../resources/views']); $finder = new FileViewFinder($this->app['files'], [__DIR__ . '/../resources/views']);
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace Barryvdh\LaravelIdeHelper\Listeners;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Contracts\Config\Repository as Config;
use Illuminate\Contracts\Console\Kernel as Artisan;
class GenerateModelHelper
{
/**
* Tracks whether we should run the models command on the CommandFinished event or not.
* Set to true by the MigrationsEnded event, needs to be cleared before artisan call to prevent infinite loop.
*
* @var bool
*/
public static $shouldRun = false;
/** @var \Illuminate\Contracts\Console\Kernel */
protected $artisan;
/** @var \Illuminate\Contracts\Config\Repository */
protected $config;
/**
* @param \Illuminate\Contracts\Console\Kernel $artisan
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(Artisan $artisan, Config $config)
{
$this->artisan = $artisan;
$this->config = $config;
}
/**
* Handle the event.
*
* @param CommandFinished $event
*/
public function handle(CommandFinished $event)
{
if (!self::$shouldRun) {
return;
}
self::$shouldRun = false;
foreach ($this->config->get('ide-helper.post_migrate', []) as $command) {
$this->artisan->call($command, [], $event->output);
}
}
}
+56 -15
View File
@@ -4,6 +4,7 @@ namespace Barryvdh\LaravelIdeHelper;
use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Tag; use Barryvdh\Reflection\DocBlock\Tag;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class Macro extends Method class Macro extends Method
@@ -16,15 +17,17 @@ class Macro extends Method
* @param \ReflectionClass $class * @param \ReflectionClass $class
* @param null $methodName * @param null $methodName
* @param array $interfaces * @param array $interfaces
* @param array $classAliases
*/ */
public function __construct( public function __construct(
$method, $method,
$alias, $alias,
$class, $class,
$methodName = null, $methodName = null,
$interfaces = [] $interfaces = [],
$classAliases = []
) { ) {
parent::__construct($method, $alias, $class, $methodName, $interfaces); parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases);
} }
/** /**
@@ -32,34 +35,72 @@ class Macro extends Method
*/ */
protected function initPhpDoc($method) protected function initPhpDoc($method)
{ {
$this->phpdoc = new DocBlock('/** */'); $this->phpdoc = new DocBlock($method);
$this->addLocationToPhpDoc(); $this->addLocationToPhpDoc();
// Add macro parameters // Add macro parameters if they are missed in original docblock
foreach ($method->getParameters() as $parameter) { if (!$this->phpdoc->hasTag('param')) {
$type = $parameter->hasType() ? $parameter->getType()->getName() : 'mixed'; foreach ($method->getParameters() as $parameter) {
$type .= $parameter->hasType() && $parameter->getType()->allowsNull() ? '|null' : ''; $reflectionType = $parameter->getType();
$name = $parameter->isVariadic() ? '...' : ''; $type = $this->concatReflectionTypes($reflectionType);
$name .= '$' . $parameter->getName();
$this->phpdoc->appendTag(Tag::createInstance("@param {$type} {$name}")); /** @psalm-suppress UndefinedClass */
if ($reflectionType && !$reflectionType instanceof \ReflectionUnionType && $reflectionType->allowsNull()) {
$type .= '|null';
}
$type = $type ?: 'mixed';
$name = $parameter->isVariadic() ? '...' : '';
$name .= '$' . $parameter->getName();
$this->phpdoc->appendTag(Tag::createInstance("@param {$type} {$name}"));
}
} }
// Add macro return type // Add macro return type if it missed in original docblock
if ($method->hasReturnType()) { if ($method->hasReturnType() && !$this->phpdoc->hasTag('return')) {
$type = $method->getReturnType()->getName(); $builder = EloquentBuilder::class;
$type .= $method->getReturnType()->allowsNull() ? '|null' : ''; $return = $method->getReturnType();
$type = $this->concatReflectionTypes($return);
/** @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()
{ {
$enclosingClass = $this->method->getClosureScopeClass(); if ($this->method->name === '__invoke') {
$enclosingClass = $this->method->getDeclaringClass();
} else {
$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) {
+7 -4
View File
@@ -38,6 +38,7 @@ class Method
protected $real_name; protected $real_name;
protected $return = null; protected $return = null;
protected $root; protected $root;
protected $classAliases;
/** /**
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method * @param \ReflectionMethod|\ReflectionFunctionAbstract $method
@@ -45,17 +46,19 @@ class Method
* @param \ReflectionClass $class * @param \ReflectionClass $class
* @param string|null $methodName * @param string|null $methodName
* @param array $interfaces * @param array $interfaces
* @param array $classAliases
*/ */
public function __construct($method, $alias, $class, $methodName = null, $interfaces = []) public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [])
{ {
$this->method = $method; $this->method = $method;
$this->interfaces = $interfaces; $this->interfaces = $interfaces;
$this->classAliases = $classAliases;
$this->name = $methodName ?: $method->name; $this->name = $methodName ?: $method->name;
$this->real_name = $method->isClosure() ? $this->name : $method->name; $this->real_name = $method->isClosure() ? $this->name : $method->name;
$this->initClassDefinedProperties($method, $class); $this->initClassDefinedProperties($method, $class);
//Reference the 'real' function in the declaring class //Reference the 'real' function in the declaring class
$this->root = '\\' . ltrim($class->getName(), '\\'); $this->root = '\\' . ltrim($method->name === '__invoke' ? $method->getDeclaringClass()->getName() : $class->getName(), '\\');
//Create a DocBlock and serializer instance //Create a DocBlock and serializer instance
$this->initPhpDoc($method); $this->initPhpDoc($method);
@@ -80,7 +83,7 @@ class Method
*/ */
protected function initPhpDoc($method) protected function initPhpDoc($method)
{ {
$this->phpdoc = new DocBlock($method, new Context($this->namespace)); $this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases));
} }
/** /**
@@ -363,7 +366,7 @@ class Method
} }
if ($method) { if ($method) {
$namespace = $method->getDeclaringClass()->getNamespaceName(); $namespace = $method->getDeclaringClass()->getNamespaceName();
$phpdoc = new DocBlock($method, new Context($namespace)); $phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases));
if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) { if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) {
//Not at the end yet, try another parent/interface.. //Not at the end yet, try another parent/interface..
+124
View File
@@ -0,0 +1,124 @@
<?php
/**
* Laravel IDE Helper Generator
*
* @author Barry vd. Heuvel <barryvdh@gmail.com>
* @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link https://github.com/barryvdh/laravel-ide-helper
*/
namespace Barryvdh\LaravelIdeHelper;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\ParserFactory;
class UsesResolver
{
/**
* @param string $classFQN
* @return array
*/
public function loadFromClass(string $classFQN): array
{
return $this->loadFromFile(
$classFQN,
(new \ReflectionClass($classFQN))->getFileName()
);
}
/**
* @param string $classFQN
* @param string $filename
* @return array
*/
public function loadFromFile(string $classFQN, string $filename): array
{
return $this->loadFromCode(
$classFQN,
file_get_contents(
$filename
)
);
}
/**
* @param string $classFQN
* @param string $code
* @return array
*/
public function loadFromCode(string $classFQN, string $code): array
{
$classFQN = ltrim($classFQN, '\\');
$namespace = rtrim(
preg_replace(
'/([^\\\\]+)$/',
'',
$classFQN
),
'\\'
);
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$namespaceData = null;
foreach ($parser->parse($code) as $node) {
if ($node instanceof Namespace_ && $node->name->toCodeString() === $namespace) {
$namespaceData = $node;
break;
}
}
if ($namespaceData === null) {
return [];
}
/** @var Namespace_ $namespaceData */
$aliases = [];
foreach ($namespaceData->stmts as $stmt) {
if ($stmt instanceof Use_) {
if ($stmt->type !== Use_::TYPE_NORMAL) {
continue;
}
foreach ($stmt->uses as $use) {
/** @var UseUse $use */
$alias = $use->alias ?
$use->alias->name :
self::classBasename($use->name->toCodeString());
$aliases[$alias] = '\\' . $use->name->toCodeString();
}
} elseif ($stmt instanceof GroupUse) {
foreach ($stmt->uses as $use) {
/** @var UseUse $use */
$alias = $use->alias ?
$use->alias->name :
self::classBasename($use->name->toCodeString());
$aliases[$alias] = '\\' . $stmt->prefix->toCodeString() . '\\' . $use->name->toCodeString();
}
}
}
return $aliases;
}
/**
* @param string $classFQN
* @return string
*/
protected static function classBasename(string $classFQN): string
{
return preg_replace('/^.*\\\\([^\\\\]+)$/', '$1', $classFQN);
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\Alias;
use Barryvdh\LaravelIdeHelper\Macro;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr;
/**
* @internal
* @coversDefaultClass \Barryvdh\LaravelIdeHelper\Alias
*/
class AliasTest extends TestCase
{
/**
* @covers ::detectMethods
*/
public function testDetectMethodsMacroableMacros(): void
{
// Mock
$macro = __FUNCTION__;
$alias = new AliasMock();
// Macros
Builder::macro(
$macro,
function () {
// empty
}
);
// Prepare
$alias->setClasses([Builder::class]);
$alias->detectMethods();
// Test
$this->assertNotNull($this->getAliasMacro($alias, Builder::class, $macro));
}
/**
* @covers ::detectMethods
*/
public function testDetectMethodsEloquentBuilderMacros(): void
{
// Mock
$macro = __FUNCTION__;
$alias = new AliasMock();
// Macros
EloquentBuilder::macro(
$macro,
function () {
// empty
}
);
// Prepare
$alias->setClasses([EloquentBuilder::class]);
$alias->detectMethods();
// Test
$this->assertNotNull($this->getAliasMacro($alias, EloquentBuilder::class, $macro));
}
protected function getAliasMacro(Alias $alias, string $class, string $method): ?Macro
{
return Arr::first(
$alias->getMethods(),
function ($macro) use ($class, $method) {
return $macro instanceof Macro
&& $macro->getDeclaringClass() === "\\{$class}"
&& $macro->getName() === $method;
}
);
}
}
/**
* @internal
* @noinspection PhpMultipleClassesDeclarationsInOneFile
*/
class AliasMock extends Alias
{
public function __construct()
{
// no need to call parent
}
/**
* @param string[] $classes
*/
public function setClasses(array $classes)
{
$this->classes = $classes;
}
public function detectMethods()
{
parent::detectMethods();
}
}
+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;
}
);
}
}
@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Comment\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Simple extends Model
{
/**
* There is not comment.
*
* @return string
*/
public function getNotCommentAttribute(): string
{
}
/**
* comment There is not format comment, invalid.
*
* @return string
*/
public function getFakerCommentAttribute(): string
{
}
/**
* @comment There is format comment, success.
*
* @return string
*/
public function getFormatCommentAttribute(): string
{
}
/**
* @comment There is format comment, success.
* This is second line, success too.
*
* @return string
*/
public function getFormatCommentLineTwoAttribute(): string
{
}
/**
* @comment There is format comment, success.
* @comment This is others format comment, invalid.
*
* @return string
*/
public function getManyFormatCommentAttribute(): string
{
}
/**
* @comment Set the user's first name.
* @param $value
*/
public function setFirstNameAttribute($value)
{
}
/**
* @comment Scope a query to only include active users.
*
* @param $query
* @return mixed
*/
public function scopeActive($query)
{
return $query;
}
/**
* @comment HasMany relations.
*
* @return HasMany
*/
public function relationHasMany(): HasMany
{
return $this->hasMany(Simple::class);
}
/**
* @comment MorphTo relations.
* @return MorphTo
*/
public function relationMorphTo(): MorphTo
{
return $this->morphTo();
}
/**
* @comment Others relations.
* @return HasOne
*/
public function relationHasOne(): HasOne
{
return $this->hasOne(Simple::class);
}
/**
* @comment I'm a setter
*/
public function setBothSameNameAttribute(): void
{
}
/**
* @comment I'm a getter
* @return string
*/
public function getBothSameNameAttribute(): string
{
}
/**
* @comment I'm a setter
*/
public function setBothWithoutGetterCommentAttribute(): void
{
}
/**
* @return string
*/
public function getBothWithoutGetterCommentAttribute(): string
{
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Comment;
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,160 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Comment\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Comment\Models\Simple
*
* @property integer $id
* @property string $both_same_name I'm a getter
* @property string $both_without_getter_comment
* @property-read string $faker_comment
* @property-read string $format_comment There is format comment, success.
* @property-read string $format_comment_line_two There is format comment, success.
* This is second line, success too.
* @property-read string $many_format_comment There is format comment, success.
* @property-read string $not_comment
* @property-read \Illuminate\Database\Eloquent\Collection|Simple[] $relationHasMany HasMany relations.
* @property-read int|null $relation_has_many_count
* @property-read Simple|null $relationHasOne Others relations.
* @property-read Model|\Eloquent $relationMorphTo MorphTo relations.
* @property-write mixed $first_name Set the user's first name.
* @method static \Illuminate\Database\Eloquent\Builder|Simple active() Scope a query to only include active users.
* @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
{
/**
* There is not comment.
*
* @return string
*/
public function getNotCommentAttribute(): string
{
}
/**
* comment There is not format comment, invalid.
*
* @return string
*/
public function getFakerCommentAttribute(): string
{
}
/**
* @comment There is format comment, success.
*
* @return string
*/
public function getFormatCommentAttribute(): string
{
}
/**
* @comment There is format comment, success.
* This is second line, success too.
*
* @return string
*/
public function getFormatCommentLineTwoAttribute(): string
{
}
/**
* @comment There is format comment, success.
* @comment This is others format comment, invalid.
*
* @return string
*/
public function getManyFormatCommentAttribute(): string
{
}
/**
* @comment Set the user's first name.
* @param $value
*/
public function setFirstNameAttribute($value)
{
}
/**
* @comment Scope a query to only include active users.
*
* @param $query
* @return mixed
*/
public function scopeActive($query)
{
return $query;
}
/**
* @comment HasMany relations.
*
* @return HasMany
*/
public function relationHasMany(): HasMany
{
return $this->hasMany(Simple::class);
}
/**
* @comment MorphTo relations.
* @return MorphTo
*/
public function relationMorphTo(): MorphTo
{
return $this->morphTo();
}
/**
* @comment Others relations.
* @return HasOne
*/
public function relationHasOne(): HasOne
{
return $this->hasOne(Simple::class);
}
/**
* @comment I'm a setter
*/
public function setBothSameNameAttribute(): void
{
}
/**
* @comment I'm a getter
* @return string
*/
public function getBothSameNameAttribute(): string
{
}
/**
* @comment I'm a setter
*/
public function setBothWithoutGetterCommentAttribute(): void
{
}
/**
* @return string
*/
public function getBothWithoutGetterCommentAttribute(): string
{
}
}
@@ -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 [
//
];
}
}
@@ -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\ModelWithFactory;
use Illuminate\Database\Eloquent\Factories\Factory;
class ModelWithFactoryFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = ModelWithFactory::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}
@@ -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 [
//
];
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
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;
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();
}
}
@@ -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
{
}
@@ -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 ModelWithoutFactory extends Model
{
use HasFactory;
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
use Closure;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Foundation\Application;
use Illuminate\Support\Str;
class Test extends AbstractModelsCommand
{
public function test(): void
{
if (!version_compare(Application::VERSION, '8.2', '>=')) {
$this->markTestSkipped(
'This test only works in Laravel >= 8.2'
);
}
Factory::guessFactoryNamesUsing(static::getFactoryNameResolver());
$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->assertStringNotContainsString('not found', $tester->getDisplay());
$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';
};
}
}
@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
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\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\ModelWithoutFactory
*
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithoutFactory newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithoutFactory newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ModelWithoutFactory query()
* @mixin \Eloquent
*/
class ModelWithoutFactory extends Model
{
use HasFactory;
}
@@ -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 {}
} }
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class CastableReturnsAnonymousCaster implements Castable
{
public static function castUsing(array $arguments)
{
return new class() implements CastsAttributes {
/**
* @inheritDoc
* @return CastedProperty
*/
public function get($model, string $key, $value, array $attributes)
{
return new CastedProperty();
}
/**
* @inheritDoc
*/
public function set($model, string $key, $value, array $attributes)
{
// TODO: Implement set() method.
}
};
}
}
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts;
use Illuminate\Contracts\Database\Eloquent\Castable;
class CastableReturnsCustomCaster implements Castable
{
public static function castUsing(array $arguments)
{
return CustomCasterWithDocblockReturn::class;
}
}
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Models; namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastableReturnsAnonymousCaster;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastableReturnsCustomCaster;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturn;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturnFqn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturnFqn;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithNullablePrimitiveReturn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithNullablePrimitiveReturn;
@@ -31,8 +33,11 @@ class CustomCast extends Model
'casted_property_with_param' => CustomCasterWithParam::class . ':param', 'casted_property_with_param' => CustomCasterWithParam::class . ':param',
'casted_property_with_static_return_docblock' => SelfCastingCasterWithStaticDocblockReturn::class, 'casted_property_with_static_return_docblock' => SelfCastingCasterWithStaticDocblockReturn::class,
'casted_property_with_this_return_docblock' => SelfCastingCasterWithThisDocblockReturn::class, 'casted_property_with_this_return_docblock' => SelfCastingCasterWithThisDocblockReturn::class,
'casted_property_with_castable' => CastableReturnsCustomCaster::class,
'casted_property_with_anonymous_cast' => CastableReturnsAnonymousCaster::class,
'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,
]; ];
} }
@@ -6,19 +6,9 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCas
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 Illuminate\Foundation\Application;
class Test extends AbstractModelsCommand class Test extends AbstractModelsCommand
{ {
protected function setUp(): void
{
parent::setUp();
if (version_compare(Application::VERSION, '7.0', '<')) {
$this->markTestSkipped('This test requires Laravel 7.0 or higher');
}
}
public function test(): void public function test(): void
{ {
$command = $this->app->make(ModelsCommand::class); $command = $this->app->make(ModelsCommand::class);
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Models; namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastableReturnsAnonymousCaster;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastableReturnsCustomCaster;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturn;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturnFqn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturnFqn;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithNullablePrimitiveReturn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithNullablePrimitiveReturn;
@@ -34,6 +36,9 @@ 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 $casted_property_with_castable
* @property \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastedProperty $casted_property_with_anonymous_cast
* @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()
@@ -65,8 +70,11 @@ class CustomCast extends Model
'casted_property_with_param' => CustomCasterWithParam::class . ':param', 'casted_property_with_param' => CustomCasterWithParam::class . ':param',
'casted_property_with_static_return_docblock' => SelfCastingCasterWithStaticDocblockReturn::class, 'casted_property_with_static_return_docblock' => SelfCastingCasterWithStaticDocblockReturn::class,
'casted_property_with_this_return_docblock' => SelfCastingCasterWithThisDocblockReturn::class, 'casted_property_with_this_return_docblock' => SelfCastingCasterWithThisDocblockReturn::class,
'casted_property_with_castable' => CastableReturnsCustomCaster::class,
'casted_property_with_anonymous_cast' => CastableReturnsAnonymousCaster::class,
'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 CustomProperty implements ModelHookInterface
{
public function run(ModelsCommand $command, Model $model): void
{
$command->setProperty('custom', 'string', true, false);
}
}
@@ -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');
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models;
use Illuminate\Database\Eloquent\Model;
class Simple extends Model
{
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
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\UnsetMethod;
use Illuminate\Filesystem\Filesystem;
use Mockery;
class Test extends AbstractModelsCommand
{
protected function getEnvironmentSetUp($app)
{
parent::getEnvironmentSetUp($app);
$app['config']->set('ide-helper', [
'model_locations' => [
// This is calculated from the base_path() which points to
// vendor/orchestra/testbench-core/laravel
'/../../../../tests/Console/ModelsCommand/ModelHooks/Models',
],
'model_hooks' => [
CustomProperty::class,
CustomMethod::class,
UnsetMethod::class,
],
]);
}
public function test(): void
{
$actualContent = null;
$mockFilesystem = Mockery::mock(Filesystem::class);
$mockFilesystem
->shouldReceive('get')
->andReturn(file_get_contents(__DIR__ . '/Models/Simple.php'))
->once();
$mockFilesystem
->shouldReceive('put')
->with(
Mockery::any(),
Mockery::capture($actualContent)
)
->andReturn(1) // Simulate we wrote _something_ to the file
->once();
$this->instance(Filesystem::class, $mockFilesystem);
$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());
$expectedContent = <<<'PHP'
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models\Simple
*
* @property int $id
* @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 newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
* @mixin \Eloquent
*/
class Simple extends Model
{
}
PHP;
$this->assertSame($expectedContent, $actualContent);
}
}
@@ -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,32 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\UnionTypes\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\Builder;
class UnionTypeModel extends Model
{
public function scopeWithUnionTypeParameter(Builder $query, string|int $bar): Builder
{
return $query->where('foo', $bar);
}
public function scopeWithNullableUnionTypeParameter(Builder $query, null|string|int $bar): Builder
{
return $query->where('foo', $bar);
}
public function withUnionTypeReturn(): HasMany|UnionTypeModel
{
return $this->hasMany(UnionTypeModel::class);
}
public function getFooAttribute(): string|int|null
{
return $this->getAttribute('foo');
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\UnionTypes;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
class Test extends AbstractModelsCommand
{
protected function setUp(): void
{
parent::setUp();
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped('This test requires PHP 8.0 or higher');
}
}
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,45 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\UnionTypes\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\Builder;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\UnionTypes\Models\UnionTypeModel
*
* @property-read string|int|null $foo
* @property-read \Illuminate\Database\Eloquent\Collection|UnionTypeModel[] $withUnionTypeReturn
* @property-read int|null $with_union_type_return_count
* @method static \Illuminate\Database\Eloquent\Builder|UnionTypeModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UnionTypeModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UnionTypeModel query()
* @method static \Illuminate\Database\Eloquent\Builder|UnionTypeModel withNullableUnionTypeParameter(string|int|null $bar)
* @method static \Illuminate\Database\Eloquent\Builder|UnionTypeModel withUnionTypeParameter(string|int $bar)
* @mixin \Eloquent
*/
class UnionTypeModel extends Model
{
public function scopeWithUnionTypeParameter(Builder $query, string|int $bar): Builder
{
return $query->where('foo', $bar);
}
public function scopeWithNullableUnionTypeParameter(Builder $query, null|string|int $bar): Builder
{
return $query->where('foo', $bar);
}
public function withUnionTypeReturn(): HasMany|UnionTypeModel
{
return $this->hasMany(UnionTypeModel::class);
}
public function getFooAttribute(): string|int|null
{
return $this->getAttribute('foo');
}
}
@@ -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');
});
}
}
+301
View File
@@ -0,0 +1,301 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\Macro;
use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Tag;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Routing\UrlGenerator;
use ReflectionClass;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use function array_map;
use function implode;
use const PHP_EOL;
/**
* @internal
* @coversDefaultClass \Barryvdh\LaravelIdeHelper\Macro
*/
class MacroTest extends TestCase
{
/**
* @covers ::initPhpDoc
* @throws \ReflectionException
*/
public function testInitPhpDocEloquentBuilderHasStaticInReturnType(): void
{
$class = new ReflectionClass(EloquentBuilder::class);
$phpdoc = (new MacroMock())->getPhpDoc(
new ReflectionFunction(
function (): EloquentBuilder {
return $this;
}
),
$class
);
$this->assertNotNull($phpdoc);
$this->assertEquals(
'@return \Illuminate\Database\Eloquent\Builder|static',
$this->tagsToString($phpdoc, 'return')
);
}
/**
* @covers ::initPhpDoc
* @throws \ReflectionException
*/
public function testInitPhpDocClosureWithoutDocBlock(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(
new ReflectionFunction(
function (int $a = null): int {
return 0;
}
)
);
$this->assertNotNull($phpdoc);
$this->assertEmpty($phpdoc->getText());
$this->assertEquals('@param int|null $a', $this->tagsToString($phpdoc, 'param'));
$this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return'));
$this->assertTrue($phpdoc->hasTag('see'));
}
/**
* @covers ::initPhpDoc
* @throws \ReflectionException
*/
public function testInitPhpDocClosureWithArgsAndReturnType(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(
new ReflectionFunction(
/**
* Test docblock.
*/
function (int $a = null): int {
return 0;
}
)
);
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock', $phpdoc->getText());
$this->assertEquals('@param int|null $a', $this->tagsToString($phpdoc, 'param'));
$this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return'));
$this->assertTrue($phpdoc->hasTag('see'));
}
/**
* @covers ::initPhpDoc
* @throws \ReflectionException
*/
public function testInitPhpDocClosureWithArgs(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(
new ReflectionFunction(
/**
* Test docblock.
*/
function (int $a = null) {
return 0;
}
)
);
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock', $phpdoc->getText());
$this->assertEquals('@param int|null $a', $this->tagsToString($phpdoc, 'param'));
$this->assertFalse($phpdoc->hasTag('return'));
$this->assertTrue($phpdoc->hasTag('see'));
}
/**
* @covers ::initPhpDoc
* @throws \ReflectionException
*/
public function testInitPhpDocClosureWithReturnType(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(
new ReflectionFunction(
/**
* Test docblock.
*/
function (): int {
return 0;
}
)
);
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock', $phpdoc->getText());
$this->assertFalse($phpdoc->hasTag('param'));
$this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return'));
$this->assertTrue($phpdoc->hasTag('see'));
}
/**
* @covers ::initPhpDoc
*/
public function testInitPhpDocParamsAddedOnlyNotPresent(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(
new ReflectionFunction(
/**
* Test docblock.
*
* @param \stdClass|null $a aaaaa
*/
function ($a = null): int {
return 0;
}
)
);
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock', $phpdoc->getText());
$this->assertEquals('@param \stdClass|null $a aaaaa', $this->tagsToString($phpdoc, 'param'));
$this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return'));
}
/**
* @covers ::initPhpDoc
*/
public function testInitPhpDocReturnAddedOnlyNotPresent(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(
new ReflectionFunction(
/**
* Test docblock.
*
* @return \stdClass|null rrrrrrr
*/
function ($a = null): int {
return 0;
}
)
);
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock', $phpdoc->getText());
$this->assertEquals('@param mixed $a', $this->tagsToString($phpdoc, 'param'));
$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)
{
$tags = $docBlock->getTagsByName($name);
$tags = array_map(
function (Tag $tag) {
return trim((string)$tag);
},
$tags
);
$tags = implode(PHP_EOL, $tags);
return $tags;
}
/**
* Test that we can actually instantiate the class
*/
public function testCanInstantiate()
{
$reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke');
$macro = new Macro($reflectionMethod, UrlGenerator::class, new \ReflectionClass(UrlGenerator::class), 'macroName');
$this->assertInstanceOf(Macro::class, $macro);
}
/**
* Test the output of a class
*/
public function testOutput()
{
$reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke');
$macro = new Macro($reflectionMethod, 'URL', new \ReflectionClass(UrlGenerator::class), 'macroName');
$output = <<<'DOC'
/**
*
*
* @param string $foo
* @param int $bar
* @return string
* @see \Barryvdh\LaravelIdeHelper\Tests\UrlGeneratorMacroClass::__invoke()
* @static
*/
DOC;
$this->assertSame($output, $macro->getDocComment(''));
$this->assertSame('__invoke', $macro->getRealName());
$this->assertSame('\\' . UrlGenerator::class, $macro->getDeclaringClass());
$this->assertSame('$foo, $bar', $macro->getParams(true));
$this->assertSame(['$foo', '$bar'], $macro->getParams(false));
$this->assertSame('$foo, $bar = 0', $macro->getParamsWithDefault(true));
$this->assertSame(['$foo', '$bar = 0'], $macro->getParamsWithDefault(false));
$this->assertTrue($macro->shouldReturn());
$this->assertSame('$instance->__invoke($foo, $bar)', $macro->getRootMethodCall());
}
}
/**
* @internal
* @noinspection PhpMultipleClassesDeclarationsInOneFile
*/
class MacroMock extends Macro
{
public function __construct()
{
// no need to call parent
}
public function getPhpDoc(ReflectionFunctionAbstract $method, ReflectionClass $class = null): DocBlock
{
return (new Macro($method, '', $class ?? $method->getClosureScopeClass()))->phpdoc;
}
}
/**
* Example of an invokable class to be used as a macro.
*/
class UrlGeneratorMacroClass
{
/**
* @param string $foo
* @param int $bar
* @return string
*/
public function __invoke(string $foo, int $bar = 0): string
{
return '';
}
}
+39 -5
View File
@@ -6,7 +6,6 @@ namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\Method; use Barryvdh\LaravelIdeHelper\Method;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Application;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class MethodTest extends TestCase class MethodTest extends TestCase
@@ -59,10 +58,6 @@ DOC;
*/ */
public function testEloquentBuilderOutput() public function testEloquentBuilderOutput()
{ {
if ((int) Application::VERSION < 8) {
$this->markTestSkipped('This test requires Laravel 8.0 or higher');
}
$reflectionClass = new \ReflectionClass(Builder::class); $reflectionClass = new \ReflectionClass(Builder::class);
$reflectionMethod = $reflectionClass->getMethod('with'); $reflectionMethod = $reflectionClass->getMethod('with');
@@ -102,6 +97,37 @@ DOC;
$this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true)); $this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true));
$this->assertSame(['$chars = \'$\\\'\\\\\''], $method->getParamsWithDefault(false)); $this->assertSame(['$chars = \'$\\\'\\\\\''], $method->getParamsWithDefault(false));
} }
/**
* Test the output of a class when using class aliases for it
*/
public function testClassAliases()
{
$reflectionClass = new \ReflectionClass(ExampleClass::class);
$reflectionMethod = $reflectionClass->getMethod('getApplication');
$method = new Method($reflectionMethod, 'Example', $reflectionClass, null, [], [
'Application' => '\\Illuminate\\Foundation\\Application',
]);
$output = <<<'DOC'
/**
*
*
* @return \Illuminate\Foundation\Application
* @static
*/
DOC;
$this->assertSame($output, $method->getDocComment(''));
$this->assertSame('getApplication', $method->getName());
$this->assertSame('\\' . ExampleClass::class, $method->getDeclaringClass());
$this->assertSame('', $method->getParams(true));
$this->assertSame([], $method->getParams(false));
$this->assertSame('', $method->getParamsWithDefault(true));
$this->assertSame([], $method->getParamsWithDefault(false));
$this->assertTrue($method->shouldReturn());
}
} }
class ExampleClass class ExampleClass
@@ -120,4 +146,12 @@ class ExampleClass
{ {
return; return;
} }
/**
* @return Application
*/
public function getApplication()
{
return;
}
} }
+56
View File
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\UsesResolver;
use PHPUnit\Framework\TestCase;
class UsesResolverTest extends TestCase
{
/**
* Test that we can correctly load uses from supplied code
*/
public function testLoadFromCode()
{
$usesResolver = new UsesResolver();
$code = <<<'DOC'
<?php
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\UsesResolver as MyUsesResolver;
use PHPUnit\Framework\TestCase;
class UsesResolverTest extends TestCase
{
//
}
DOC;
$this->assertEquals(
$usesResolver->loadFromCode('Barryvdh\\LaravelIdeHelper\\Tests\\UsesResolverTest', $code),
[
'MyUsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
'TestCase' => '\\PHPUnit\Framework\TestCase',
]
);
}
/**
* Test that we can correctly load uses from a class
*/
public function testLoadFromClass()
{
$usesResolver = new UsesResolver();
$this->assertEquals(
$usesResolver->loadFromClass(self::class),
[
'UsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
'TestCase' => '\\PHPUnit\Framework\TestCase',
]
);
}
}