Compare commits

..
36 Commits
Author SHA1 Message Date
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
33 changed files with 1373 additions and 80 deletions
+2 -4
View File
@@ -2,12 +2,10 @@
// Share common rules between non-test and test files
return [
// PSR12 from https://github.com/FriendsOfPHP/PHP-CS-Fixer/pull/4943
'@PSR2' => true,
'@PSR12' => true,
'blank_line_after_opening_tag' => true,
'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,
'declare_equal_normalize' => true,
+34 -3
View File
@@ -2,11 +2,42 @@
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.9.3...master)
--------------
### Changed
- Dropped support for Laravel 6 and Laravel 7, as well as support for PHP 7.2 [\# / mfn]()
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)
### 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
- 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)
+71 -1
View File
@@ -14,6 +14,9 @@ Generation is done based on the files in your project, so they are always up-to-
- [Usage](#usage)
- [Automatic PHPDoc generation for Laravel Facades](#automatic-phpdoc-generation-for-laravel-facades)
- [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)
- [Auto-completion for factory builders](#auto-completion-for-factory-builders)
- [PhpStorm Meta for Container instances](#phpstorm-meta-for-container-instances)
@@ -175,6 +178,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).
```bash
@@ -189,6 +194,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.
#### Ignore Models
Models can be ignored using the `--ignore (-I)` option
```bash
@@ -206,7 +213,7 @@ Or can be ignored by setting the `ignored_models` config
#### 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`.
@@ -216,6 +223,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`.
#### 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
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 +277,42 @@ 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');
}
}
```
```php
/**
* MyModel
*
* @property integer $id
* @property-read string $custom
```
### Automatic PHPDocs generation for Laravel Fluent methods
If you need PHPDocs support for Fluent methods in migration, for example
+4
View File
@@ -28,6 +28,7 @@
"illuminate/console": "^8",
"illuminate/filesystem": "^8",
"illuminate/support": "^8",
"nikic/php-parser": "^4.7",
"phpdocumentor/type-resolver": "^1.1.0"
},
"require-dev": {
@@ -41,6 +42,9 @@
"spatie/phpunit-snapshot-assertions": "^3 || ^4",
"vimeo/psalm": "^3.12"
},
"suggest": {
"illuminate/events": "Required for automatic helper generation (^6|^7|^8)."
},
"config": {
"sort-packages": true
},
+27
View File
@@ -144,6 +144,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
@@ -278,4 +293,16 @@ return [
*/
'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>
</UndefinedClass>
</file>
<file src="src/IdeHelperServiceProvider.php">
<TooFewArguments occurrences="1">
<code>new PhpEngine()</code>
</TooFewArguments>
</file>
</files>
+10 -7
View File
@@ -40,6 +40,7 @@ class Alias
protected $magicMethods = [];
protected $interfaces = [];
protected $phpdoc = null;
protected $classAliases = [];
/** @var ConfigRepository */
protected $config;
@@ -78,10 +79,11 @@ class Alias
$this->detectExtendsNamespace();
if (!empty($this->namespace)) {
//Create a DocBlock and serializer instance
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace));
}
$this->classAliases = (new UsesResolver())->loadFromClass($this->root);
//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') {
$this->usedMethods = ['decrement', 'increment'];
@@ -330,7 +332,7 @@ class Alias
if (!in_array($magic, $this->usedMethods)) {
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;
}
@@ -344,7 +346,6 @@ class Alias
*/
protected function detectMethods()
{
foreach ($this->classes as $class) {
$reflection = new \ReflectionClass($class);
@@ -360,7 +361,8 @@ class Alias
$this->alias,
$reflection,
$method->name,
$this->interfaces
$this->interfaces,
$this->classAliases
);
}
$this->usedMethods[] = $method->name;
@@ -382,7 +384,8 @@ class Alias
$this->alias,
$reflection,
$macro_name,
$this->interfaces
$this->interfaces,
$this->classAliases
);
$this->usedMethods[] = $macro_name;
}
+155 -32
View File
@@ -11,6 +11,7 @@
namespace Barryvdh\LaravelIdeHelper\Console;
use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface;
use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Context;
use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
@@ -18,6 +19,7 @@ use Barryvdh\Reflection\DocBlock\Tag;
use Composer\Autoload\ClassMapGenerator;
use Illuminate\Console\Command;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -34,7 +36,9 @@ use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Str;
use phpDocumentor\Reflection\Types\ContextFactory;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionObject;
use ReflectionType;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -209,8 +213,6 @@ class ModelsCommand extends Command
protected function generateDocs($loadModels, $ignore = '')
{
$output = "<?php
// @formatter:off
@@ -277,6 +279,10 @@ class ModelsCommand extends Command
$this->getPropertiesFromMethods($model);
$this->getSoftDeleteMethods($model);
$this->getCollectionMethods($model);
$this->getFactoryMethods($model);
$this->runModelHooks($model);
$output .= $this->createPhpDocs($name);
$ignore[] = $name;
$this->nullableColumns = [];
@@ -309,9 +315,8 @@ class ModelsCommand extends Command
$dirs = glob($dir, GLOB_ONLYDIR);
foreach ($dirs as $dir) {
if (!is_dir($dir)) {
$this->error("Cannot locate directory '{'$dir}'");
$this->error("Cannot locate directory '{$dir}'");
continue;
}
@@ -335,7 +340,7 @@ class ModelsCommand extends Command
*
* @param \Illuminate\Database\Eloquent\Model $model
*/
protected function castPropertiesType($model)
public function castPropertiesType($model)
{
$casts = $model->getCasts();
foreach ($casts as $name => $type) {
@@ -411,7 +416,7 @@ class ModelsCommand extends Command
*
* @param \Illuminate\Database\Eloquent\Model $model
*/
protected function getPropertiesFromTable($model)
public function getPropertiesFromTable($model)
{
$table = $model->getConnection()->getTablePrefix() . $model->getTable();
$schema = $model->getConnection()->getDoctrineSchemaManager();
@@ -508,7 +513,7 @@ class ModelsCommand extends Command
/**
* @param \Illuminate\Database\Eloquent\Model $model
*/
protected function getPropertiesFromMethods($model)
public function getPropertiesFromMethods($model)
{
$methods = get_class_methods($model);
if ($methods) {
@@ -526,7 +531,8 @@ class ModelsCommand extends Command
$reflection = new \ReflectionMethod($model, $method);
$type = $this->getReturnType($reflection);
$type = $this->getTypeInModel($model, $type);
$this->setProperty($name, $type, true, null);
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, $type, true, null, $comment);
}
} elseif (
Str::startsWith($method, 'set') && Str::endsWith(
@@ -537,13 +543,16 @@ class ModelsCommand extends Command
//Magic set<name>Attribute
$name = Str::snake(substr($method, 3, -9));
if (!empty($name)) {
$this->setProperty($name, null, null, true);
$reflection = new \ReflectionMethod($model, $method);
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, null, null, true, $comment);
}
} elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery') {
//Magic set<name>Attribute
$name = Str::camel(substr($method, 5));
if (!empty($name)) {
$reflection = new \ReflectionMethod($model, $method);
$comment = $this->getCommentFromDocBlock($reflection);
$args = $this->getParameters($reflection);
//Remove the first ($query) argument
array_shift($args);
@@ -555,7 +564,7 @@ class ModelsCommand extends Command
$reflection->getDeclaringClass(),
$reflection->getDeclaringClass()->getName()
);
$this->setMethod($name, $builder . '|' . $modelName, $args);
$this->setMethod($name, $builder . '|' . $modelName, $args, $comment);
}
} elseif (in_array($method, ['query', 'newQuery', 'newModelQuery'])) {
$builder = $this->getClassNameInDestinationFile($model, get_class($model->newModelQuery()));
@@ -576,7 +585,7 @@ class ModelsCommand extends Command
$reflection = new \ReflectionMethod($model, $method);
if ($returnType = $reflection->getReturnType()) {
$type = $returnType instanceof \ReflectionNamedType
$type = $returnType instanceof ReflectionNamedType
? $returnType->getName()
: (string)$returnType;
} else {
@@ -607,6 +616,7 @@ class ModelsCommand extends Command
continue;
}
$comment = $this->getCommentFromDocBlock($reflection);
// Adding constraints requires reading model properties which
// can cause errors. Since we don't need constraints we can
// disable them when we fetch the relation to avoid errors.
@@ -638,7 +648,8 @@ class ModelsCommand extends Command
$method,
$collectionClassNameInModel . '|' . $relatedModel . '[]',
true,
null
null,
$comment
);
if ($this->write_model_relation_count_properties) {
$this->setProperty(
@@ -646,6 +657,7 @@ class ModelsCommand extends Command
'int|null',
true,
false
// What kind of comments should be added to the relation count here?
);
}
} elseif ($relation === 'morphTo') {
@@ -654,7 +666,8 @@ class ModelsCommand extends Command
$method,
$this->getClassNameInDestinationFile($model, Model::class) . '|\Eloquent',
true,
null
null,
$comment
);
} else {
//Single model is returned
@@ -663,7 +676,7 @@ class ModelsCommand extends Command
$relatedModel,
true,
null,
'',
$comment,
$this->isRelationNullable($relation, $relationObj)
);
}
@@ -712,7 +725,7 @@ class ModelsCommand extends Command
* @param string|null $comment
* @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])) {
$this->properties[$name] = [];
@@ -736,7 +749,7 @@ class ModelsCommand extends Command
}
}
protected function setMethod($name, $type = '', $arguments = [])
protected function setMethod($name, $type = '', $arguments = [], $comment = '')
{
$methods = array_change_key_case($this->methods, CASE_LOWER);
@@ -744,6 +757,7 @@ class ModelsCommand extends Command
$this->methods[$name] = [];
$this->methods[$name]['type'] = $type;
$this->methods[$name]['arguments'] = $arguments;
$this->methods[$name]['comment'] = $comment;
}
}
@@ -753,7 +767,6 @@ class ModelsCommand extends Command
*/
protected function createPhpDocs($class)
{
$reflection = new ReflectionClass($class);
$namespace = $reflection->getNamespaceName();
$classname = $reflection->getShortName();
@@ -820,7 +833,11 @@ class ModelsCommand extends Command
continue;
}
$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);
}
@@ -980,6 +997,30 @@ class ModelsCommand extends Command
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
*
@@ -1011,16 +1052,12 @@ class ModelsCommand extends Command
return null;
}
$type = $returnType instanceof \ReflectionNamedType
? $returnType->getName()
: (string)$returnType;
$types = $this->extractReflectionTypes($returnType);
if (!$returnType->isBuiltin()) {
$type = '\\' . $type;
}
$type = implode('|', $types);
if ($returnType->allowsNull()) {
$type .= '|null';
$type .='|null';
}
return $type;
@@ -1043,6 +1080,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;
}
$traits = class_uses(get_class($model), true);
if (!in_array('Illuminate\\Database\\Eloquent\\Factories\\HasFactory', $traits)) {
return;
}
$modelName = get_class($model);
$modelBaseName = class_basename($modelName);
$factory = "\Database\Factories\\{$modelBaseName}Factory";
if ($modelName::newFactory()) {
$factory = get_class($modelName::newFactory());
}
$factory = '\\' . trim($factory, '\\');
if (!class_exists($factory)) {
return;
}
$this->setMethod('factory', $factory, ['...$parameters']);
}
/**
* Generates methods that return collections
* @param \Illuminate\Database\Eloquent\Model $model
@@ -1101,7 +1172,7 @@ class ModelsCommand extends Command
$reflectionType = $this->getReturnTypeFromDocBlock($methodReflection);
}
if($reflectionType === 'static' || $reflectionType === '$this') {
if ($reflectionType === 'static' || $reflectionType === '$this') {
$reflectionType = $type;
}
@@ -1186,17 +1257,19 @@ class ModelsCommand extends Command
protected function getParamType(\ReflectionMethod $method, \ReflectionParameter $parameter): ?string
{
if ($paramType = $parameter->getType()) {
$parameterName = $paramType->getName();
$types = $this->extractReflectionTypes($paramType);
if (!$paramType->isBuiltin()) {
$parameterName = '\\' . $parameterName;
}
$type = implode('|', $types);
if ($paramType->allowsNull()) {
return '?' . $parameterName;
if (count($types)==1) {
$type = '?' . $type;
} else {
$type .='|null';
}
}
return $parameterName;
return $type;
}
$docComment = $method->getDocComment();
@@ -1261,4 +1334,54 @@ class ModelsCommand extends Command
// then we have found the type of the variable if not we return null
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);
}
}
}
+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;
}
+3 -3
View File
@@ -54,9 +54,9 @@ class Generator
// Find the drivers to add to the extra/interfaces
$this->detectDrivers();
$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->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces'));
$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->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces'), []);
// Make all interface classes absolute
foreach ($this->interfaces as &$interface) {
$interface = '\\' . ltrim($interface, '\\');
-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\MetaCommand;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Listeners\GenerateModelHelper;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Foundation\Application;
use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Engines\PhpEngine;
@@ -32,6 +34,13 @@ class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProv
*/
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')) {
$viewPath = __DIR__ . '/../resources/views';
$this->loadViewsFrom($viewPath, 'ide-helper');
@@ -110,10 +119,6 @@ class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProv
{
$resolver = new EngineResolver();
$resolver->register('php', function () {
if (Helpers::isLaravel() && (int) Application::VERSION < 8) {
return new PhpEngine();
}
return new PhpEngine($this->app['files']);
});
$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);
}
}
}
+9 -3
View File
@@ -17,15 +17,17 @@ class Macro extends Method
* @param \ReflectionClass $class
* @param null $methodName
* @param array $interfaces
* @param array $classAliases
*/
public function __construct(
$method,
$alias,
$class,
$methodName = null,
$interfaces = []
$interfaces = [],
$classAliases = []
) {
parent::__construct($method, $alias, $class, $methodName, $interfaces);
parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases);
}
/**
@@ -65,7 +67,11 @@ class Macro extends Method
protected function addLocationToPhpDoc()
{
$enclosingClass = $this->method->getClosureScopeClass();
if ($this->method->name === '__invoke') {
$enclosingClass = $this->method->getDeclaringClass();
} else {
$enclosingClass = $this->method->getClosureScopeClass();
}
/** @var \ReflectionMethod $enclosingMethod */
$enclosingMethod = Collection::make($enclosingClass->getMethods())
+7 -4
View File
@@ -38,6 +38,7 @@ class Method
protected $real_name;
protected $return = null;
protected $root;
protected $classAliases;
/**
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method
@@ -45,17 +46,19 @@ class Method
* @param \ReflectionClass $class
* @param string|null $methodName
* @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->interfaces = $interfaces;
$this->classAliases = $classAliases;
$this->name = $methodName ?: $method->name;
$this->real_name = $method->isClosure() ? $this->name : $method->name;
$this->initClassDefinedProperties($method, $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
$this->initPhpDoc($method);
@@ -80,7 +83,7 @@ class 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) {
$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) {
//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 <[email protected]>
* @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);
}
}
@@ -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
{
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Getter\Models\Simple;
use Illuminate\Database\Eloquent\Factories\Factory;
class SimpleFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Simple::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}
@@ -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,24 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\SimpleFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Simple extends Model
{
use HasFactory;
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return SimpleFactory::new();
}
}
@@ -0,0 +1,32 @@
<?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 Illuminate\Foundation\Application;
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'
);
}
$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();
}
}
@@ -0,0 +1,56 @@
<?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;
}
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\SimpleFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Models\Simple
*
* @property integer $id
* @method static \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Factories\Factories\SimpleFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
* @mixin \Eloquent
*/
class Simple extends Model
{
use HasFactory;
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return SimpleFactory::new();
}
}
@@ -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,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,88 @@
<?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\CustomProperty;
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,
],
]);
}
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 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
{
}
PHP;
$this->assertSame($expectedContent, $actualContent);
}
}
@@ -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');
}
}
+58
View File
@@ -8,6 +8,7 @@ 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;
@@ -200,6 +201,47 @@ class MacroTest extends TestCase
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());
}
}
/**
@@ -218,3 +260,19 @@ class MacroMock extends Macro
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
View File
@@ -97,6 +97,37 @@ DOC;
$this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true));
$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
@@ -115,4 +146,12 @@ class ExampleClass
{
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',
]
);
}
}