Compare commits

...
17 Commits
Author SHA1 Message Date
Barry vd. Heuvel f4d110d7a8 Fix baseline 2026-02-07 14:29:43 +01:00
Barry vd. Heuvel 5af7ebdec7 Fix cs 2026-02-07 14:27:39 +01:00
Barry vd. Heuvel fe65d97001 Use larastan instead of psalm 2026-02-07 14:24:53 +01:00
SavaandSava Markovic d87ec5835d Support all DNF types from PHP RFC (#1751)
* Support all DNF types from PHP RFC

* Update failing test

---------

Co-authored-by: Sava Markovic <[email protected]>
2026-01-26 14:24:36 +01:00
Evan Burrell b0dcd7c62f Make Soft Deleted Relationships Nullable (#1749) 2026-01-11 14:34:18 +01:00
JaspurandBarry vd. Heuvel 2ac73f2953 Skip calling fake() when required parameters exist (Socialite compatibility) (#1746)
* Skip calling fake() on facades that require parameters (fix Socialite error)

The ide-helper attempted to invoke Facade::fake() unconditionally, which
breaks when a facade (such as Laravel Socialite) requires a mandatory
parameter in its fake() method signature. This resulted in an
ArgumentCountError during `php artisan ide-helper:generate`.

This patch adds a ReflectionMethod check and skips calling fake() when
required parameters are present, ensuring compatibility with Socialite
and preserving expected behavior for facades that support parameterless
faking.

* Update Alias.php

---------

Co-authored-by: Barry vd. Heuvel <[email protected]>
2025-12-10 10:31:15 +01:00
Barry vd. Heuvel c95825a745 Fix test 2025-12-10 10:16:51 +01:00
barryvdh db606f30d1 Update CHANGELOG 2025-12-10 09:12:44 +00:00
Yannick Schinko b106f7ee85 Add support for decimal column type (#1739)
* Add support for `decimal` column type

Previously `decimal` columns were represented with a cast. This now is no longer the case and instead the column type itself is `decimal`. This change takes that into account.

* The column type may also be `numeric`

* Update tests
2025-12-10 10:11:07 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d61d20357e Bump the deps group across 1 directory with 2 updates (#1743)
Bumps the deps group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

Updates `stefanzweifel/git-auto-commit-action` from 6 to 7
- [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases)
- [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: deps
- dependency-name: stefanzweifel/git-auto-commit-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: deps
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 10:10:25 +01:00
Jesper Noordsij e9283c40a2 Remove calls to PHP 8.5-deprecated setAccessible (#1744)
These are no-op as of PHP 8.1 and thus no longer needed

See also https://wiki.php.net/rfc/make-reflection-setaccessible-no-op
2025-12-10 10:10:09 +01:00
WentTheFox 12a0d7d54c Fix alias fake error (#1745)
* Add catch block in Alias::detectFake

* Update Alias unit tests
2025-12-10 10:09:42 +01:00
Serhii Petrov 9ef25f60e7 Add php 8.5 support (#1735) 2025-10-01 15:12:26 +02:00
Keita Shima 8dff6bb5ff Fix typos in documentation and code comments (#1733)
- Fixed 'seperate' to 'separate' in README.md (2 occurrences)
- Fixed 'columname' to 'columnname' in README.md (2 occurrences)
- Fixed 'Wether' to 'Whether' in src/Method.php PHPDoc comments (2 occurrences)
2025-09-15 14:14:26 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 53854ea7d9 Bump actions/checkout from 4 to 5 in the deps group (#1731)
Bumps the deps group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: deps
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 16:32:00 +02:00
Peter Jørgensen 1fa84a3b65 fix methodsto typo (#1723) 2025-08-04 15:07:10 +02:00
barryvdh 5e973967a7 Update CHANGELOG 2025-07-18 14:10:29 +00:00
55 changed files with 1253 additions and 231 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Git checkout - name: Git checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Validate Composer configuration - name: Validate Composer configuration
run: composer validate --strict run: composer validate --strict
@@ -23,6 +23,6 @@ jobs:
composer global require ergebnis/composer-normalize composer global require ergebnis/composer-normalize
composer normalize composer normalize
- uses: stefanzweifel/git-auto-commit-action@v6 - uses: stefanzweifel/git-auto-commit-action@v7
with: with:
commit_message: normalize composer.json commit_message: normalize composer.json
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@@ -27,7 +27,7 @@ jobs:
- run: composer fix-style - run: composer fix-style
continue-on-error: true continue-on-error: true
- uses: stefanzweifel/git-auto-commit-action@v6 - uses: stefanzweifel/git-auto-commit-action@v7
with: with:
commit_message: composer fix-style commit_message: composer fix-style
commit_author: laravel-ide-helper <[email protected]> commit_author: laravel-ide-helper <[email protected]>
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
name: P${{ matrix.php }} - Laravel${{ matrix.laravel }} name: P${{ matrix.php }} - Laravel${{ matrix.laravel }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
path: src path: src
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@@ -25,4 +25,4 @@ jobs:
run: composer install --prefer-dist --no-progress --no-suggest run: composer install --prefer-dist --no-progress --no-suggest
- name: Analyze - name: Analyze
run: composer analyze run: vendor/bin/phpstan --no-progress --error-format=github
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
php: [8.4, 8.3, 8.2] php: [8.5, 8.4, 8.3, 8.2]
laravel: [~11.15, 12.x] laravel: [~11.15, 12.x]
stability: [prefer-lowest, prefer-stable] stability: [prefer-lowest, prefer-stable]
include: include:
@@ -37,7 +37,7 @@ jobs:
git config --global core.eol lf git config --global core.eol lf
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: ${{ github.event.release.target_commitish }} ref: ${{ github.event.release.target_commitish }}
@@ -27,7 +27,7 @@ jobs:
release-notes: ${{ github.event.release.body }} release-notes: ${{ github.event.release.body }}
- name: Commit updated CHANGELOG - name: Commit updated CHANGELOG
uses: stefanzweifel/git-auto-commit-action@v6 uses: stefanzweifel/git-auto-commit-action@v7
with: with:
branch: ${{ github.event.release.target_commitish }} branch: ${{ github.event.release.target_commitish }}
commit_message: Update CHANGELOG commit_message: Update CHANGELOG
+1 -1
View File
@@ -1,5 +1,5 @@
.phpunit.result.cache .phpunit.result.cache
/build
/.idea /.idea
/.php-cs-fixer.cache /.php-cs-fixer.cache
/.php-cs-fixer.php /.php-cs-fixer.php
+1 -1
View File
@@ -3,7 +3,7 @@ require __DIR__ . '/vendor/autoload.php';
$finder = PhpCsFixer\Finder::create() $finder = PhpCsFixer\Finder::create()
->in(__DIR__) ->in(__DIR__)
->exclude('tests'); ->exclude(['tests', 'build']);
$config = require __DIR__ . '/.php-cs-fixer.common.php'; $config = require __DIR__ . '/.php-cs-fixer.common.php';
+54
View File
@@ -1,5 +1,59 @@
# Changelog # Changelog
## v3.6.1 - 2025-12-10
### What's Changed
* Fix `methodsto` typo in README by @peterchrjoergensen in https://github.com/barryvdh/laravel-ide-helper/pull/1723
* Bump actions/checkout from 4 to 5 in the deps group by @dependabot[bot] in https://github.com/barryvdh/laravel-ide-helper/pull/1731
* Fix typos in documentation and code comments by @kei1111 in https://github.com/barryvdh/laravel-ide-helper/pull/1733
* Add php 8.5 support by @sergiy-petrov in https://github.com/barryvdh/laravel-ide-helper/pull/1735
* Fix alias fake error by @WentTheFox in https://github.com/barryvdh/laravel-ide-helper/pull/1745
* Remove calls to PHP 8.5-deprecated `setAccessible` by @jnoordsij in https://github.com/barryvdh/laravel-ide-helper/pull/1744
* Bump the deps group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/barryvdh/laravel-ide-helper/pull/1743
* Add support for `decimal` column type by @BrainStone in https://github.com/barryvdh/laravel-ide-helper/pull/1739
### New Contributors
* @peterchrjoergensen made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1723
* @kei1111 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1733
* @WentTheFox made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1745
* @BrainStone made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1739
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.6.0...v3.6.1
## v3.6.0 - 2025-07-18
### What's Changed
* fix: Change AsArrayObject cast to be Laravel's ArrayObject by @wsamoht in https://github.com/barryvdh/laravel-ide-helper/pull/1675
* Add extends declaration for Macroable classes to fix missing inherited methods by @KentarouTakeda in https://github.com/barryvdh/laravel-ide-helper/pull/1674
* fix(meta): ignore aliases in the autoloader (Fixes #1671) by @pataar in https://github.com/barryvdh/laravel-ide-helper/pull/1686
* feat(ModelsCommand): add support for the new Scope attribute by @pataar in https://github.com/barryvdh/laravel-ide-helper/pull/1694
* fix type change for scope default float parameter by @nivseb in https://github.com/barryvdh/laravel-ide-helper/pull/1697
* Revert #1629 - *Allow adding custom Macroable classes* by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1707
* Configurable macro return type defaults by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1711
* docs(readme): add Laravel 12 support information by @SantosVilanculos in https://github.com/barryvdh/laravel-ide-helper/pull/1717
* Add multi-level directory support for translation files by @RosiersRobin in https://github.com/barryvdh/laravel-ide-helper/pull/1718
* Support `AsCollection::of($map)`, `AsCollection::using($class, $map)` by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1714
* fix: Fixed wrong doc for SoftDeletes `withTrashed` method by @eldair in https://github.com/barryvdh/laravel-ide-helper/pull/1688
* Support other OS on tests by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1715
* Fix tests on windows by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1720
* Bump stefanzweifel/git-auto-commit-action from 5 to 6 in the deps group by @dependabot[bot] in https://github.com/barryvdh/laravel-ide-helper/pull/1719
* Update .gitattributes - avoid all `.php-cs-fixer` files on vendor by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1708
* fix(ModelsCommand): use 'string' as realType for 'encrypted' casts by @pataar in https://github.com/barryvdh/laravel-ide-helper/pull/1698
* Trim strings and bump reflection docblock by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1721
* Add magic *_exists properties by @erikn69 in https://github.com/barryvdh/laravel-ide-helper/pull/1712
### New Contributors
* @wsamoht made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1675
* @nivseb made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1697
* @SantosVilanculos made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1717
* @RosiersRobin made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1718
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.5...v3.6.0
## v3.5.5 - 2025-02-21 ## v3.5.5 - 2025-02-21
### What's Changed ### What's Changed
+2 -2
View File
@@ -51,7 +51,7 @@ php artisan ide-helper:models -RW
If you don't want the full _ide_helper.php file, you can run add `--write-eloquent-helper` to the model command to generate small version, which is required for the `@mixin \Eloquent` to be able to add the QueryBuilder methods. If you don't want the full _ide_helper.php file, you can run add `--write-eloquent-helper` to the model command to generate small version, which is required for the `@mixin \Eloquent` to be able to add the QueryBuilder methods.
If you don't want to add all the phpdocs to your Models directly, you can use `--nowrite` to create a seperate file. The `--write-mixin` option can be used to only add a `@mixin` to your models, but add the generated phpdocs in a seperate file. This avoids having the results marked as duplicate. If you don't want to add all the phpdocs to your Models directly, you can use `--nowrite` to create a separate file. The `--write-mixin` option can be used to only add a `@mixin` to your models, but add the generated phpdocs in a separate file. This avoids having the results marked as duplicate.
_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!_
@@ -212,7 +212,7 @@ If for some reason it's undesired to have them generated (one for each column),
#### Magic `*_count` and `*_exists` properties #### Magic `*_count` and `*_exists` properties
You may use the [`::withCount`](https://laravel.com/docs/master/eloquent-relationships#counting-related-models) and [`::withExists`](https://laravel.com/docs/master/eloquent-relationships#other-aggregate-functions) methodsto count the number results from a relationship without actually loading them. Those results are then placed in attributes following the `<columname>_count` and `<columname>_exists` convention. You may use the [`::withCount`](https://laravel.com/docs/master/eloquent-relationships#counting-related-models) and [`::withExists`](https://laravel.com/docs/master/eloquent-relationships#other-aggregate-functions) methods to count the number results from a relationship without actually loading them. Those results are then placed in attributes following the `<columnname>_count` and `<columnname>_exists` convention.
By default, these attributes are generated in the phpdoc. You can turn them off by setting the config `write_model_relation_count_properties` and `write_model_relation_exists_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` and `write_model_relation_exists_properties` to `false`.
+4 -3
View File
@@ -35,11 +35,12 @@
"friendsofphp/php-cs-fixer": "^3", "friendsofphp/php-cs-fixer": "^3",
"illuminate/config": "^11.15 || ^12", "illuminate/config": "^11.15 || ^12",
"illuminate/view": "^11.15 || ^12", "illuminate/view": "^11.15 || ^12",
"larastan/larastan": "^3.1",
"mockery/mockery": "^1.4", "mockery/mockery": "^1.4",
"orchestra/testbench": "^9.2 || ^10", "orchestra/testbench": "^9.2 || ^10",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^10.5 || ^11.5.3", "phpunit/phpunit": "^10.5 || ^11.5.3",
"spatie/phpunit-snapshot-assertions": "^4 || ^5", "spatie/phpunit-snapshot-assertions": "^4 || ^5",
"vimeo/psalm": "^5.4",
"vlucas/phpdotenv": "^5" "vlucas/phpdotenv": "^5"
}, },
"suggest": { "suggest": {
@@ -74,7 +75,7 @@
} }
}, },
"scripts": { "scripts": {
"analyze": "psalm", "analyze": "phpstan",
"check-style": [ "check-style": [
"php-cs-fixer fix --diff --diff-format=udiff --dry-run", "php-cs-fixer fix --diff --diff-format=udiff --dry-run",
"php-cs-fixer fix --diff --diff-format=udiff --dry-run --config=.php_cs.tests.php" "php-cs-fixer fix --diff --diff-format=udiff --dry-run --config=.php_cs.tests.php"
@@ -83,7 +84,7 @@
"php-cs-fixer fix", "php-cs-fixer fix",
"php-cs-fixer fix --config=.php-cs-fixer.tests.php" "php-cs-fixer fix --config=.php-cs-fixer.tests.php"
], ],
"psalm-set-baseline": "psalm --set-baseline=psalm-baseline.xml", "analyze-set-baseline": "phpstan --generate-baseline",
"test": "phpunit", "test": "phpunit",
"test-ci": "phpunit -d --without-creating-snapshots", "test-ci": "phpunit -d --without-creating-snapshots",
"test-regenerate": "phpunit -d --update-snapshots" "test-regenerate": "phpunit -d --update-snapshots"
+23 -14
View File
@@ -47,20 +47,6 @@ return [
'include_fluent' => false, 'include_fluent' => false,
/*
|--------------------------------------------------------------------------
| Factory builders
|--------------------------------------------------------------------------
|
| Set to true to generate factory generators for better factory()
| method auto-completion.
|
| Deprecated for Laravel 8 or latest.
|
*/
'include_factory_builders' => false,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Write model magic methods | Write model magic methods
@@ -339,6 +325,29 @@ return [
'enforce_nullable_relationships' => true, 'enforce_nullable_relationships' => true,
/*
|--------------------------------------------------------------------------
| Make soft deletable relations nullable
|--------------------------------------------------------------------------
|
| When set to true (default), relationships to models using SoftDeletes trait
| will be marked as nullable. This is because soft-deleted records are excluded
| from queries by default, meaning even non-nullable foreign keys can return
| null when the related model is soft-deleted.
|
| Default: true
| A relationship to a soft-deletable model will include |null in the type:
| * @property-read Team|null $team
|
| Option: false
| A relationship to a soft-deletable model will NOT include |null (unless
| nullable for other reasons such as nullable foreign key column):
| * @property-read Team $team
|
*/
'soft_deletes_force_nullable' => true,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Run artisan commands after migrations to generate model helpers | Run artisan commands after migrations to generate model helpers
-1
View File
@@ -124,7 +124,6 @@ $reflection = new ReflectionClass($loader);
$property = $reflection->hasProperty('paths') $property = $reflection->hasProperty('paths')
? $reflection->getProperty('paths') ? $reflection->getProperty('paths')
: $reflection->getProperty('path'); : $reflection->getProperty('path');
$property->setAccessible(true);
$paths = Illuminate\Support\Arr::wrap($property->getValue($loader)); $paths = Illuminate\Support\Arr::wrap($property->getValue($loader));
+67
View File
@@ -0,0 +1,67 @@
parameters:
ignoreErrors:
-
message: '#^File ends with a trailing whitespace\. This may cause problems when running the code in the web browser\. Remove the closing \?\> mark or remove the whitespace\.$#'
identifier: whitespace.fileEnd
count: 1
path: resources/views/helper.php
-
message: '#^Call to an undefined method Illuminate\\Contracts\\Filesystem\\Filesystem\:\:requireOnce\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/MetaCommand.php
-
message: '#^Call to an undefined method Illuminate\\Contracts\\Foundation\\Application\:\:getBindings\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/MetaCommand.php
-
message: '#^Call to an undefined method Barryvdh\\Reflection\\DocBlock\\Tag\:\:getMethodName\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/ModelsCommand.php
-
message: '#^Call to an undefined method Barryvdh\\Reflection\\DocBlock\\Tag\:\:getType\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/ModelsCommand.php
-
message: '#^Call to an undefined method Barryvdh\\Reflection\\DocBlock\\Tag\:\:getVariableName\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/ModelsCommand.php
-
message: '#^Call to an undefined method Illuminate\\Support\\Optional\:\:getNumberOfParameters\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/ModelsCommand.php
-
message: '#^Call to an undefined method Illuminate\\Support\\Optional\:\:getParameters\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/ModelsCommand.php
-
message: '#^Call to an undefined method Illuminate\\Support\\Optional\:\:getReturnType\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Console/ModelsCommand.php
-
message: '#^Call to an undefined static method Illuminate\\Database\\Eloquent\\Model\:\:newFactory\(\)\.$#'
identifier: staticMethod.notFound
count: 2
path: src/Console/ModelsCommand.php
-
message: '#^Call to an undefined method ReflectionFunctionAbstract\:\:getDeclaringClass\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Method.php
+11
View File
@@ -0,0 +1,11 @@
includes:
- phpstan-baseline.neon
- vendor/phpstan/phpstan-phpunit/extension.neon
parameters:
level: 2
paths:
- src
- resources/views
excludePaths:
-24
View File
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.4.0@62db5d4f6a7ae0a20f7cc5a4952d730272fc0863">
<file src="src/Factories.php">
<UndefinedClass occurrences="2">
<code>Factory</code>
<code>Factory</code>
</UndefinedClass>
</file>
<file src="src/Generator.php">
<UndefinedClass occurrences="6">
<code>\Auth</code>
<code>\Cache</code>
<code>\Cache</code>
<code>\DB</code>
<code>\Queue</code>
<code>\Storage</code>
</UndefinedClass>
</file>
<file src="src/Console/ModelsCommand.php">
<UndefinedClass occurrences="1">
<code>\UnitEnum</code>
</UndefinedClass>
</file>
</files>
-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>
+1 -9
View File
@@ -3,6 +3,7 @@
/** /**
* @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_alias_ns * @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_alias_ns
* @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_extends_ns * @var Barryvdh\LaravelIdeHelper\Alias[][] $namespaces_by_extends_ns
* @var string[] $real_time_facades
* @var bool $include_fluent * @var bool $include_fluent
* @var string $helpers * @var string $helpers
*/ */
@@ -119,12 +120,3 @@ namespace Illuminate\Support {
} }
<?php endif ?> <?php endif ?>
<?php foreach ($factories as $factory) : ?>
namespace <?=$factory->getNamespaceName()?> {
/**
* @method \Illuminate\Database\Eloquent\Collection|<?=$factory->getShortName()?>[]|<?=$factory->getShortName()?> create($attributes = [])
* @method \Illuminate\Database\Eloquent\Collection|<?=$factory->getShortName()?>[]|<?=$factory->getShortName()?> make($attributes = [])
*/
class <?=$factory->getShortName()?>FactoryBuilder extends \Illuminate\Database\Eloquent\FactoryBuilder {}
}
<?php endforeach; ?>
+14 -11
View File
@@ -1,4 +1,16 @@
<?= '<?php' ?> <?= '<?php' ?>
<?php
/**
* @var array $bindings
* @var string[] $methods
* @var string[] $configMethods
* @var Illuminate\Support\Collection $configValues
* @var array<string, array> $expectedArgumentSets
* @var array $expectedArguments
* @var string[] $userMethods
* @var string $userModel/
* */
?>
/* @noinspection ALL */ /* @noinspection ALL */
// @formatter:off // @formatter:off
@@ -35,15 +47,6 @@ namespace PHPSTORM_META {
])); ]));
<?php endforeach; ?> <?php endforeach; ?>
<?php if (count($factories)) : ?>
override(\factory(0), map([
'' => '@FactoryBuilder',
<?php foreach ($factories as $factory) : ?>
'<?= $factory->getName() ?>' => \<?= $factory->getName() ?>FactoryBuilder::class,
<?php endforeach; ?>
]));
<?php endif; ?>
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::mock(0), map(["" => "@&\Mockery\MockInterface"])); override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::mock(0), map(["" => "@&\Mockery\MockInterface"]));
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::partialMock(0), map(["" => "@&\Mockery\MockInterface"])); override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::partialMock(0), map(["" => "@&\Mockery\MockInterface"]));
override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::instance(0), type(1)); override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::instance(0), type(1));
@@ -79,7 +82,7 @@ namespace PHPSTORM_META {
override(\tap(0), type(0)); override(\tap(0), type(0));
override(\optional(0), type(0)); override(\optional(0), type(0));
<?php if (isset($expectedArgumentSets)): ?> <?php if ($expectedArgumentSets): ?>
<?php foreach ($expectedArgumentSets as $name => $argumentsList) : ?> <?php foreach ($expectedArgumentSets as $name => $argumentsList) : ?>
registerArgumentsSet('<?= $name ?>', <?php foreach ($argumentsList as $i => $arg) : ?><?php if ($i % 5 == 0) { registerArgumentsSet('<?= $name ?>', <?php foreach ($argumentsList as $i => $arg) : ?><?php if ($i % 5 == 0) {
echo "\n"; echo "\n";
@@ -87,7 +90,7 @@ namespace PHPSTORM_META {
<?php endforeach; ?> <?php endforeach; ?>
<?php endif ?> <?php endif ?>
<?php if (isset($expectedArguments)) : ?> <?php if ($expectedArguments) : ?>
<?php foreach ($expectedArguments as $arguments) : ?> <?php foreach ($expectedArguments as $arguments) : ?>
<?php <?php
$classes = isset($arguments['class']) ? (array) $arguments['class'] : [null]; $classes = isset($arguments['class']) ? (array) $arguments['class'] : [null];
+7 -6
View File
@@ -248,11 +248,17 @@ class Alias
return; return;
} }
$reflection = new \ReflectionMethod($facade, 'fake');
if ($reflection->getNumberOfRequiredParameters() > 0) {
return;
}
$real = $facade::getFacadeRoot(); $real = $facade::getFacadeRoot();
try { try {
$facade::fake(); $facade::fake();
$fake = $facade::getFacadeRoot(); $fake = $facade::getFacadeRoot();
if ($fake !== $real) { if ($fake !== $real) {
$this->addClass(get_class($fake)); $this->addClass(get_class($fake));
} }
@@ -319,9 +325,8 @@ class Alias
/** /**
* Get the real root of a facade * Get the real root of a facade
* *
* @return bool|string
*/ */
protected function detectRoot() protected function detectRoot(): void
{ {
$facade = $this->facade; $facade = $this->facade;
@@ -380,7 +385,6 @@ class Alias
if ($class !== $this->root) { if ($class !== $this->root) {
$this->methods[] = new Method( $this->methods[] = new Method(
$method, $method,
$this->alias,
$class, $class,
$magic, $magic,
$this->interfaces, $this->interfaces,
@@ -397,7 +401,6 @@ class Alias
/** /**
* Get the methods for one or multiple classes. * Get the methods for one or multiple classes.
* *
* @return string
*/ */
protected function detectMethods() protected function detectMethods()
{ {
@@ -413,7 +416,6 @@ class Alias
if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') { if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') {
$this->methods[] = new Method( $this->methods[] = new Method(
$method, $method,
$this->alias,
$reflection, $reflection,
$method->name, $method->name,
$this->interfaces, $this->interfaces,
@@ -443,7 +445,6 @@ class Alias
// Add macros // Add macros
$this->methods[] = new Macro( $this->methods[] = new Macro(
$method, $method,
$this->alias,
$reflection, $reflection,
$macro_name, $macro_name,
$this->interfaces, $this->interfaces,
-5
View File
@@ -11,7 +11,6 @@
namespace Barryvdh\LaravelIdeHelper\Console; namespace Barryvdh\LaravelIdeHelper\Console;
use Barryvdh\LaravelIdeHelper\Factories;
use Dotenv\Parser\Entry; use Dotenv\Parser\Entry;
use Dotenv\Parser\Parser; use Dotenv\Parser\Parser;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@@ -108,9 +107,6 @@ class MetaCommand extends Command
*/ */
public function handle() public function handle()
{ {
// Needs to run before exception handler is registered
$factories = $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : [];
$ourAutoloader = $this->registerClassAutoloadExceptions(); $ourAutoloader = $this->registerClassAutoloadExceptions();
$bindings = []; $bindings = [];
@@ -149,7 +145,6 @@ class MetaCommand extends Command
$content = $this->view->make('ide-helper::meta', [ $content = $this->view->make('ide-helper::meta', [
'bindings' => $bindings, 'bindings' => $bindings,
'methods' => $this->methods, 'methods' => $this->methods,
'factories' => $factories,
'configMethods' => $this->configMethods, 'configMethods' => $this->configMethods,
'configValues' => $configValues, 'configValues' => $configValues,
'expectedArgumentSets' => $this->getExpectedArgumentSets(), 'expectedArgumentSets' => $this->getExpectedArgumentSets(),
+65 -16
View File
@@ -584,6 +584,8 @@ class ModelsCommand extends Command
'float', 'real', 'float4', 'float', 'real', 'float4',
'double', 'float8' => 'float', 'double', 'float8' => 'float',
'decimal', 'numeric' => 'numeric',
default => 'string', default => 'string',
}; };
} }
@@ -887,7 +889,6 @@ class ModelsCommand extends Command
if (in_array($relation, ['hasOne', 'hasOneThrough', 'morphOne'], true)) { if (in_array($relation, ['hasOne', 'hasOneThrough', 'morphOne'], true)) {
$defaultProp = $reflectionObj->getProperty('withDefault'); $defaultProp = $reflectionObj->getProperty('withDefault');
$defaultProp->setAccessible(true);
return !$defaultProp->getValue($relationObj); return !$defaultProp->getValue($relationObj);
} }
@@ -897,7 +898,6 @@ class ModelsCommand extends Command
} }
$fkProp = $reflectionObj->getProperty('foreignKey'); $fkProp = $reflectionObj->getProperty('foreignKey');
$fkProp->setAccessible(true);
$enforceNullableRelation = $this->laravel['config']->get('ide-helper.enforce_nullable_relationships', true); $enforceNullableRelation = $this->laravel['config']->get('ide-helper.enforce_nullable_relationships', true);
@@ -911,9 +911,31 @@ class ModelsCommand extends Command
} }
} }
if ($this->relatedModelUsesSoftDeletes($relationObj)) {
return true;
}
return false; return false;
} }
/**
* Check if the related model uses the SoftDeletes trait
*
* @param Relation $relationObj
*
* @return bool
*/
protected function relatedModelUsesSoftDeletes(Relation $relationObj): bool
{
if (!$this->laravel['config']->get('ide-helper.soft_deletes_force_nullable', true)) {
return false;
}
$relatedModel = $relationObj->getRelated();
return in_array('Illuminate\\Database\\Eloquent\\SoftDeletes', class_uses_recursive($relatedModel));
}
/** /**
* Check if the morphTo relation is nullable * Check if the morphTo relation is nullable
* *
@@ -930,7 +952,6 @@ class ModelsCommand extends Command
} }
$fkProp = $reflectionObj->getProperty('foreignKey'); $fkProp = $reflectionObj->getProperty('foreignKey');
$fkProp->setAccessible(true);
foreach (Arr::wrap($fkProp->getValue($relationObj)) as $foreignKey) { foreach (Arr::wrap($fkProp->getValue($relationObj)) as $foreignKey) {
if (isset($this->nullableColumns[$foreignKey])) { if (isset($this->nullableColumns[$foreignKey])) {
@@ -1112,6 +1133,7 @@ class ModelsCommand extends Command
$serializer = new DocBlockSerializer(); $serializer = new DocBlockSerializer();
$docComment = $serializer->getDocComment($phpdoc); $docComment = $serializer->getDocComment($phpdoc);
$mixinClassName = null;
if ($this->write_mixin) { if ($this->write_mixin) {
$phpdocMixin = new DocBlock($reflection, new Context($namespace)); $phpdocMixin = new DocBlock($reflection, new Context($namespace));
@@ -1283,9 +1305,6 @@ class ModelsCommand extends Command
*/ */
protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection
{ {
// Private/protected ReflectionMethods require setAccessible prior to PHP 8.1
$reflectionMethod->setAccessible(true);
/** @var Attribute $attribute */ /** @var Attribute $attribute */
$attribute = $reflectionMethod->invoke($model); $attribute = $reflectionMethod->invoke($model);
@@ -1643,7 +1662,8 @@ class ModelsCommand extends Command
$type = implode('|', $types); $type = implode('|', $types);
if ($paramType->allowsNull()) { if ($paramType->allowsNull()) {
if (count($types) == 1) { // Use ?Type syntax only for single named types, not for intersection types
if (count($types) == 1 && !str_starts_with($type, '(')) {
$type = '?' . $type; $type = '?' . $type;
} else { } else {
$type .= '|null'; $type .= '|null';
@@ -1661,7 +1681,7 @@ class ModelsCommand extends Command
preg_match( preg_match(
'/@param ((?:(?:[\w?|\\\\<>])+(?:\[])?)+)/', '/@param ((?:(?:[\w?|\\\\<>])+(?:\[])?)+)/',
$docComment ?? '', $docComment,
$matches $matches
); );
$type = $matches[1] ?? ''; $type = $matches[1] ?? '';
@@ -1716,24 +1736,53 @@ class ModelsCommand extends Command
return $type; return $type;
} }
protected function extractReflectionTypes(ReflectionType $reflection_type) protected function extractReflectionTypes(ReflectionType $reflection_type): array
{ {
if ($reflection_type instanceof ReflectionNamedType) { if ($reflection_type instanceof ReflectionNamedType) {
$types[] = $this->getReflectionNamedType($reflection_type); return [$this->getReflectionNamedType($reflection_type)];
} else { }
$types = [];
foreach ($reflection_type->getTypes() as $named_type) { if ($reflection_type instanceof \ReflectionIntersectionType) {
if ($named_type->getName() === 'null') { return [$this->formatIntersectionType($reflection_type)];
}
if ($reflection_type instanceof \ReflectionUnionType) {
return $this->extractUnionTypes($reflection_type);
}
// Unknown type - return empty array as fallback
return [];
}
protected function extractUnionTypes(\ReflectionUnionType $union_type): array
{
$types = [];
foreach ($union_type->getTypes() as $inner_type) {
if ($inner_type instanceof ReflectionNamedType) {
if ($inner_type->getName() === 'null') {
continue; continue;
} }
$types[] = $this->getReflectionNamedType($inner_type);
$types[] = $this->getReflectionNamedType($named_type); } elseif ($inner_type instanceof \ReflectionIntersectionType) {
$types[] = $this->formatIntersectionType($inner_type);
} }
// ReflectionUnionType cannot be nested per PHP's DNF rules
} }
return $types; return $types;
} }
protected function formatIntersectionType(\ReflectionIntersectionType $intersection_type): string
{
$parts = [];
foreach ($intersection_type->getTypes() as $type) {
$parts[] = $this->getReflectionNamedType($type);
}
return '(' . implode('&', $parts) . ')';
}
protected function getReflectionNamedType(ReflectionNamedType $paramType): string protected function getReflectionNamedType(ReflectionNamedType $paramType): string
{ {
$parameterName = $paramType->getName(); $parameterName = $paramType->getName();
-36
View File
@@ -1,36 +0,0 @@
<?php
namespace Barryvdh\LaravelIdeHelper;
use Exception;
use Illuminate\Database\Eloquent\Factory;
use ReflectionClass;
class Factories
{
public static function all()
{
$factories = [];
if (static::isLaravelSevenOrLower()) {
$factory = app(Factory::class);
$definitions = (new ReflectionClass(Factory::class))->getProperty('definitions');
$definitions->setAccessible(true);
foreach ($definitions->getValue($factory) as $factory_target => $config) {
try {
$factories[] = new ReflectionClass($factory_target);
} catch (Exception $exception) {
}
}
}
return $factories;
}
protected static function isLaravelSevenOrLower()
{
return class_exists('Illuminate\Database\Eloquent\Factory');
}
}
+1 -2
View File
@@ -74,7 +74,7 @@ class Generator
/** /**
* Generate the helper file contents; * Generate the helper file contents;
* *
* @return string; * @return string
*/ */
public function generate() public function generate()
{ {
@@ -85,7 +85,6 @@ class Generator
->with('real_time_facades', $this->getRealTimeFacades()) ->with('real_time_facades', $this->getRealTimeFacades())
->with('helpers', $this->detectHelpers()) ->with('helpers', $this->detectHelpers())
->with('include_fluent', $this->config->get('ide-helper.include_fluent', true)) ->with('include_fluent', $this->config->get('ide-helper.include_fluent', true))
->with('factories', $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : [])
->render(); ->render();
} }
+39 -10
View File
@@ -15,7 +15,6 @@ class Macro extends Method
* Macro constructor. * Macro constructor.
* *
* @param \ReflectionFunctionAbstract $method * @param \ReflectionFunctionAbstract $method
* @param string $alias
* @param \ReflectionClass $class * @param \ReflectionClass $class
* @param null $methodName * @param null $methodName
* @param array $interfaces * @param array $interfaces
@@ -24,14 +23,13 @@ class Macro extends Method
*/ */
public function __construct( public function __construct(
$method, $method,
$alias,
$class, $class,
$methodName = null, $methodName = null,
$interfaces = [], $interfaces = [],
$classAliases = [], $classAliases = [],
$returnTypeNormalizers = [] $returnTypeNormalizers = []
) { ) {
parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases, $returnTypeNormalizers); parent::__construct($method, $class, $methodName, $interfaces, $classAliases, $returnTypeNormalizers);
} }
public static function setDefaultReturnTypes(array $map = []) public static function setDefaultReturnTypes(array $map = [])
@@ -75,8 +73,8 @@ class Macro extends Method
$type = $this->concatReflectionTypes($return); $type = $this->concatReflectionTypes($return);
/** @psalm-suppress UndefinedClass */
if (!$return instanceof \ReflectionUnionType) { if (!$return instanceof \ReflectionUnionType) {
/** @phpstan-ignore method.notFound */
$type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : ''; $type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : '';
$type .= $return->allowsNull() ? '|null' : ''; $type .= $return->allowsNull() ? '|null' : '';
} }
@@ -93,17 +91,48 @@ class Macro extends Method
protected function concatReflectionTypes(?\ReflectionType $type): string protected function concatReflectionTypes(?\ReflectionType $type): string
{ {
/** @psalm-suppress UndefinedClass */ if ($type instanceof \ReflectionNamedType) {
$returnTypes = $type instanceof \ReflectionUnionType return $type->getName();
? $type->getTypes() }
: [$type];
return Collection::make($returnTypes) if ($type instanceof \ReflectionIntersectionType) {
return $this->formatIntersectionType($type);
}
if ($type instanceof \ReflectionUnionType) {
return $this->formatUnionType($type);
}
// Unknown or null type
return '';
}
protected function formatUnionType(\ReflectionUnionType $type): string
{
return Collection::make($type->getTypes())
->map(function (\ReflectionType $inner) {
if ($inner instanceof \ReflectionNamedType) {
return $inner->getName();
}
if ($inner instanceof \ReflectionIntersectionType) {
return $this->formatIntersectionType($inner);
}
// ReflectionUnionType cannot be nested per PHP's DNF rules
return null;
})
->filter() ->filter()
->map->getName()
->implode('|'); ->implode('|');
} }
protected function formatIntersectionType(\ReflectionIntersectionType $type): string
{
$parts = Collection::make($type->getTypes())
->map(fn (\ReflectionNamedType $t) => $t->getName())
->toArray();
return '(' . implode('&', $parts) . ')';
}
protected function addLocationToPhpDoc() protected function addLocationToPhpDoc()
{ {
if ($this->method->name === '__invoke') { if ($this->method->name === '__invoke') {
+3 -4
View File
@@ -44,7 +44,6 @@ class Method
/** /**
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method * @param \ReflectionMethod|\ReflectionFunctionAbstract $method
* @param string $alias
* @param \ReflectionClass $class * @param \ReflectionClass $class
* @param string|null $methodName * @param string|null $methodName
* @param array $interfaces * @param array $interfaces
@@ -52,7 +51,7 @@ class Method
* @param array $returnTypeNormalizers * @param array $returnTypeNormalizers
* @param string[] $templateNames * @param string[] $templateNames
*/ */
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [], array $templateNames = []) public function __construct($method, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [], array $templateNames = [])
{ {
$this->method = $method; $this->method = $method;
$this->interfaces = $interfaces; $this->interfaces = $interfaces;
@@ -178,7 +177,7 @@ class Method
/** /**
* Get the parameters for this method * Get the parameters for this method
* *
* @param bool $implode Wether to implode the array or not * @param bool $implode Whether to implode the array or not
* @return string * @return string
*/ */
public function getParams($implode = true) public function getParams($implode = true)
@@ -208,7 +207,7 @@ class Method
/** /**
* Get the parameters for this method including default values * Get the parameters for this method including default values
* *
* @param bool $implode Wether to implode the array or not * @param bool $implode Whether to implode the array or not
* @return string * @return string
*/ */
public function getParamsWithDefault($implode = true) public function getParamsWithDefault($implode = true)
+39
View File
@@ -9,6 +9,7 @@ use Barryvdh\LaravelIdeHelper\Macro;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Facade;
/** /**
* @internal * @internal
@@ -66,6 +67,22 @@ class AliasTest extends TestCase
$this->assertNotNull($this->getAliasMacro($alias, EloquentBuilder::class, $macro)); $this->assertNotNull($this->getAliasMacro($alias, EloquentBuilder::class, $macro));
} }
/**
* @covers ::detectFake
*/
public function testNoExceptionOnRequiredFakeParameters(): void
{
// Mock
$alias = new AliasMock();
// Prepare
$alias->setFacade(MockFacade::class);
$this->expectNotToPerformAssertions();
// Test
$alias->detectFake();
}
/** /**
* @covers ::detectTemplateNames * @covers ::detectTemplateNames
*/ */
@@ -118,4 +135,26 @@ class AliasMock extends Alias
{ {
parent::detectMethods(); parent::detectMethods();
} }
public function detectFake()
{
parent::detectFake();
}
public function setFacade(string $facade)
{
$this->facade = $facade;
}
}
class MockFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'Illuminate\Foundation\Exceptions\Handler';
}
public static function fake(string $test1, $test2 = null)
{
}
} }
@@ -42,8 +42,8 @@ use Illuminate\Database\Eloquent\Model;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -0,0 +1,218 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DnfTypes\Models;
use ArrayAccess;
use Countable;
use Illuminate\Database\Eloquent\Model;
use Iterator;
use Stringable;
use Traversable;
/**
* Test model covering all DNF type scenarios from PHP RFC.
*
* @see https://wiki.php.net/rfc/dnf_types
*/
class DnfTypeModel extends Model
{
// =========================================================================
// RFC Example 1: (A&B)|null - Intersection with null
// =========================================================================
/**
* Basic DNF: intersection OR null.
*/
public function getBasicDnfAttribute(): (Countable&Iterator)|null
{
return null;
}
// =========================================================================
// RFC Example 2: (A&B)|D - Intersection OR single type (no null)
// =========================================================================
/**
* Intersection OR single class type.
*/
public function getIntersectionOrClassAttribute(): (Countable&Iterator)|\stdClass
{
return new \stdClass();
}
// =========================================================================
// RFC Example 3: C|(X&D)|null - Single type OR intersection OR null
// =========================================================================
/**
* Single type OR intersection OR null.
*/
public function getSingleOrIntersectionOrNullAttribute(): \stdClass|(Countable&Iterator)|null
{
return null;
}
// =========================================================================
// RFC Example 4: (A&B&D)|int|null - Triple intersection OR primitive OR null
// =========================================================================
/**
* Triple intersection OR primitive OR null.
*/
public function getTripleIntersectionAttribute(): (Countable&Iterator&Traversable)|int|null
{
return null;
}
// =========================================================================
// RFC Example 5: (A&B)|(C&D)|null - Multiple intersection segments
// =========================================================================
/**
* Multiple intersection segments OR null.
*/
public function getMultipleIntersectionsAttribute(): (Countable&Iterator)|(ArrayAccess&Stringable)|null
{
return null;
}
// =========================================================================
// RFC Example 6: (A&B)|(C&D)|(E&F) - Multiple intersections without null
// =========================================================================
/**
* Multiple intersection segments without null.
*/
public function getMultipleIntersectionsNoNullAttribute(): (Countable&Iterator)|(ArrayAccess&Stringable)|(Traversable&Countable)
{
return new class () implements Countable, Iterator {
public function count(): int
{
return 0;
}
public function current(): mixed
{
return null;
}
public function key(): mixed
{
return null;
}
public function next(): void
{
}
public function rewind(): void
{
}
public function valid(): bool
{
return false;
}
};
}
// =========================================================================
// Pure intersection type (A&B) - No union, just intersection
// =========================================================================
/**
* Pure intersection type without union.
*/
public function getPureIntersectionAttribute(): Countable&Iterator
{
return new class () implements Countable, Iterator {
public function count(): int
{
return 0;
}
public function current(): mixed
{
return null;
}
public function key(): mixed
{
return null;
}
public function next(): void
{
}
public function rewind(): void
{
}
public function valid(): bool
{
return false;
}
};
}
// =========================================================================
// Parameter context: DNF types in method parameters
// =========================================================================
/**
* Scope with DNF parameter: (A&B)|null.
*/
public function scopeWithBasicDnfParam(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|null $param
): \Illuminate\Database\Query\Builder {
return $query;
}
/**
* Scope with complex DNF parameter: C|(A&B)|null.
*/
public function scopeWithComplexDnfParam(
\Illuminate\Database\Query\Builder $query,
\stdClass|(Countable&Iterator)|null $param
): \Illuminate\Database\Query\Builder {
return $query;
}
/**
* Scope with multiple intersection parameters.
*/
public function scopeWithMultipleIntersectionParam(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|(ArrayAccess&Stringable) $param
): \Illuminate\Database\Query\Builder {
return $query;
}
/**
* Scope with intersection OR primitive parameter.
*/
public function scopeWithIntersectionOrPrimitive(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|int $param
): \Illuminate\Database\Query\Builder {
return $query;
}
// =========================================================================
// Mixed: DNF in both parameter and return type
// =========================================================================
/**
* DNF in both parameter and return type.
*/
public function scopeWithDnfParamAndReturn(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|null $param
): (Countable&Iterator)|\Illuminate\Database\Query\Builder {
return $query;
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DnfTypes;
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,234 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DnfTypes\Models;
use ArrayAccess;
use Countable;
use Illuminate\Database\Eloquent\Model;
use Iterator;
use Stringable;
use Traversable;
/**
* Test model covering all DNF type scenarios from PHP RFC.
*
* @see https://wiki.php.net/rfc/dnf_types
* @property-read (\Countable&\Iterator)|null $basic_dnf
* @property-read (\Countable&\Iterator)|\stdClass $intersection_or_class
* @property-read (\Countable&\Iterator)|(\ArrayAccess&\Stringable)|null $multiple_intersections
* @property-read (\Countable&\Iterator)|(\ArrayAccess&\Stringable)|(\Traversable&\Countable) $multiple_intersections_no_null
* @property-read (\Countable&\Iterator) $pure_intersection
* @property-read \stdClass|(\Countable&\Iterator)|null $single_or_intersection_or_null
* @property-read (\Countable&\Iterator&\Traversable)|int|null $triple_intersection
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel withBasicDnfParam((\Countable&\Iterator)|null $param)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel withComplexDnfParam(\stdClass|(\Countable&\Iterator)|null $param)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel withDnfParamAndReturn((\Countable&\Iterator)|null $param)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel withIntersectionOrPrimitive((\Countable&\Iterator)|int $param)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DnfTypeModel withMultipleIntersectionParam((\Countable&\Iterator)|(\ArrayAccess&\Stringable) $param)
* @mixin \Eloquent
*/
class DnfTypeModel extends Model
{
// =========================================================================
// RFC Example 1: (A&B)|null - Intersection with null
// =========================================================================
/**
* Basic DNF: intersection OR null.
*/
public function getBasicDnfAttribute(): (Countable&Iterator)|null
{
return null;
}
// =========================================================================
// RFC Example 2: (A&B)|D - Intersection OR single type (no null)
// =========================================================================
/**
* Intersection OR single class type.
*/
public function getIntersectionOrClassAttribute(): (Countable&Iterator)|\stdClass
{
return new \stdClass();
}
// =========================================================================
// RFC Example 3: C|(X&D)|null - Single type OR intersection OR null
// =========================================================================
/**
* Single type OR intersection OR null.
*/
public function getSingleOrIntersectionOrNullAttribute(): \stdClass|(Countable&Iterator)|null
{
return null;
}
// =========================================================================
// RFC Example 4: (A&B&D)|int|null - Triple intersection OR primitive OR null
// =========================================================================
/**
* Triple intersection OR primitive OR null.
*/
public function getTripleIntersectionAttribute(): (Countable&Iterator&Traversable)|int|null
{
return null;
}
// =========================================================================
// RFC Example 5: (A&B)|(C&D)|null - Multiple intersection segments
// =========================================================================
/**
* Multiple intersection segments OR null.
*/
public function getMultipleIntersectionsAttribute(): (Countable&Iterator)|(ArrayAccess&Stringable)|null
{
return null;
}
// =========================================================================
// RFC Example 6: (A&B)|(C&D)|(E&F) - Multiple intersections without null
// =========================================================================
/**
* Multiple intersection segments without null.
*/
public function getMultipleIntersectionsNoNullAttribute(): (Countable&Iterator)|(ArrayAccess&Stringable)|(Traversable&Countable)
{
return new class () implements Countable, Iterator {
public function count(): int
{
return 0;
}
public function current(): mixed
{
return null;
}
public function key(): mixed
{
return null;
}
public function next(): void
{
}
public function rewind(): void
{
}
public function valid(): bool
{
return false;
}
};
}
// =========================================================================
// Pure intersection type (A&B) - No union, just intersection
// =========================================================================
/**
* Pure intersection type without union.
*/
public function getPureIntersectionAttribute(): Countable&Iterator
{
return new class () implements Countable, Iterator {
public function count(): int
{
return 0;
}
public function current(): mixed
{
return null;
}
public function key(): mixed
{
return null;
}
public function next(): void
{
}
public function rewind(): void
{
}
public function valid(): bool
{
return false;
}
};
}
// =========================================================================
// Parameter context: DNF types in method parameters
// =========================================================================
/**
* Scope with DNF parameter: (A&B)|null.
*/
public function scopeWithBasicDnfParam(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|null $param
): \Illuminate\Database\Query\Builder {
return $query;
}
/**
* Scope with complex DNF parameter: C|(A&B)|null.
*/
public function scopeWithComplexDnfParam(
\Illuminate\Database\Query\Builder $query,
\stdClass|(Countable&Iterator)|null $param
): \Illuminate\Database\Query\Builder {
return $query;
}
/**
* Scope with multiple intersection parameters.
*/
public function scopeWithMultipleIntersectionParam(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|(ArrayAccess&Stringable) $param
): \Illuminate\Database\Query\Builder {
return $query;
}
/**
* Scope with intersection OR primitive parameter.
*/
public function scopeWithIntersectionOrPrimitive(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|int $param
): \Illuminate\Database\Query\Builder {
return $query;
}
// =========================================================================
// Mixed: DNF in both parameter and return type
// =========================================================================
/**
* DNF in both parameter and return type.
*/
public function scopeWithDnfParamAndReturn(
\Illuminate\Database\Query\Builder $query,
(Countable&Iterator)|null $param
): (Countable&Iterator)|\Illuminate\Database\Query\Builder {
return $query;
}
}
@@ -48,8 +48,8 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DoesNotGenerateP
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -44,8 +44,8 @@ use Illuminate\Database\Eloquent\Model;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -42,8 +42,8 @@ use Illuminate\Database\Eloquent\Model;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -42,8 +42,8 @@ use Illuminate\Database\Eloquent\Model;
* @property float $floatNotNullable * @property float $floatNotNullable
* @property float|null $doubleNullable * @property float|null $doubleNullable
* @property float $doubleNotNullable * @property float $doubleNotNullable
* @property string|null $decimalNullable * @property numeric|null $decimalNullable
* @property string $decimalNotNullable * @property numeric $decimalNotNullable
* @property int|null $booleanNullable * @property int|null $booleanNullable
* @property int $booleanNotNullable * @property int $booleanNotNullable
* @property string|null $enumNullable * @property string|null $enumNullable
@@ -42,8 +42,8 @@ use Illuminate\Database\Eloquent\Model;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -48,8 +48,8 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\GeneratePhpdocWi
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -43,8 +43,8 @@ use Illuminate\Database\Eloquent\Model;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -44,8 +44,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -50,8 +50,8 @@ use Illuminate\Support\Carbon;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -48,8 +48,8 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\GeneratePhpdocWi
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -95,8 +95,8 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\GeneratePhpdocWi
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -42,8 +42,8 @@ use Illuminate\Database\Eloquent\Model;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -81,8 +81,8 @@ namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\QueryScopes\Mode
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -43,8 +43,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
* @property float $float_not_nullable * @property float $float_not_nullable
* @property float|null $double_nullable * @property float|null $double_nullable
* @property float $double_not_nullable * @property float $double_not_nullable
* @property string|null $decimal_nullable * @property numeric|null $decimal_nullable
* @property string $decimal_not_nullable * @property numeric $decimal_not_nullable
* @property int|null $boolean_nullable * @property int|null $boolean_nullable
* @property int $boolean_not_nullable * @property int $boolean_not_nullable
* @property string|null $enum_nullable * @property string|null $enum_nullable
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
class ModelWithRelations extends Model
{
protected $table = 'models_with_relations';
public function softDeletable(): BelongsTo
{
return $this->belongsTo(SoftDeletableModel::class, 'soft_deletable_model_id');
}
public function nonSoftDeletable(): BelongsTo
{
return $this->belongsTo(NonSoftDeletableModel::class, 'non_soft_deletable_model_id');
}
public function softDeletableHasOne(): HasOne
{
return $this->hasOne(SoftDeletableModel::class, 'model_with_relations_id');
}
public function nonSoftDeletableHasOne(): HasOne
{
return $this->hasOne(NonSoftDeletableModel::class, 'model_with_relations_id');
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
class NonSoftDeletableModel extends Model
{
}
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class SoftDeletableModel extends Model
{
use SoftDeletes;
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
use Illuminate\Support\Facades\Config;
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();
}
public function testSoftDeletesForceNullableDisabled(): void
{
Config::set('ide-helper.soft_deletes_force_nullable', false);
$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();
Config::set('ide-helper.soft_deletes_force_nullable', true);
}
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* @property int $id
* @property int $soft_deletable_model_id
* @property int $non_soft_deletable_model_id
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\NonSoftDeletableModel $nonSoftDeletable
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\NonSoftDeletableModel|null $nonSoftDeletableHasOne
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\SoftDeletableModel $softDeletable
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\SoftDeletableModel|null $softDeletableHasOne
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations whereNonSoftDeletableModelId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations whereSoftDeletableModelId($value)
* @mixin \Eloquent
*/
class ModelWithRelations extends Model
{
protected $table = 'models_with_relations';
public function softDeletable(): BelongsTo
{
return $this->belongsTo(SoftDeletableModel::class, 'soft_deletable_model_id');
}
public function nonSoftDeletable(): BelongsTo
{
return $this->belongsTo(NonSoftDeletableModel::class, 'non_soft_deletable_model_id');
}
public function softDeletableHasOne(): HasOne
{
return $this->hasOne(SoftDeletableModel::class, 'model_with_relations_id');
}
public function nonSoftDeletableHasOne(): HasOne
{
return $this->hasOne(NonSoftDeletableModel::class, 'model_with_relations_id');
}
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int|null $model_with_relations_id
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel whereModelWithRelationsId($value)
* @mixin \Eloquent
*/
class NonSoftDeletableModel extends Model
{
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* @property int $id
* @property int|null $model_with_relations_id
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel whereModelWithRelationsId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel withoutTrashed()
* @mixin \Eloquent
*/
class SoftDeletableModel extends Model
{
use SoftDeletes;
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* @property int $id
* @property int $soft_deletable_model_id
* @property int $non_soft_deletable_model_id
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\NonSoftDeletableModel $nonSoftDeletable
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\NonSoftDeletableModel|null $nonSoftDeletableHasOne
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\SoftDeletableModel|null $softDeletable
* @property-read \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models\SoftDeletableModel|null $softDeletableHasOne
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations whereNonSoftDeletableModelId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ModelWithRelations whereSoftDeletableModelId($value)
* @mixin \Eloquent
*/
class ModelWithRelations extends Model
{
protected $table = 'models_with_relations';
public function softDeletable(): BelongsTo
{
return $this->belongsTo(SoftDeletableModel::class, 'soft_deletable_model_id');
}
public function nonSoftDeletable(): BelongsTo
{
return $this->belongsTo(NonSoftDeletableModel::class, 'non_soft_deletable_model_id');
}
public function softDeletableHasOne(): HasOne
{
return $this->hasOne(SoftDeletableModel::class, 'model_with_relations_id');
}
public function nonSoftDeletableHasOne(): HasOne
{
return $this->hasOne(NonSoftDeletableModel::class, 'model_with_relations_id');
}
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int|null $model_with_relations_id
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|NonSoftDeletableModel whereModelWithRelationsId($value)
* @mixin \Eloquent
*/
class NonSoftDeletableModel extends Model
{
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SoftDeletesRelations\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* @property int $id
* @property int|null $model_with_relations_id
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel whereModelWithRelationsId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SoftDeletableModel withoutTrashed()
* @mixin \Eloquent
*/
class SoftDeletableModel extends Model
{
use SoftDeletes;
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SoftDeletesRelationsTable extends Migration
{
public function up(): void
{
Schema::create('soft_deletable_models', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('model_with_relations_id')->nullable();
$table->softDeletes();
});
Schema::create('non_soft_deletable_models', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('model_with_relations_id')->nullable();
});
Schema::create('models_with_relations', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('soft_deletable_model_id');
$table->unsignedBigInteger('non_soft_deletable_model_id');
$table->foreign('soft_deletable_model_id')
->references('id')
->on('soft_deletable_models');
$table->foreign('non_soft_deletable_model_id')
->references('id')
->on('non_soft_deletable_models');
});
}
}
-18
View File
@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Factories;
use Barryvdh\LaravelIdeHelper\Factories;
use PHPUnit\Framework\TestCase;
class AllTest extends TestCase
{
public function testAll(): void
{
$factories = Factories::all();
self::assertEmpty($factories);
}
}
+60 -3
View File
@@ -207,6 +207,63 @@ class MacroTest extends TestCase
$this->assertEquals('@return \Stringable|string|null', $this->tagsToString($phpdoc, 'return')); $this->assertEquals('@return \Stringable|string|null', $this->tagsToString($phpdoc, 'return'));
} }
public function testInitPhpDocParamsWithDnfTypes(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP'
return new ReflectionFunction(
/**
* Test docblock with DNF types.
*/
function ((\Countable&\Iterator)|null $a): (\Countable&\Iterator)|\stdClass|null {
return $a;
}
);
PHP));
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock with DNF types', $phpdoc->getText());
$this->assertEquals('@param (Countable&Iterator)|null $a', $this->tagsToString($phpdoc, 'param'));
$this->assertEquals('@return (Countable&Iterator)|\stdClass|null', $this->tagsToString($phpdoc, 'return'));
}
public function testInitPhpDocParamsWithPureIntersectionType(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP'
return new ReflectionFunction(
/**
* Test docblock with pure intersection type.
*/
function (\Countable&\Iterator $a): \Countable&\Iterator {
return $a;
}
);
PHP));
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock with pure intersection type', $phpdoc->getText());
$this->assertEquals('@param (Countable&Iterator) $a', $this->tagsToString($phpdoc, 'param'));
$this->assertEquals('@return (Countable&Iterator)', $this->tagsToString($phpdoc, 'return'));
}
public function testInitPhpDocParamsWithMultipleIntersections(): void
{
$phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP'
return new ReflectionFunction(
/**
* Test docblock with multiple intersection segments.
*/
function ((\Countable&\Iterator)|(\ArrayAccess&\Stringable) $a): (\Countable&\Iterator)|(\ArrayAccess&\Stringable)|null {
return $a;
}
);
PHP));
$this->assertNotNull($phpdoc);
$this->assertStringContainsString('Test docblock with multiple intersection segments', $phpdoc->getText());
$this->assertEquals('@param (Countable&Iterator)|(ArrayAccess&Stringable) $a', $this->tagsToString($phpdoc, 'param'));
$this->assertEquals('@return (Countable&Iterator)|(ArrayAccess&Stringable)|null', $this->tagsToString($phpdoc, 'return'));
}
protected function tagsToString(DocBlock $docBlock, string $name) protected function tagsToString(DocBlock $docBlock, string $name)
{ {
$tags = $docBlock->getTagsByName($name); $tags = $docBlock->getTagsByName($name);
@@ -227,7 +284,7 @@ class MacroTest extends TestCase
{ {
$reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke'); $reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke');
$macro = new Macro($reflectionMethod, UrlGenerator::class, new ReflectionClass(UrlGenerator::class), 'macroName'); $macro = new Macro($reflectionMethod, new ReflectionClass(UrlGenerator::class), 'macroName');
$this->assertInstanceOf(Macro::class, $macro); $this->assertInstanceOf(Macro::class, $macro);
} }
@@ -239,7 +296,7 @@ class MacroTest extends TestCase
{ {
$reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke'); $reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke');
$macro = new Macro($reflectionMethod, 'URL', new ReflectionClass(UrlGenerator::class), 'macroName'); $macro = new Macro($reflectionMethod, new ReflectionClass(UrlGenerator::class), 'macroName');
$output = <<<'DOC' $output = <<<'DOC'
/** /**
* @param string $foo * @param string $foo
@@ -274,7 +331,7 @@ class MacroMock extends Macro
public function getPhpDoc(ReflectionFunctionAbstract $method, ?ReflectionClass $class = null): DocBlock public function getPhpDoc(ReflectionFunctionAbstract $method, ?ReflectionClass $class = null): DocBlock
{ {
return (new Macro($method, '', $class ?? $method->getClosureScopeClass()))->phpdoc; return (new Macro($method, $class ?? $method->getClosureScopeClass()))->phpdoc;
} }
} }
+8 -8
View File
@@ -19,7 +19,7 @@ class MethodTest extends TestCase
$reflectionClass = new \ReflectionClass(ExampleClass::class); $reflectionClass = new \ReflectionClass(ExampleClass::class);
$reflectionMethod = $reflectionClass->getMethod('setName'); $reflectionMethod = $reflectionClass->getMethod('setName');
$method = new Method($reflectionMethod, 'Example', $reflectionClass); $method = new Method($reflectionMethod, $reflectionClass);
$this->assertInstanceOf(Method::class, $method); $this->assertInstanceOf(Method::class, $method);
} }
@@ -32,7 +32,7 @@ class MethodTest extends TestCase
$reflectionClass = new \ReflectionClass(ExampleClass::class); $reflectionClass = new \ReflectionClass(ExampleClass::class);
$reflectionMethod = $reflectionClass->getMethod('setName'); $reflectionMethod = $reflectionClass->getMethod('setName');
$method = new Method($reflectionMethod, 'Example', $reflectionClass); $method = new Method($reflectionMethod, $reflectionClass);
$output = <<<'DOC' $output = <<<'DOC'
/** /**
@@ -60,7 +60,7 @@ DOC;
$reflectionClass = new \ReflectionClass(EloquentBuilder::class); $reflectionClass = new \ReflectionClass(EloquentBuilder::class);
$reflectionMethod = $reflectionClass->getMethod('upsert'); $reflectionMethod = $reflectionClass->getMethod('upsert');
$method = new Method($reflectionMethod, 'Builder', $reflectionClass); $method = new Method($reflectionMethod, $reflectionClass);
$output = <<<'DOC' $output = <<<'DOC'
/** /**
@@ -92,7 +92,7 @@ DOC;
$reflectionClass = new \ReflectionClass(EloquentBuilder::class); $reflectionClass = new \ReflectionClass(EloquentBuilder::class);
$reflectionMethod = $reflectionClass->getMethod('where'); $reflectionMethod = $reflectionClass->getMethod('where');
$method = new Method($reflectionMethod, 'Builder', $reflectionClass, null, [], [], ['$this' => '\\' . EloquentBuilder::class . '<static>']); $method = new Method($reflectionMethod, $reflectionClass, null, [], [], ['$this' => '\\' . EloquentBuilder::class . '<static>']);
$output = <<<'DOC' $output = <<<'DOC'
/** /**
@@ -123,7 +123,7 @@ DOC;
$reflectionClass = new \ReflectionClass(QueryBuilder::class); $reflectionClass = new \ReflectionClass(QueryBuilder::class);
$reflectionMethod = $reflectionClass->getMethod('whereNull'); $reflectionMethod = $reflectionClass->getMethod('whereNull');
$method = new Method($reflectionMethod, 'Builder', $reflectionClass, null, [], [], ['$this' => '\\' . EloquentBuilder::class . '<static>']); $method = new Method($reflectionMethod, $reflectionClass, null, [], [], ['$this' => '\\' . EloquentBuilder::class . '<static>']);
$output = <<<'DOC' $output = <<<'DOC'
/** /**
@@ -154,7 +154,7 @@ DOC;
$reflectionClass = new \ReflectionClass(ExampleClass::class); $reflectionClass = new \ReflectionClass(ExampleClass::class);
$reflectionMethod = $reflectionClass->getMethod('setSpecialChars'); $reflectionMethod = $reflectionClass->getMethod('setSpecialChars');
$method = new Method($reflectionMethod, 'Example', $reflectionClass); $method = new Method($reflectionMethod, $reflectionClass);
$this->assertSame('$chars', $method->getParams(true)); $this->assertSame('$chars', $method->getParams(true));
$this->assertSame(['$chars'], $method->getParams(false)); $this->assertSame(['$chars'], $method->getParams(false));
$this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true)); $this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true));
@@ -169,7 +169,7 @@ DOC;
$reflectionClass = new \ReflectionClass(ExampleClass::class); $reflectionClass = new \ReflectionClass(ExampleClass::class);
$reflectionMethod = $reflectionClass->getMethod('getApplication'); $reflectionMethod = $reflectionClass->getMethod('getApplication');
$method = new Method($reflectionMethod, 'Example', $reflectionClass, null, [], [ $method = new Method($reflectionMethod, $reflectionClass, null, [], [
'Application' => '\\Illuminate\\Foundation\\Application', 'Application' => '\\Illuminate\\Foundation\\Application',
]); ]);
@@ -195,7 +195,7 @@ DOC;
$reflectionClass = new \ReflectionClass(EloquentBuilder::class); $reflectionClass = new \ReflectionClass(EloquentBuilder::class);
$reflectionMethod = $reflectionClass->getMethod('firstOr'); $reflectionMethod = $reflectionClass->getMethod('firstOr');
$method = new Method($reflectionMethod, 'Builder', $reflectionClass, null, [], [], [], ['TModel']); $method = new Method($reflectionMethod, $reflectionClass, null, [], [], [], ['TModel']);
$output = <<<'DOC' $output = <<<'DOC'
/** /**