├── .php-cs-fixer.common.php ├── .php-cs-fixer.dist.php ├── .php-cs-fixer.tests.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── ide-helper.php ├── php-templates ├── LICENSE.md ├── README.md ├── auth.php ├── configs.php ├── middleware.php ├── routes.php ├── translations.php └── views.php ├── resources └── views │ ├── helper.php │ └── meta.php └── src ├── Alias.php ├── Console ├── EloquentCommand.php ├── GeneratorCommand.php ├── MetaCommand.php └── ModelsCommand.php ├── Contracts └── ModelHookInterface.php ├── Eloquent.php ├── Factories.php ├── Generator.php ├── IdeHelperServiceProvider.php ├── Listeners └── GenerateModelHelper.php ├── Macro.php ├── Method.php └── Parsers └── PhpDocReturnTypeParser.php /.php-cs-fixer.common.php: -------------------------------------------------------------------------------- 1 | true, 6 | 'blank_line_after_opening_tag' => true, 7 | 'braces' => [ 8 | 'allow_single_line_anonymous_class_with_empty_body' => true, 9 | ], 10 | 'compact_nullable_typehint' => true, 11 | 'declare_equal_normalize' => true, 12 | 'lowercase_cast' => true, 13 | 'lowercase_static_reference' => true, 14 | 'new_with_braces' => true, 15 | 'no_blank_lines_after_class_opening' => true, 16 | 'no_leading_import_slash' => true, 17 | 'no_whitespace_in_blank_line' => true, 18 | 'ordered_class_elements' => [ 19 | 'order' => [ 20 | 'use_trait', 21 | ], 22 | ], 23 | 'ordered_imports' => [ 24 | 'imports_order' => [ 25 | 'class', 26 | 'function', 27 | 'const', 28 | ], 29 | 'sort_algorithm' => 'alpha', 30 | ], 31 | 'return_type_declaration' => true, 32 | 'short_scalar_cast' => true, 33 | 'single_trait_insert_per_statement' => true, 34 | 'ternary_operator_spaces' => true, 35 | 'visibility_required' => [ 36 | 'elements' => [ 37 | 'const', 38 | 'method', 39 | 'property', 40 | ], 41 | ], 42 | 43 | // Further quality-of-life improvements 44 | 'array_syntax' => [ 45 | 'syntax' => 'short', 46 | ], 47 | 'concat_space' => [ 48 | 'spacing' => 'one', 49 | ], 50 | 'fully_qualified_strict_types' => true, 51 | 'native_function_invocation' => [ 52 | 'include' => [], 53 | 'strict' => true, 54 | ], 55 | 'no_unused_imports' => true, 56 | 'single_quote' => true, 57 | 'space_after_semicolon' => true, 58 | 'trailing_comma_in_multiline' => true, 59 | 'trim_array_spaces' => true, 60 | 'unary_operator_spaces' => true, 61 | 'whitespace_after_comma_in_array' => true, 62 | ]; 63 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 6 | ->exclude('tests'); 7 | 8 | $config = require __DIR__ . '/.php-cs-fixer.common.php'; 9 | 10 | return (new PhpCsFixer\Config()) 11 | ->setFinder($finder) 12 | ->setRules($config) 13 | ->setRiskyAllowed(true) 14 | ->setCacheFile(__DIR__ . '/.php-cs-fixer.cache'); 15 | -------------------------------------------------------------------------------- /.php-cs-fixer.tests.php: -------------------------------------------------------------------------------- 1 | in(__DIR__ . '/tests') 6 | ->exclude('__snapshots__'); 7 | 8 | $config = require __DIR__ . '/.php-cs-fixer.common.php'; 9 | 10 | // Additional rules for tests 11 | $config = array_merge( 12 | $config, 13 | [ 14 | 'declare_strict_types' => true, 15 | ] 16 | ); 17 | 18 | return (new PhpCsFixer\Config()) 19 | ->setFinder($finder) 20 | ->setRules($config) 21 | ->setRiskyAllowed(true) 22 | ->setCacheFile(__DIR__ . '/.php-cs-fixer.tests.cache'); 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v3.5.5 - 2025-02-21 4 | 5 | ### What's Changed 6 | 7 | * Fix for incorrect config item types in meta file by @eldair in https://github.com/barryvdh/laravel-ide-helper/pull/1662 8 | * Prevent generation of incorrect property annotation by @skyler544 in https://github.com/barryvdh/laravel-ide-helper/pull/1665 9 | * Fix MorphTo Model Doc Generation by @yparitcher in https://github.com/barryvdh/laravel-ide-helper/pull/1668 10 | * Laravel 12 support by @jonnott in https://github.com/barryvdh/laravel-ide-helper/pull/1672 11 | 12 | ### New Contributors 13 | 14 | * @eldair made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1662 15 | * @skyler544 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1665 16 | * @yparitcher made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1668 17 | * @jonnott made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1672 18 | 19 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.4...v3.5.5 20 | 21 | ## v3.5.4 - 2025-01-14 22 | 23 | ### What's Changed 24 | 25 | * Convert auth() helper to use Auth facade by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1656 26 | * Check if returnType from docblock is not null by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1658 27 | 28 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.3...v3.5.4 29 | 30 | ## v3.5.3 - 2025-01-08 31 | 32 | ### What's Changed 33 | 34 | * Catch meta, tweak auth by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1654 35 | * Check if macro is valid by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1655 36 | * feat: use generics of return type to determine resulting models by @Bloemendaal in https://github.com/barryvdh/laravel-ide-helper/pull/1653 37 | 38 | ### New Contributors 39 | 40 | * @Bloemendaal made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1653 41 | 42 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.2...v3.5.3 43 | 44 | ## v3.5.2 - 2025-01-06 45 | 46 | ### Fixes 47 | 48 | Fix empty/anonymous closure in meta command. 49 | 50 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.1...v3.5.2 51 | 52 | ## v3.5.1 - 2025-01-06 53 | 54 | ### What's Changed 55 | 56 | * Remove duplicate config, fix ->can() by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1650 57 | 58 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.0...v3.5.1 59 | 60 | ## v3.5.0 - 2025-01-06 61 | 62 | ### What's Changed 63 | 64 | * Add phpstorm meta argument hints by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1640 65 | * Add meta override for user return types by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1642 66 | * Use forked ContextFactory by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1643 67 | * Remove php parser by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1644 68 | * Also add eloquent template tags from base class by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1645 69 | * Add more metadata by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1646 70 | * Fixed generating PHPDoc for methods with class templates by @chack1172 in https://github.com/barryvdh/laravel-ide-helper/pull/1647 71 | * Feat guess macro types by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1648 72 | * Allow adding custom Macroable classes. by @mathieutu in https://github.com/barryvdh/laravel-ide-helper/pull/1629 73 | * Add special `dev` to composer keywords by @jnoordsij in https://github.com/barryvdh/laravel-ide-helper/pull/1649 74 | 75 | ### New Contributors 76 | 77 | * @chack1172 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1647 78 | * @mathieutu made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1629 79 | * @jnoordsij made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1649 80 | 81 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.4.0...v3.5.0 82 | 83 | ## v3.4.0 - 2024-12-29 84 | 85 | ### What's Changed 86 | 87 | * fix: add @template TModel of static for Eloquent by @imzyf in https://github.com/barryvdh/laravel-ide-helper/pull/1631 88 | * Add templates to Eloquent by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1634 89 | * Update testsuite for Generator, simplify service provider and mock by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1635 90 | * Add option for only eloquent by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1636 91 | * Add weak generics for array type objects by @LauJosefsen in https://github.com/barryvdh/laravel-ide-helper/pull/1621 92 | * Make all "note" in README apply quote style by @hms5232 in https://github.com/barryvdh/laravel-ide-helper/pull/1590 93 | * Update README.md by @Mtillmann in https://github.com/barryvdh/laravel-ide-helper/pull/1587 94 | * Rename view var by @barryvdh and @pb30 in https://github.com/barryvdh/laravel-ide-helper/pull/1637 and https://github.com/barryvdh/laravel-ide-helper/pull/1563 95 | * Format IDE helper by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1638 96 | * Add TLDR section, update options by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1639 97 | 98 | ### New Contributors 99 | 100 | * @imzyf made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1631 101 | * @LauJosefsen made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1621 102 | * @hms5232 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1590 103 | * @Mtillmann made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1587 104 | * @pb30 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1563 105 | 106 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.3.0...v3.4.0 107 | 108 | ## v3.3.0 - 2024-12-18 109 | 110 | ### What's Changed 111 | 112 | * Feature: Add Config Option to Enforce Nullable Relationships by @jeramyhing in https://github.com/barryvdh/laravel-ide-helper/pull/1580 113 | * Improve replacement of return type for methods from Query\Builder by @pjio in https://github.com/barryvdh/laravel-ide-helper/pull/1575 114 | * Update CHANGELOG.md, fix typo(s) by @NicholasWilsonDEV in https://github.com/barryvdh/laravel-ide-helper/pull/1613 115 | * Fixed PHP 8.4 deprecation warning by @eusonlito in https://github.com/barryvdh/laravel-ide-helper/pull/1622 116 | * Fix PHP 8.4 deprecations by @JeppeKnockaert in https://github.com/barryvdh/laravel-ide-helper/pull/1618 117 | * Assign $output method parameter to $this->output on Generator by @eusonlito in https://github.com/barryvdh/laravel-ide-helper/pull/1623 118 | 119 | ### New Contributors 120 | 121 | * @jeramyhing made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1580 122 | * @pjio made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1575 123 | * @NicholasWilsonDEV made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1613 124 | * @eusonlito made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1622 125 | 126 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.2.2...v3.3.0 127 | 128 | ## v3.2.2 - 2024-10-29 129 | 130 | ### What’s Changed 131 | 132 | * fix(pivot): only use unique classes in the pivot union (Fixes #1606) (#1607) @pataar 133 | * docs(pr): remove the changelog checklist item (#1608) @pataar 134 | * Create update-changelog.yaml (#1605) @barryvdh 135 | 136 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.2.1...v3.2.2 137 | 138 | ## 3.2.1 - 2024-10-28 139 | 140 | ### What's Changed 141 | 142 | * chore: Fix the description of unused option by @KentarouTakeda in https://github.com/barryvdh/laravel-ide-helper/pull/1600 143 | * feat(pivot): add support for multiple pivot types when using the same accessor by @pataar in https://github.com/barryvdh/laravel-ide-helper/pull/1597 144 | * Add support for `AsCollection::using` and `AsEnumCollection::of` casts by @uno-sw in https://github.com/barryvdh/laravel-ide-helper/pull/1577 145 | * Smarter reset by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1603 146 | * feat: use `numeric` type on fields with `decimal` casts by @ekisu in https://github.com/barryvdh/laravel-ide-helper/pull/1583 147 | 148 | ### New Contributors 149 | 150 | * @uno-sw made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1577 151 | * @ekisu made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1583 152 | 153 | **Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.2.0...v3.2.1 154 | 155 | ## 3.2.0 - 2024-10-18 156 | 157 | ### Fixed 158 | 159 | - Fix type of hashed model property to `string` 160 | 161 | ### Changed 162 | 163 | - Update view "version" variable name to avoid potential conflicts 164 | 165 | - Add support for EloquentBuilder generics introduced in Laravel 11.15. 166 | 167 | - Drop support for Laravel versions earlier than 11.15. 168 | 169 | 170 | ### Added 171 | 172 | - Introduce `enforce_nullable_relationships` configuration option to control how nullable Eloquent relationships are enforced during static analysis. This provides flexibility for scenarios where application logic ensures data integrity without relying on database constraints. [#1580 / jeramyhing](https://github.com/barryvdh/laravel-ide-helper/pull/1580) 173 | 174 | - Add support for AsCollection::using and AsEnumCollection::of casts [#1577 / uno-sw](https://github.com/barryvdh/laravel-ide-helper/pull/1577) 175 | 176 | 177 | ## 3.1.0 - 2024-07-12 178 | 179 | ### Fixed 180 | 181 | - Fix return value of query scopes from parent class [#1366 / sforward](https://github.com/barryvdh/laravel-ide-helper/pull/1366) 182 | - Add static to isBuiltin() check in ide-helper:models [#1541 / bram-pkg](https://github.com/barryvdh/laravel-ide-helper/pull/1541) 183 | - Fix for getSomethingAttribute functions which return a collection with type templating in the phpDoc. [#1567 / stefanScrumble](https://github.com/barryvdh/laravel-ide-helper/pull/1567) 184 | 185 | ### Added 186 | 187 | - Add type to pivot when using a custom pivot class [#1518 / d3v2a](https://github.com/barryvdh/laravel-ide-helper/pull/1518) 188 | - Add support in morphTo relationship for null values [#1547 / matysekmichal](https://github.com/barryvdh/laravel-ide-helper/pull/1547) 189 | - Add support for AsEnumCollection casts [#1557 / Braunson](https://github.com/barryvdh/laravel-ide-helper/pull/1557) 190 | - Support for Attribute class in attributes [#1567 / stefanScrumble](https://github.com/barryvdh/laravel-ide-helper/pull/1567) 191 | 192 | ## 3.0.0 - 2024-03-01 193 | 194 | ### Added 195 | 196 | - Support for Laravel 11 [#1520 / KentarouTakeda](https://github.com/barryvdh/laravel-ide-helper/pull/1520) 197 | 198 | ### Changed 199 | 200 | - Make `--reset` always keep the text and remove `--smart-reset`. Always skip the classname [#1523 / barryvdh](https://github.com/barryvdh/laravel-ide-helper/pull/1523) & [#1525 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1525) 201 | - Use short types (`int` and `bool` instead of `integer` and `boolean`) [#1524 / barryvdh](https://github.com/barryvdh/laravel-ide-helper/pull/1524) 202 | 203 | ### Removed 204 | 205 | - Support for Laravel 9 and use of doctrine/dbal [#1512 / barryvdh](https://github.com/barryvdh/laravel-ide-helper/pull/1512) 206 | With this functionality gone, a few changes have been made: 207 | - support for custom datatypes has been dropped (config `custom_db_types`) unknown data types default to `string` now and to fix the type, add a proper cast in Eloquent 208 | - You *might* have top-level dependency on doctrine/dbal. This may have been in the past due to ide-helper, we suggest to check if you still need it and remove it otherwise 209 | - Minimum PHP version, due to Laravel 10, is now PHP 8.1 210 | 211 | 212 | ## 2024-02-15, 2.15.1 213 | 214 | ### Fixed 215 | 216 | - Fix final class keyword in wrong position [#1517 / barryvdh](https://github.com/barryvdh/laravel-ide-helper/pull/1517) 217 | 218 | ### Changed 219 | 220 | ### Added 221 | 222 | ## 2024-02-14, 2.15.0 223 | 224 | ### Fixed 225 | 226 | - Fix case issue in `ModelsCommand::unsetMethod()` [#1453 / leo108](https://github.com/barryvdh/laravel-ide-helper/pull/1453) 227 | - Fix non-facade classes will result in no autocomplete [#841 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/841) 228 | - Skip swoole, otherwise fatal error [#1477 / TimoFrenzel](https://github.com/barryvdh/laravel-ide-helper/pull/1477) 229 | - Fix vulnerability CVE-2021-43608 [#1392 / allanlaal](https://github.com/barryvdh/laravel-ide-helper/pull/1392) 230 | - Reset foreignKeyConstraintsColumns on model loop start [#1461 / snmatsui](https://github.com/barryvdh/laravel-ide-helper/pull/1461) 231 | - Accept scope & scopes as relation [#1452 / Muetze42](https://github.com/barryvdh/laravel-ide-helper/pull/1452) 232 | - Fix #1300 relation_return_type must take precedence if it is defined [#1394 / menthol](https://github.com/barryvdh/laravel-ide-helper/pull/1394) 233 | 234 | ### Changed 235 | 236 | - Disable inspections of helper files [#1486 / eidng8](https://github.com/barryvdh/laravel-ide-helper/pull/1486) 237 | - Removed support for Laravel 8 and therefore for PHP < 8.0 [#1504 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1504) 238 | 239 | ### Added 240 | 241 | - Add support for enum default arguments using enum cases. [#1464 / d8vjork](https://github.com/barryvdh/laravel-ide-helper/pull/1464) 242 | - Add support for real-time facades in the helper file. [#1455 / filipac](https://github.com/barryvdh/laravel-ide-helper/pull/1455) 243 | - Add support for relations with composite keys. [#1479 / calebdw](https://github.com/barryvdh/laravel-ide-helper/pull/1479) 244 | - Add support for attribute accessors with no backing field or type hinting [#1411 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1411). 245 | - Add support for AsCollection and AsArrayObject casts [#1393 / pataar](https://github.com/barryvdh/laravel-ide-helper/pull/1393) 246 | - Reintroduce support for multi-db setups [#1426 / benpoulson](https://github.com/barryvdh/laravel-ide-helper/pull/1426) 247 | - Support the BINARY(...) database field type [#1434 / Sfonxs](https://github.com/barryvdh/laravel-ide-helper/pull/1434) 248 | - Add AllowDynamicProperties Attribute to cooperate with php8.2 deprecation [#1428 / GeoSot](https://github.com/barryvdh/laravel-ide-helper/pull/1428) 249 | 250 | ## 2024-02-05, 2.14.0 251 | 252 | ### Changed 253 | 254 | - Official support for Lumen has been dropped [#1425 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1425) 255 | - Refactor resolving of null information for custom casted attribute types [#1330 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1330) 256 | 257 | ### Fixed 258 | 259 | - Catch exceptions when loading aliases [#1465 / dongm2ez](https://github.com/barryvdh/laravel-ide-helper/pull/1465) 260 | 261 | ### Added 262 | 263 | - Add support for nikic/php-parser 5 (next to 4) [#1502 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1502) 264 | - Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380) 265 | - Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339) 266 | 267 | ## 2023-02-04, 2.13.0 268 | 269 | ### Fixed 270 | 271 | - Fix return type of methods provided by `SoftDeletes` [#1345 / KentarouTakeda](https://github.com/barryvdh/laravel-ide-helper/pull/1345) 272 | - Handle PHP 8.1 deprecation warnings when passing `null` to `new \ReflectionClass` [#1351 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1351) 273 | - Fix issue where \Eloquent is not included when using write_mixin [#1352 / Jefemy](https://github.com/barryvdh/laravel-ide-helper/pull/1352) 274 | - Fix model factory method arguments for Laravel >= 9 [#1361 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1361) 275 | - Improve return type of mock helper methods in tests [#1405 / bentleyo](https://github.com/barryvdh/laravel-ide-helper/pull/1405) 276 | - Fix Castable class if failed to detect it from return types [#1388 / kwarcu](https://github.com/barryvdh/laravel-ide-helper/pull/1388) 277 | 278 | ### Added 279 | 280 | - Added Laravel 10 support [#1407 / lptn](https://github.com/barryvdh/laravel-ide-helper/pull/1407) 281 | - Add support for custom casts that implement `CastsInboundAttributes` [#1329 / sforward](https://github.com/barryvdh/laravel-ide-helper/pull/1329) 282 | - Add option `use_generics_annotations` for collection type hints [#1298 / tanerkay](https://github.com/barryvdh/laravel-ide-helper/pull/1298) 283 | 284 | ## 2022-03-06, 2.12.3 285 | 286 | ### Fixed 287 | 288 | - Fix date and datetime handling for attributes that set a serialization format option for the Carbon instance [#1324 / FLeudts](https://github.com/barryvdh/laravel-ide-helper/pull/1324) 289 | - Fix composer conflict with composer/pcre version 2/3. [#1327 / barryvdh](https://github.com/barryvdh/laravel-ide-helper/pull/1327) 290 | 291 | ## 2022-02-08, 2.12.2 292 | 293 | ### Fixed 294 | 295 | - Remove composer dependency, use copy of ClassMapGenerator [#1313 / barryvdh](https://github.com/barryvdh/laravel-ide-helper/pull/1313) 296 | 297 | ## 2022-01-24, 2.12.1 298 | 299 | ### Fixed 300 | 301 | - Properly handle `Castable`s without return type. [#1306 / binotaliu](https://github.com/barryvdh/laravel-ide-helper/pull/1306) 302 | 303 | ## 2022-01-23, 2.12.0 304 | 305 | ### Added 306 | 307 | - Add support for custom casts that using `Castable` [#1287 / binotaliu](https://github.com/barryvdh/laravel-ide-helper/pull/1287) 308 | - Added Laravel 9 support [#1297 / rcerljenko](https://github.com/barryvdh/laravel-ide-helper/pull/1297) 309 | - Added option `additional_relation_return_types` for custom relations that don't fit the typical naming scheme 310 | 311 | ## 2022-01-03, 2.11.0 312 | 313 | ### Added 314 | 315 | - Add support for Laravel 8.77 Attributes [#1289 / SimonJnsson](https://github.com/barryvdh/laravel-ide-helper/pull/1289) 316 | - Add support for cast types `decimal:*`, `encrypted:*`, `immutable_date`, `immutable_datetime`, `custom_datetime`, and `immutable_custom_datetime` [#1262 / miken32](https://github.com/barryvdh/laravel-ide-helper/pull/1262) 317 | - Add support of variadic parameters in `ide-helper:models` [#1234 / shaffe-fr](https://github.com/barryvdh/laravel-ide-helper/pull/1234) 318 | - Add support of custom casts without properties [#1267 / sparclex](https://github.com/barryvdh/laravel-ide-helper/pull/1267) 319 | 320 | ### Fixed 321 | 322 | - Fix recursively searching for `HasFactory` and `Macroable` traits [#1216 / daniel-de-wit](https://github.com/barryvdh/laravel-ide-helper/pull/1216) 323 | - Use platformName to determine db type when casting boolean types [#1212 / stockalexander](https://github.com/barryvdh/laravel-ide-helper/pull/1212) 324 | 325 | ### Changed 326 | 327 | - Move default models helper filename to config [#1241 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1241) 328 | 329 | ## 2021-06-18, 2.10.1 330 | 331 | ### Added 332 | 333 | - Added Type registration according to [Custom Mapping Types documentation](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types) [#1228 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1241) 334 | 335 | ### Fixed 336 | 337 | - Fixing issue where configured custom_db_types could cause a DBAL exception to be thrown while running `ide-helper:models` [#1228 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1241) 338 | 339 | ## 2021-04-09, 2.10.0 340 | 341 | ### Added 342 | 343 | - Allowing Methods to be set or unset in ModelHooks [#1198 / jenga201](https://github.com/barryvdh/laravel-ide-helper/pull/1198) 344 | Note: the visibility of `\Barryvdh\LaravelIdeHelper\Console\ModelsCommand::setMethod` has been changed to **public**! 345 | 346 | ### Fixed 347 | 348 | - Fixing issue where incorrect autoloader unregistered [#1210 / tezhm](https://github.com/barryvdh/laravel-ide-helper/pull/1210) 349 | 350 | ## 2021-04-02, 2.9.3 351 | 352 | ### Fixed 353 | 354 | - Support both customized namespace factories as well as default resolvable ones [#1201 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1201) 355 | 356 | ## 2021-04-01, 2.9.2 357 | 358 | ### Added 359 | 360 | - 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) 361 | 362 | ### Fixed 363 | 364 | - Fix ide-helper:models exception if model doesn't have factory [#1196 / ahmed-aliraqi](https://github.com/barryvdh/laravel-ide-helper/pull/1196) 365 | - Running tests triggering post_migrate hooks [#1193 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1193) 366 | - Array_merge error when config is cached prior to package install [#1184 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1184) 367 | 368 | ## 2021-03-15, 2.9.1 369 | 370 | ### Added 371 | 372 | - Generate PHPDoc for Laravel 8.x factories [#1074 / ahmed-aliraqi](https://github.com/barryvdh/laravel-ide-helper/pull/1074) 373 | - Add a comment to a property like table columns [#1168 / biiiiiigmonster](https://github.com/barryvdh/laravel-ide-helper/pull/1168) 374 | - Added `post_migrate` hook to run commands after a migration [#1163 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1163) 375 | - Allow for PhpDoc for macros with union types [#1148 / riesjart](https://github.com/barryvdh/laravel-ide-helper/pull/1148) 376 | 377 | ### Fixed 378 | 379 | - Error when generating helper for invokable classes [#1124 / standaniels](https://github.com/barryvdh/laravel-ide-helper/pull/1124) 380 | - Fix broken ReflectionUnionTypes [#1132 / def-studio](https://github.com/barryvdh/laravel-ide-helper/pull/1132) 381 | - Relative class names are not converted to fully-qualified class names [#1005 / SavKS](https://github.com/barryvdh/laravel-ide-helper/pull/1005) 382 | 383 | ## 2020-12-30, 2.9.0 384 | 385 | ### Changed 386 | 387 | - 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) 388 | 389 | ### Fixed 390 | 391 | - `Macro::initPhpDoc()` will save original docblock if present [#1116 / LastDragon-ru](https://github.com/barryvdh/laravel-ide-helper/pull/1116) 392 | - `Alias` will grab macros from `\Illuminate\Database\Eloquent\Builder` too [#1118 / LastDragon-ru](https://github.com/barryvdh/laravel-ide-helper/pull/1118) 393 | 394 | ## 2020-12-08, 2.8.2 395 | 396 | ### Added 397 | 398 | - Fix phpdoc generate for custom cast with parameter [#986 / artelkr](https://github.com/barryvdh/laravel-ide-helper/pull/986) 399 | - Created a possibility to add custom relation type [#987 / efinder2](https://github.com/barryvdh/laravel-ide-helper/pull/987) 400 | - Added `@see` with macro/mixin definition location to PhpDoc [#1054 / riesjart](https://github.com/barryvdh/laravel-ide-helper/pull/1054) 401 | - Initial compatibility for PHP8 [#1106 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1106) 402 | 403 | ### Changed 404 | 405 | - Implement DeferrableProvider [#914 / kon-shou](https://github.com/barryvdh/laravel-ide-helper/pull/914) 406 | 407 | ### Fixed 408 | 409 | - Compatibility with Lumen [#1043 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1043) 410 | - Allow model_locations to have glob patterns [#1059 / saackearl](https://github.com/barryvdh/laravel-ide-helper/pull/1059) 411 | - Error when generating helper for macroable classes which are not facades and contain a "fake" method [#1066 / domkrm] (https://github.com/barryvdh/laravel-ide-helper/pull/1066) 412 | - Casts with a return type of `static` or `$this` now resolve to an instance of the cast [#1103 / riesjart](https://github.com/barryvdh/laravel-ide-helper/pull/1103) 413 | 414 | ### Removed 415 | 416 | - Removed format and broken generateJsonHelper [#1053 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1053) 417 | 418 | ## 2020-09-07, 2.8.1 419 | 420 | ### Added 421 | 422 | - Support Laravel 8 [#1022 / barryvdh](https://github.com/barryvdh/laravel-ide-helper/pull/1022) 423 | - Add option to force usage of FQN [#1031 / edvordo](https://github.com/barryvdh/laravel-ide-helper/pull/1031) 424 | - Add support for macros of all macroable classes [#1006 / domkrm](https://github.com/barryvdh/laravel-ide-helper/pull/1006) 425 | 426 | ## 2020-08-11, 2.8.0 427 | 428 | ### Added 429 | 430 | - Add static return type to builder methods [#924 / dmason30](https://github.com/barryvdh/laravel-ide-helper/pull/924) 431 | - Add `optional` to meta generator for PhpStorm [#932 / halaei](https://github.com/barryvdh/laravel-ide-helper/pull/932) 432 | - Decimal columns as string in Models [#948 / fgibaux](https://github.com/barryvdh/laravel-ide-helper/pull/948) 433 | - Simplify full namespaces for already included resources [#954 / LANGERGabriel](https://github.com/barryvdh/laravel-ide-helper/pull/954) 434 | - Make writing relation count properties optional [#969 / AegirLeet](https://github.com/barryvdh/laravel-ide-helper/pull/969) 435 | - Add more methods able to resolve container instances [#996 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/996) 436 | 437 | ### Fixed 438 | 439 | - Test `auth` is bound before detect Auth driver [#946 / zhwei](https://github.com/barryvdh/laravel-ide-helper/pull/946) 440 | - Fix inline doc-block for final models [#944 / Gummibeer](https://github.com/barryvdh/laravel-ide-helper/pull/955) 441 | 442 | ## 2020-04-22, 2.7.0 443 | 444 | ### Added 445 | 446 | - Add `ignored_models` as config option [#890 / pataar](https://github.com/barryvdh/laravel-ide-helper/pull/890) 447 | - Infer return type from reflection if no phpdoc given [#906 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/906) 448 | - Add custom collection support for get and all methods [#903 / dmason30](https://github.com/barryvdh/laravel-ide-helper/pull/903) 449 | - if a model implements interfaces, include them in the stub [#920 / mr-feek](https://github.com/barryvdh/laravel-ide-helper/pull/920) 450 | - Generate noinspections PHPStorm tags [#905 / mzglinski](https://github.com/barryvdh/laravel-ide-helper/pull/905) 451 | - Added support for Laravel 7 custom casts [#913 / belamov](https://github.com/barryvdh/laravel-ide-helper/pull/913) 452 | - Ability to use patterns for model_locations [#921 / 4n70w4](https://github.com/barryvdh/laravel-ide-helper/pull/921) 453 | 454 | ### Fixed 455 | 456 | - MorphToMany relations with query not working [#894 / UksusoFF](https://github.com/barryvdh/laravel-ide-helper/pull/894) 457 | - Fix camelCase duplicated properties generator [#881 / bop10](https://github.com/barryvdh/laravel-ide-helper/pull/881) 458 | - Prevent generation of invalid code for certain parameter default values [#901 / loilo](https://github.com/barryvdh/laravel-ide-helper/pull/901) 459 | - Make hasOne and morphOne nullable [#864 / leo108](https://github.com/barryvdh/laravel-ide-helper/pull/864) 460 | - Remove unnecessary and wrong definition of SoftDelete methods [#918 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/918) 461 | - Unregister meta command custom autoloader when it is no longer needed [#919 / mr-feek](https://github.com/barryvdh/laravel-ide-helper/pull/919) 462 | 463 | ## 2020-02-25, 2.6.7 464 | 465 | ### Added 466 | 467 | - Support for Laravel 7 [commit by barryvdh](https://github.com/barryvdh/laravel-ide-helper/commit/edd69c5e0508972c81f1f7173236de2459c45814) 468 | 469 | ## 2019-12-02, 2.6.6 470 | 471 | ### Added 472 | 473 | - Add splat operator (...) support [#860 / ngmy](https://github.com/barryvdh/laravel-ide-helper/pull/860) 474 | - Add support for custom date class via Date::use() [#859 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/859) 475 | 476 | ### Fixed 477 | 478 | - Prevent undefined property errors [#877 / matt-allan](https://github.com/barryvdh/laravel-ide-helper/pull/877) 479 | 480 | 481 | --- 482 | 483 | Missing an older changelog? Feel free to submit a PR! 484 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Barry vd. Heuvel 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IDE Helper Generator for Laravel 2 | 3 | [![Tests](https://github.com/barryvdh/laravel-ide-helper/actions/workflows/run-tests.yml/badge.svg)](https://github.com/barryvdh/laravel-ide-helper/actions) 4 | [![Packagist License](https://img.shields.io/badge/Licence-MIT-blue)](http://choosealicense.com/licenses/mit/) 5 | [![Latest Stable Version](https://img.shields.io/packagist/v/barryvdh/laravel-ide-helper?label=Stable)](https://packagist.org/packages/barryvdh/laravel-ide-helper) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/barryvdh/laravel-ide-helper?label=Downloads)](https://packagist.org/packages/barryvdh/laravel-ide-helper) 7 | [![Fruitcake](https://img.shields.io/badge/Powered%20By-Fruitcake-b2bc35.svg)](https://fruitcake.nl/) 8 | 9 | **Complete PHPDocs, directly from the source** 10 | 11 | This package generates helper files that enable your IDE to provide accurate autocompletion. 12 | Generation is done based on the files in your project, so they are always up-to-date. 13 | 14 | The 3.x branch supports Laravel 10 and 11. For older version, use the 2.x releases. 15 | 16 | - [Installation](#installation) 17 | - [Usage](#usage) 18 | - [Automatic PHPDoc generation for Laravel Facades](#automatic-phpdoc-generation-for-laravel-facades) 19 | - [Automatic PHPDocs for models](#automatic-phpdocs-for-models) 20 | - [Model Directories](#model-directories) 21 | - [Ignore Models](#ignore-models) 22 | - [Model Hooks](#model-hooks) 23 | - [Automatic PHPDocs generation for Laravel Fluent methods](#automatic-phpdocs-generation-for-laravel-fluent-methods) 24 | - [Auto-completion for factory builders](#auto-completion-for-factory-builders) 25 | - [PhpStorm Meta for Container instances](#phpstorm-meta-for-container-instances) 26 | - [License](#license) 27 | 28 | ## Installation 29 | 30 | Require this package with composer using the following command: 31 | 32 | ```bash 33 | composer require --dev barryvdh/laravel-ide-helper 34 | ``` 35 | 36 | 37 | ## Usage 38 | 39 | ### TL;DR 40 | 41 | Run this to generate autocompletion for Facades. This creates _ide_helper.php 42 | 43 | ``` 44 | php artisan ide-helper:generate 45 | ``` 46 | 47 | Run this to add phpdocs for your models. Add -RW to Reset existing phpdocs and Write to the models directly. 48 | ``` 49 | php artisan ide-helper:models -RW 50 | ``` 51 | 52 | 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. 53 | 54 | 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. 55 | 56 | 57 | _Check out [this Laracasts video](https://laracasts.com/series/how-to-be-awesome-in-phpstorm/episodes/15) for a quick introduction/explanation!_ 58 | 59 | - `php artisan ide-helper:generate` - [PHPDoc generation for Laravel Facades ](#automatic-phpdoc-generation-for-laravel-facades) 60 | - `php artisan ide-helper:models` - [PHPDocs for models](#automatic-phpdocs-for-models) 61 | - `php artisan ide-helper:meta` - [PhpStorm Meta file](#phpstorm-meta-for-container-instances) 62 | 63 | 64 | > Note: You do need CodeComplice for Sublime Text: https://github.com/spectacles/CodeComplice 65 | 66 | ### Automatic PHPDoc generation for Laravel Facades 67 | 68 | You can now re-generate the docs yourself (for future updates) 69 | 70 | ```bash 71 | php artisan ide-helper:generate 72 | ``` 73 | 74 | This will generate the file `_ide_helper.php` which is expected to be additionally parsed by your IDE for autocomplete. You can use the config `filename` to change its name. 75 | 76 | You can configure your `composer.json` to do this each time you update your dependencies: 77 | 78 | ```js 79 | "scripts": { 80 | "post-update-cmd": [ 81 | "Illuminate\\Foundation\\ComposerScripts::postUpdate", 82 | "@php artisan ide-helper:generate", 83 | "@php artisan ide-helper:meta" 84 | ] 85 | }, 86 | ``` 87 | 88 | You can also publish the config file to change implementations (ie. interface to specific class) or set defaults for `--helpers`. 89 | 90 | ```bash 91 | php artisan vendor:publish --provider="Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider" --tag=config 92 | ``` 93 | 94 | The generator tries to identify the real class, but if it cannot be found, you can define it in the config file. 95 | 96 | Some classes need a working database connection. If you do not have a default working connection, some facades will not be included. 97 | You can use an in-memory SQLite driver by adding the `-M` option. 98 | 99 | If you use [real-time facades](https://laravel.com/docs/master/facades#real-time-facades) in your app, those will also be included in the generated file using a `@mixin` annotation and extending the original class underneath the facade. 100 | 101 | > **Note**: this feature uses the generated real-time facades files in the `storage/framework/cache` folder. Those files are generated on-demand as you use the real-time facade, so if the framework has not generated that first, it will not be included in the helper file. Run the route/command/code first and then regenerate the helper file and this time the real-time facade will be included in it. 102 | 103 | You can choose to include helper files. This is not enabled by default, but you can override it with the `--helpers (-H)` option. 104 | The `Illuminate/Support/helpers.php` is already set up, but you can add/remove your own files in the config file. 105 | 106 | ### Automatic PHPDoc generation for macros and mixins 107 | 108 | This package can generate PHPDocs for macros and mixins which will be added to the `_ide_helper.php` file. 109 | 110 | But this only works if you use type hinting when declaring a macro. 111 | 112 | ```php 113 | Str::macro('concat', function(string $str1, string $str2) : string { 114 | return $str1 . $str2; 115 | }); 116 | ``` 117 | 118 | ### Automatic PHPDocs for models 119 | 120 | If you don't want to write your properties yourself, you can use the command `php artisan ide-helper:models` to generate 121 | PHPDocs, based on table columns, relations and getters/setters. 122 | 123 | > Note: this command requires a working database connection to introspect the table of each model 124 | 125 | By default, you are asked to overwrite or write to a separate file (`_ide_helper_models.php`). 126 | You can write the comments directly to your Model file, using the `--write (-W)` option, or 127 | force to not write with `--nowrite (-N)`. 128 | 129 | Alternatively using the `--write-mixin (-M)` option will only add a mixin tag to your Model file, 130 | writing the rest in (`_ide_helper_models.php`). 131 | The class name will be different from the model, avoiding the IDE duplicate annoyance. 132 | 133 | > Please make sure to back up your models, before writing the info. 134 | 135 | > You need the _ide_helper.php file to add the QueryBuilder methods. You can add --write-eloquent-helper/-E to generate a minimal version. If this file does not exist, you will be prompted for it. 136 | 137 | Writing to the models should keep the existing comments and only append new properties/methods. It will not update changed properties/methods. 138 | 139 | With the `--reset (-R)` option, the whole existing PHPDoc is replaced, including any comments that have been made. 140 | 141 | ```bash 142 | php artisan ide-helper:models "App\Models\Post" 143 | ``` 144 | 145 | ```php 146 | /** 147 | * App\Models\Post 148 | * 149 | * @property integer $id 150 | * @property integer $author_id 151 | * @property string $title 152 | * @property string $text 153 | * @property \Illuminate\Support\Carbon $created_at 154 | * @property \Illuminate\Support\Carbon $updated_at 155 | * @property-read \User $author 156 | * @property-read \Illuminate\Database\Eloquent\Collection|\Comment[] $comments 157 | * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post newModelQuery() 158 | * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post newQuery() 159 | * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post query() 160 | * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post whereTitle($value) 161 | * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post forAuthors(\User ...$authors) 162 | * … 163 | */ 164 | ``` 165 | 166 | With the `--write-mixin (-M)` option 167 | ```php 168 | /** 169 | * … 170 | * @mixin IdeHelperPost 171 | */ 172 | ``` 173 | 174 | #### Model Directories 175 | 176 | By default, models in `app/models` are scanned. The optional argument tells what models to use (also outside app/models). 177 | 178 | ```bash 179 | php artisan ide-helper:models "App\Models\Post" "App\Models\User" 180 | ``` 181 | 182 | You can also scan a different directory, using the `--dir` option (relative from the base path): 183 | 184 | ```bash 185 | php artisan ide-helper:models --dir="path/to/models" --dir="app/src/Model" 186 | ``` 187 | 188 | You can publish the config file (`php artisan vendor:publish`) and set the default directories. 189 | 190 | #### Ignore Models 191 | 192 | Models can be ignored using the `--ignore (-I)` option 193 | 194 | ```bash 195 | php artisan ide-helper:models --ignore="App\Models\Post,App\Models\User" 196 | ``` 197 | 198 | Or can be ignored by setting the `ignored_models` config 199 | 200 | ```php 201 | 'ignored_models' => [ 202 | App\Post::class, 203 | Api\User::class 204 | ], 205 | ``` 206 | 207 | #### Magic `where*` methods 208 | 209 | Eloquent allows calling `where` on your models, e.g. `Post::whereTitle(…)` and automatically translates this to e.g. `Post::where('title', '=', '…')`. 210 | 211 | 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`. 212 | 213 | #### Magic `*_count` properties 214 | 215 | You may use the [`::withCount`](https://laravel.com/docs/master/eloquent-relationships#counting-related-models) method to count the number results from a relationship without actually loading them. Those results are then placed in attributes following the `_count` convention. 216 | 217 | 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`. 218 | 219 | #### Generics annotations 220 | 221 | Laravel 9 introduced generics annotations in DocBlocks for collections. PhpStorm 2022.3 and above support the use of generics annotations within `@property` and `@property-read` declarations in DocBlocks, e.g. `Collection` instead of `Collection|User[]`. 222 | 223 | These can be disabled by setting the config `use_generics_annotations` to `false`. 224 | 225 | #### Support `@comment` based on DocBlock 226 | 227 | 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: 228 | ```php 229 | class Users extends Model 230 | { 231 | /** 232 | * @comment Get User's full name 233 | * 234 | * @return string 235 | */ 236 | public function getFullNameAttribute(): string 237 | { 238 | return $this->first_name . ' ' .$this->last_name ; 239 | } 240 | } 241 | 242 | // => after generate models 243 | 244 | /** 245 | * App\Models\Users 246 | * 247 | * @property-read string $full_name Get User's full name 248 | * … 249 | */ 250 | ``` 251 | 252 | #### Dedicated Eloquent Builder methods 253 | 254 | A new method to the eloquent models was added called `newEloquentBuilder` [Reference](https://timacdonald.me/dedicated-eloquent-model-query-builders/) where we can 255 | add support for creating a new dedicated class instead of using local scopes in the model itself. 256 | 257 | If for some reason it's undesired to have them generated (one for each column), you can disable this via config `write_model_external_builder_methods` and setting it to `false`. 258 | 259 | #### Custom Relationship Types 260 | 261 | If you are using relationships not built into Laravel you will need to specify the name and returning class in the config to get proper generation. 262 | 263 | ```php 264 | 'additional_relation_types' => [ 265 | 'externalHasMany' => \My\Package\externalHasMany::class 266 | ], 267 | ``` 268 | 269 | Found relationships will typically generate a return value based on the name of the relationship. 270 | 271 | If your custom relationships don't follow this traditional naming scheme you can define its return type manually. The available options are `many` and `morphTo`. 272 | 273 | ```php 274 | 'additional_relation_return_types' => [ 275 | 'externalHasMultiple' => 'many' 276 | ], 277 | ``` 278 | 279 | #### Model Hooks 280 | 281 | If you need additional information on your model from sources that are not handled by default, you can hook in to the 282 | generation process with model hooks to add extra information on the fly. 283 | Simply create a class that implements `ModelHookInterface` and add it to the `model_hooks` array in the config: 284 | 285 | ```php 286 | 'model_hooks' => [ 287 | MyCustomHook::class, 288 | ], 289 | ``` 290 | 291 | The `run` method will be called during generation for every model and receives the current running `ModelsCommand` and the current `Model`, e.g.: 292 | 293 | ```php 294 | class MyCustomHook implements ModelHookInterface 295 | { 296 | public function run(ModelsCommand $command, Model $model): void 297 | { 298 | if (! $model instanceof MyModel) { 299 | return; 300 | } 301 | 302 | $command->setProperty('custom', 'string', true, false, 'My custom property'); 303 | $command->unsetMethod('method'); 304 | $command->setMethod('method', $command->getMethodType($model, '\Some\Class'), ['$param']); 305 | } 306 | } 307 | ``` 308 | 309 | ```php 310 | /** 311 | * MyModel 312 | * 313 | * @property integer $id 314 | * @property-read string $custom 315 | ``` 316 | 317 | ### Automatic PHPDocs generation for Laravel Fluent methods 318 | 319 | If you need PHPDocs support for Fluent methods in migration, for example 320 | 321 | ```php 322 | $table->string("somestring")->nullable()->index(); 323 | ``` 324 | 325 | After publishing vendor, simply change the `include_fluent` line in your `config/ide-helper.php` file into: 326 | 327 | ```php 328 | 'include_fluent' => true, 329 | ``` 330 | 331 | Then run `php artisan ide-helper:generate`, you will now see all Fluent methods recognized by your IDE. 332 | 333 | ### Auto-completion for factory builders 334 | 335 | If you would like the `factory()->create()` and `factory()->make()` methods to return the correct model class, 336 | you can enable custom factory builders with the `include_factory_builders` line in your `config/ide-helper.php` file. 337 | Deprecated for Laravel 8 or latest. 338 | 339 | ```php 340 | 'include_factory_builders' => true, 341 | ``` 342 | 343 | For this to work, you must also publish the PhpStorm Meta file (see below). 344 | 345 | ## PhpStorm Meta for Container instances 346 | 347 | It's possible to generate a PhpStorm meta file to [add support for factory design pattern](https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html). 348 | For Laravel, this means we can make PhpStorm understand what kind of object we are resolving from the IoC Container. 349 | For example, `events` will return an `Illuminate\Events\Dispatcher` object, 350 | so with the meta file you can call `app('events')` and it will autocomplete the Dispatcher methods. 351 | 352 | ```bash 353 | php artisan ide-helper:meta 354 | ``` 355 | 356 | ```php 357 | app('events')->fire(); 358 | \App::make('events')->fire(); 359 | 360 | /** @var \Illuminate\Foundation\Application $app */ 361 | $app->make('events')->fire(); 362 | 363 | // When the key is not found, it uses the argument as class name 364 | app('App\SomeClass'); 365 | // Also works with 366 | app(App\SomeClass::class); 367 | ``` 368 | 369 | > Note: You might need to restart PhpStorm and make sure `.phpstorm.meta.php` is indexed. 370 | 371 | > Note: When you receive a FatalException: class not found, check your config 372 | > (for example, remove S3 as cloud driver when you don't have S3 configured. Remove Redis ServiceProvider when you don't use it). 373 | 374 | You can change the generated filename via the config `meta_filename`. This can be useful for cases where you want to take advantage of PhpStorm's support of the _directory_ `.phpstorm.meta.php/`: all files placed there are parsed, should you want to provide additional files to PhpStorm. 375 | 376 | ## License 377 | 378 | The Laravel IDE Helper Generator is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 379 | 380 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "barryvdh/laravel-ide-helper", 3 | "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", 4 | "license": "MIT", 5 | "keywords": [ 6 | "laravel", 7 | "autocomplete", 8 | "ide", 9 | "helper", 10 | "phpstorm", 11 | "netbeans", 12 | "sublime", 13 | "codeintel", 14 | "phpdoc", 15 | "dev" 16 | ], 17 | "authors": [ 18 | { 19 | "name": "Barry vd. Heuvel", 20 | "email": "barryvdh@gmail.com" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.2", 25 | "ext-json": "*", 26 | "barryvdh/reflection-docblock": "^2.3", 27 | "composer/class-map-generator": "^1.0", 28 | "illuminate/console": "^11.15 || ^12", 29 | "illuminate/database": "^11.15 || ^12", 30 | "illuminate/filesystem": "^11.15 || ^12", 31 | "illuminate/support": "^11.15 || ^12" 32 | }, 33 | "require-dev": { 34 | "ext-pdo_sqlite": "*", 35 | "friendsofphp/php-cs-fixer": "^3", 36 | "illuminate/config": "^11.15 || ^12", 37 | "illuminate/view": "^11.15 || ^12", 38 | "mockery/mockery": "^1.4", 39 | "orchestra/testbench": "^9.2 || ^10", 40 | "phpunit/phpunit": "^10.5 || ^11.5.3", 41 | "spatie/phpunit-snapshot-assertions": "^4 || ^5", 42 | "vimeo/psalm": "^5.4", 43 | "vlucas/phpdotenv": "^5" 44 | }, 45 | "suggest": { 46 | "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)." 47 | }, 48 | "minimum-stability": "dev", 49 | "prefer-stable": true, 50 | "autoload": { 51 | "psr-4": { 52 | "Barryvdh\\LaravelIdeHelper\\": "src" 53 | } 54 | }, 55 | "autoload-dev": { 56 | "psr-4": { 57 | "Barryvdh\\LaravelIdeHelper\\Tests\\": "tests" 58 | } 59 | }, 60 | "config": { 61 | "allow-plugins": { 62 | "composer/package-versions-deprecated": true 63 | }, 64 | "sort-packages": true 65 | }, 66 | "extra": { 67 | "branch-alias": { 68 | "dev-master": "3.5-dev" 69 | }, 70 | "laravel": { 71 | "providers": [ 72 | "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" 73 | ] 74 | } 75 | }, 76 | "scripts": { 77 | "analyze": "psalm", 78 | "check-style": [ 79 | "php-cs-fixer fix --diff --diff-format=udiff --dry-run", 80 | "php-cs-fixer fix --diff --diff-format=udiff --dry-run --config=.php_cs.tests.php" 81 | ], 82 | "fix-style": [ 83 | "php-cs-fixer fix", 84 | "php-cs-fixer fix --config=.php-cs-fixer.tests.php" 85 | ], 86 | "psalm-set-baseline": "psalm --set-baseline=psalm-baseline.xml", 87 | "test": "phpunit", 88 | "test-ci": "phpunit -d --without-creating-snapshots", 89 | "test-regenerate": "phpunit -d --update-snapshots" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /config/ide-helper.php: -------------------------------------------------------------------------------- 1 | '_ide_helper.php', 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Models filename 19 | |-------------------------------------------------------------------------- 20 | | 21 | | The default filename for the models helper file. 22 | | 23 | */ 24 | 25 | 'models_filename' => '_ide_helper_models.php', 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | PhpStorm meta filename 30 | |-------------------------------------------------------------------------- 31 | | 32 | | PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary 33 | | files in it, should you need additional files for your project; e.g. 34 | | `.phpstorm.meta.php/laravel_ide_Helper.php'. 35 | | 36 | */ 37 | 'meta_filename' => '.phpstorm.meta.php', 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Fluent helpers 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Set to true to generate commonly used Fluent methods. 45 | | 46 | */ 47 | 48 | 'include_fluent' => false, 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Factory builders 53 | |-------------------------------------------------------------------------- 54 | | 55 | | Set to true to generate factory generators for better factory() 56 | | method auto-completion. 57 | | 58 | | Deprecated for Laravel 8 or latest. 59 | | 60 | */ 61 | 62 | 'include_factory_builders' => false, 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Write model magic methods 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Set to false to disable write magic methods of model. 70 | | 71 | */ 72 | 73 | 'write_model_magic_where' => true, 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Write model external Eloquent builder methods 78 | |-------------------------------------------------------------------------- 79 | | 80 | | Set to false to disable write external Eloquent builder methods. 81 | | 82 | */ 83 | 84 | 'write_model_external_builder_methods' => true, 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Write model relation count properties 89 | |-------------------------------------------------------------------------- 90 | | 91 | | Set to false to disable writing of relation count properties to model DocBlocks. 92 | | 93 | */ 94 | 95 | 'write_model_relation_count_properties' => true, 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Write Eloquent model mixins 100 | |-------------------------------------------------------------------------- 101 | | 102 | | This will add the necessary DocBlock mixins to the model class 103 | | contained in the Laravel framework. This helps the IDE with 104 | | auto-completion. 105 | | 106 | | Please be aware that this setting changes a file within the /vendor directory. 107 | | 108 | */ 109 | 110 | 'write_eloquent_model_mixins' => false, 111 | 112 | /* 113 | |-------------------------------------------------------------------------- 114 | | Helper files to include 115 | |-------------------------------------------------------------------------- 116 | | 117 | | Include helper files. By default not included, but can be toggled with the 118 | | -- helpers (-H) option. Extra helper files can be included. 119 | | 120 | */ 121 | 122 | 'include_helpers' => false, 123 | 124 | 'helper_files' => [ 125 | base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', 126 | base_path() . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php', 127 | ], 128 | 129 | /* 130 | |-------------------------------------------------------------------------- 131 | | Model locations to include 132 | |-------------------------------------------------------------------------- 133 | | 134 | | Define in which directories the ide-helper:models command should look 135 | | for models. 136 | | 137 | | glob patterns are supported to easier reach models in sub-directories, 138 | | e.g. `app/Services/* /Models` (without the space). 139 | | 140 | */ 141 | 142 | 'model_locations' => [ 143 | 'app', 144 | ], 145 | 146 | /* 147 | |-------------------------------------------------------------------------- 148 | | Models to ignore 149 | |-------------------------------------------------------------------------- 150 | | 151 | | Define which models should be ignored. 152 | | 153 | */ 154 | 155 | 'ignored_models' => [ 156 | // App\MyModel::class, 157 | ], 158 | 159 | /* 160 | |-------------------------------------------------------------------------- 161 | | Models hooks 162 | |-------------------------------------------------------------------------- 163 | | 164 | | Define which hook classes you want to run for models to add custom information. 165 | | 166 | | Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface. 167 | | 168 | */ 169 | 170 | 'model_hooks' => [ 171 | // App\Support\IdeHelper\MyModelHook::class 172 | ], 173 | 174 | /* 175 | |-------------------------------------------------------------------------- 176 | | Extra classes 177 | |-------------------------------------------------------------------------- 178 | | 179 | | These implementations are not really extended, but called with magic functions. 180 | | 181 | */ 182 | 183 | 'extra' => [ 184 | 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], 185 | 'Session' => ['Illuminate\Session\Store'], 186 | ], 187 | 188 | 'magic' => [], 189 | 190 | /* 191 | |-------------------------------------------------------------------------- 192 | | Interface implementations 193 | |-------------------------------------------------------------------------- 194 | | 195 | | These interfaces will be replaced with the implementing class. Some interfaces 196 | | are detected by the helpers, others can be listed below. 197 | | 198 | */ 199 | 200 | 'interfaces' => [ 201 | // App\MyInterface::class => App\MyImplementation::class, 202 | ], 203 | 204 | /* 205 | |-------------------------------------------------------------------------- 206 | | Support for camel cased models 207 | |-------------------------------------------------------------------------- 208 | | 209 | | There are some Laravel packages (such as Eloquence) that allow for accessing 210 | | Eloquent model properties via camel case, instead of snake case. 211 | | 212 | | Enabling this option will support these packages by saving all model 213 | | properties as camel case, instead of snake case. 214 | | 215 | | For example, normally you would see this: 216 | | 217 | | * @property \Illuminate\Support\Carbon $created_at 218 | | * @property \Illuminate\Support\Carbon $updated_at 219 | | 220 | | With this enabled, the properties will be this: 221 | | 222 | | * @property \Illuminate\Support\Carbon $createdAt 223 | | * @property \Illuminate\Support\Carbon $updatedAt 224 | | 225 | | Note, it is currently an all-or-nothing option. 226 | | 227 | */ 228 | 'model_camel_case_properties' => false, 229 | 230 | /* 231 | |-------------------------------------------------------------------------- 232 | | Property casts 233 | |-------------------------------------------------------------------------- 234 | | 235 | | Cast the given "real type" to the given "type". 236 | | 237 | */ 238 | 'type_overrides' => [ 239 | 'integer' => 'int', 240 | 'boolean' => 'bool', 241 | ], 242 | 243 | /* 244 | |-------------------------------------------------------------------------- 245 | | Include DocBlocks from classes 246 | |-------------------------------------------------------------------------- 247 | | 248 | | Include DocBlocks from classes to allow additional code inspection for 249 | | magic methods and properties. 250 | | 251 | */ 252 | 'include_class_docblocks' => false, 253 | 254 | /* 255 | |-------------------------------------------------------------------------- 256 | | Force FQN usage 257 | |-------------------------------------------------------------------------- 258 | | 259 | | Use the fully qualified (class) name in DocBlocks, 260 | | even if the class exists in the same namespace 261 | | or there is an import (use className) of the class. 262 | | 263 | */ 264 | 'force_fqn' => false, 265 | 266 | /* 267 | |-------------------------------------------------------------------------- 268 | | Use generics syntax 269 | |-------------------------------------------------------------------------- 270 | | 271 | | Use generics syntax within DocBlocks, 272 | | e.g. `Collection` instead of `Collection|User[]`. 273 | | 274 | */ 275 | 'use_generics_annotations' => true, 276 | 277 | /* 278 | |-------------------------------------------------------------------------- 279 | | Default return types for macros 280 | |-------------------------------------------------------------------------- 281 | | 282 | | Define default return types for macros without explicit return types. 283 | | e.g. `\Illuminate\Database\Query\Builder::class => 'static'`, 284 | | `\Illuminate\Support\Str::class => 'string'` 285 | | 286 | */ 287 | 'macro_default_return_types' => [ 288 | Illuminate\Http\Client\Factory::class => Illuminate\Http\Client\PendingRequest::class, 289 | ], 290 | 291 | /* 292 | |-------------------------------------------------------------------------- 293 | | Additional relation types 294 | |-------------------------------------------------------------------------- 295 | | 296 | | Sometimes it's needed to create custom relation types. The key of the array 297 | | is the relationship method name. The value of the array is the fully-qualified 298 | | class name of the relationship, e.g. `'relationName' => RelationShipClass::class`. 299 | | 300 | */ 301 | 'additional_relation_types' => [], 302 | 303 | /* 304 | |-------------------------------------------------------------------------- 305 | | Additional relation return types 306 | |-------------------------------------------------------------------------- 307 | | 308 | | When using custom relation types its possible for the class name to not contain 309 | | the proper return type of the relation. The key of the array is the relationship 310 | | method name. The value of the array is the return type of the relation ('many' 311 | | or 'morphTo'). 312 | | e.g. `'relationName' => 'many'`. 313 | | 314 | */ 315 | 'additional_relation_return_types' => [], 316 | 317 | /* 318 | |-------------------------------------------------------------------------- 319 | | Enforce nullable Eloquent relationships on not null columns 320 | |-------------------------------------------------------------------------- 321 | | 322 | | When set to true (default), this option enforces nullable Eloquent relationships. 323 | | However, in cases where the application logic ensures the presence of related 324 | | records it may be desirable to set this option to false to avoid unwanted null warnings. 325 | | 326 | | Default: true 327 | | A not null column with no foreign key constraint will have a "nullable" relationship. 328 | | * @property int $not_null_column_with_no_foreign_key_constraint 329 | | * @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint 330 | | 331 | | Option: false 332 | | A not null column with no foreign key constraint will have a "not nullable" relationship. 333 | | * @property int $not_null_column_with_no_foreign_key_constraint 334 | | * @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint 335 | | 336 | */ 337 | 338 | 'enforce_nullable_relationships' => true, 339 | 340 | /* 341 | |-------------------------------------------------------------------------- 342 | | Run artisan commands after migrations to generate model helpers 343 | |-------------------------------------------------------------------------- 344 | | 345 | | The specified commands should run after migrations are finished running. 346 | | 347 | */ 348 | 'post_migrate' => [ 349 | // 'ide-helper:models --nowrite', 350 | ], 351 | 352 | ]; 353 | -------------------------------------------------------------------------------- /php-templates/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Taylor Otwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /php-templates/README.md: -------------------------------------------------------------------------------- 1 | The templates here are based on the official VS Code extension by Laravel https://github.com/laravel/vs-code-extension 2 | 3 | Modifications: 4 | - return instead of echo 5 | - do not serialize to JSON 6 | -------------------------------------------------------------------------------- /php-templates/auth.php: -------------------------------------------------------------------------------- 1 | map(function ($policy, $key) { 5 | $reflection = new ReflectionFunction($policy); 6 | 7 | $policyClass = null; 8 | 9 | $closureThis = $reflection->getClosureThis(); 10 | if ($closureThis && get_class($closureThis) === Illuminate\Auth\Access\Gate::class) { 11 | $vars = $reflection->getClosureUsedVariables(); 12 | 13 | if (isset($vars['callback'])) { 14 | [$policyClass, $method] = explode('@', $vars['callback']); 15 | 16 | $reflection = new ReflectionMethod($policyClass, $method); 17 | } 18 | } 19 | return [ 20 | 'key' => $key, 21 | 'uri' => $reflection->getFileName(), 22 | 'policy_class' => $policyClass, 23 | 'lineNumber' => $reflection->getStartLine(), 24 | ]; 25 | }) 26 | ->merge( 27 | collect(Illuminate\Support\Facades\Gate::policies())->flatMap(function ($policy, $model) { 28 | $methods = (new ReflectionClass($policy))->getMethods(); 29 | 30 | return collect($methods)->map(function (ReflectionMethod $method) use ($policy) { 31 | return [ 32 | 'key' => $method->getName(), 33 | 'uri' => $method->getFileName(), 34 | 'policy_class' => $policy, 35 | 'lineNumber' => $method->getStartLine(), 36 | ]; 37 | })->filter(function ($ability) { 38 | return !in_array($ability['key'], ['allow', 'deny']); 39 | }); 40 | }), 41 | ) 42 | ->values() 43 | ->groupBy('key'); 44 | -------------------------------------------------------------------------------- /php-templates/configs.php: -------------------------------------------------------------------------------- 1 | merge(glob(config_path('**/*.php'))) 5 | ->map(fn ($path) => [ 6 | (string) Illuminate\Support\Str::of($path) 7 | ->replace([config_path('/'), '.php'], '') 8 | ->replace('/', '.'), 9 | $path, 10 | ]); 11 | 12 | $vendor = collect(glob(base_path('vendor/**/**/config/*.php')))->map(fn ( 13 | $path 14 | ) => [ 15 | (string) Illuminate\Support\Str::of($path) 16 | ->afterLast('/config/') 17 | ->replace('.php', '') 18 | ->replace('/', '.'), 19 | $path, 20 | ]); 21 | 22 | $configPaths = $local 23 | ->merge($vendor) 24 | ->groupBy(0) 25 | ->map(fn ($items) => $items->pluck(1)); 26 | 27 | $cachedContents = []; 28 | $cachedParsed = []; 29 | 30 | function vsCodeGetConfigValue($value, $key, $configPaths) 31 | { 32 | $parts = explode('.', $key); 33 | $toFind = $key; 34 | $found = null; 35 | 36 | while (count($parts) > 0) { 37 | array_pop($parts); 38 | $toFind = implode('.', $parts); 39 | 40 | if ($configPaths->has($toFind)) { 41 | $found = $toFind; 42 | break; 43 | } 44 | } 45 | 46 | if ($found === null) { 47 | return null; 48 | } 49 | 50 | $file = null; 51 | $line = null; 52 | 53 | if ($found === $key) { 54 | $file = $configPaths->get($found)[0]; 55 | } else { 56 | foreach ($configPaths->get($found) as $path) { 57 | $cachedContents[$path] ??= file_get_contents($path); 58 | $cachedParsed[$path] ??= token_get_all($cachedContents[$path]); 59 | 60 | $keysToFind = Illuminate\Support\Str::of($key) 61 | ->replaceFirst($found, '') 62 | ->ltrim('.') 63 | ->explode('.'); 64 | 65 | if (is_numeric($keysToFind->last())) { 66 | $index = $keysToFind->pop(); 67 | 68 | if ($index !== '0') { 69 | return null; 70 | } 71 | 72 | $key = collect(explode('.', $key)); 73 | $key->pop(); 74 | $key = $key->implode('.'); 75 | $value = []; 76 | } 77 | 78 | $nextKey = $keysToFind->shift(); 79 | $expectedDepth = 1; 80 | 81 | $depth = 0; 82 | 83 | foreach ($cachedParsed[$path] as $token) { 84 | if ($token === '[') { 85 | $depth++; 86 | } 87 | 88 | if ($token === ']') { 89 | $depth--; 90 | } 91 | 92 | if (!is_array($token)) { 93 | continue; 94 | } 95 | 96 | $str = trim($token[1], '"\''); 97 | 98 | if ( 99 | $str === $nextKey && 100 | $depth === $expectedDepth && 101 | $token[0] === T_CONSTANT_ENCAPSED_STRING 102 | ) { 103 | $nextKey = $keysToFind->shift(); 104 | $expectedDepth++; 105 | 106 | if ($nextKey === null) { 107 | $file = $path; 108 | $line = $token[2]; 109 | break; 110 | } 111 | } 112 | } 113 | 114 | if ($file) { 115 | break; 116 | } 117 | } 118 | } 119 | 120 | return [ 121 | 'name' => $key, 122 | 'value' => $value, 123 | 'file' => $file === null ? null : str_replace(base_path('/'), '', $file), 124 | 'line' => $line, 125 | ]; 126 | } 127 | 128 | return collect(Illuminate\Support\Arr::dot(config()->all())) 129 | ->map(fn ($value, $key) => vsCodeGetConfigValue($value, $key, $configPaths)) 130 | ->filter() 131 | ->values(); 132 | -------------------------------------------------------------------------------- /php-templates/middleware.php: -------------------------------------------------------------------------------- 1 | getMiddlewareGroups()) 4 | ->merge(app("Illuminate\Contracts\Http\Kernel")->getRouteMiddleware()) 5 | ->map(function ($middleware, $key) { 6 | $result = [ 7 | 'class' => null, 8 | 'uri' => null, 9 | 'startLine' => null, 10 | 'parameters' => null, 11 | 'groups' => [], 12 | ]; 13 | 14 | if (is_array($middleware)) { 15 | $result['groups'] = collect($middleware)->map(function ($m) { 16 | if (!class_exists($m)) { 17 | return [ 18 | 'class' => $m, 19 | 'uri' => null, 20 | 'startLine' => null, 21 | ]; 22 | } 23 | 24 | $reflected = new ReflectionClass($m); 25 | $reflectedMethod = $reflected->getMethod('handle'); 26 | 27 | return [ 28 | 'class' => $m, 29 | 'uri' => $reflected->getFileName(), 30 | 'startLine' => 31 | $reflectedMethod->getFileName() === $reflected->getFileName() 32 | ? $reflectedMethod->getStartLine() 33 | : null, 34 | ]; 35 | })->all(); 36 | 37 | return $result; 38 | } 39 | 40 | $reflected = new ReflectionClass($middleware); 41 | $reflectedMethod = $reflected->getMethod('handle'); 42 | 43 | $result = array_merge($result, [ 44 | 'class' => $middleware, 45 | 'uri' => $reflected->getFileName(), 46 | 'startLine' => $reflectedMethod->getStartLine(), 47 | ]); 48 | 49 | $parameters = collect($reflectedMethod->getParameters()) 50 | ->filter(function ($rc) { 51 | return $rc->getName() !== 'request' && $rc->getName() !== 'next'; 52 | }) 53 | ->map(function ($rc) { 54 | return $rc->getName() . ($rc->isVariadic() ? '...' : ''); 55 | }); 56 | 57 | if ($parameters->isEmpty()) { 58 | return $result; 59 | } 60 | 61 | return array_merge($result, [ 62 | 'parameters' => $parameters->implode(','), 63 | ]); 64 | }); 65 | -------------------------------------------------------------------------------- /php-templates/routes.php: -------------------------------------------------------------------------------- 1 | getActionName() === 'Closure') { 6 | return new ReflectionFunction($route->getAction()['uses']); 7 | } 8 | 9 | if (!str_contains($route->getActionName(), '@')) { 10 | return new ReflectionClass($route->getActionName()); 11 | } 12 | 13 | try { 14 | return new ReflectionMethod($route->getControllerClass(), $route->getActionMethod()); 15 | } catch (Throwable $e) { 16 | $namespace = app(Illuminate\Routing\UrlGenerator::class)->getRootControllerNamespace() 17 | ?? (app()->getNamespace() . 'Http\Controllers'); 18 | 19 | return new ReflectionMethod( 20 | $namespace . '\\' . ltrim($route->getControllerClass(), '\\'), 21 | $route->getActionMethod(), 22 | ); 23 | } 24 | } 25 | 26 | return collect(app('router')->getRoutes()->getRoutes()) 27 | ->map(function (Illuminate\Routing\Route $route) { 28 | try { 29 | $reflection = vsCodeGetRouterReflection($route); 30 | } catch (Throwable $e) { 31 | $reflection = null; 32 | } 33 | 34 | return [ 35 | 'method' => collect($route->methods())->filter(function ($method) { 36 | return $method !== 'HEAD'; 37 | })->implode('|'), 38 | 'uri' => $route->uri(), 39 | 'name' => $route->getName(), 40 | 'action' => $route->getActionName(), 41 | 'parameters' => $route->parameterNames(), 42 | 'filename' => $reflection ? $reflection->getFileName() : null, 43 | 'line' => $reflection ? $reflection->getStartLine() : null, 44 | ]; 45 | }) 46 | ; 47 | -------------------------------------------------------------------------------- /php-templates/translations.php: -------------------------------------------------------------------------------- 1 | filter() 13 | ->first(); 14 | 15 | $fileLines = Illuminate\Support\Facades\File::lines($file); 16 | $lines = []; 17 | $inComment = false; 18 | 19 | foreach ($fileLines as $index => $line) { 20 | $trimmed = trim($line); 21 | 22 | if (substr($trimmed, 0, 2) === '/*') { 23 | $inComment = true; 24 | continue; 25 | } 26 | 27 | if ($inComment) { 28 | if (substr($trimmed, -2) !== '*/') { 29 | continue; 30 | } 31 | 32 | $inComment = false; 33 | } 34 | 35 | if (substr($trimmed, 0, 2) === '//') { 36 | continue; 37 | } 38 | 39 | $lines[] = [$index + 1, $trimmed]; 40 | } 41 | 42 | return [ 43 | 'k' => $key, 44 | 'la' => $lang, 45 | 'vs' => collect(Illuminate\Support\Arr::dot((Illuminate\Support\Arr::wrap(__($key, [], $lang))))) 46 | ->map( 47 | fn ($value, $key) => vsCodeTranslationValue( 48 | $key, 49 | $value, 50 | str_replace(base_path(DIRECTORY_SEPARATOR), '', $file), 51 | $lines 52 | ) 53 | ) 54 | ->filter(), 55 | ]; 56 | } 57 | 58 | function vsCodeTranslationValue($key, $value, $file, $lines): ?array 59 | { 60 | if (is_array($value)) { 61 | return null; 62 | } 63 | 64 | $lineNumber = 1; 65 | $keys = explode('.', $key); 66 | $index = 0; 67 | $currentKey = array_shift($keys); 68 | 69 | foreach ($lines as $index => $line) { 70 | if ( 71 | strpos($line[1], '"' . $currentKey . '"', 0) !== false || 72 | strpos($line[1], "'" . $currentKey . "'", 0) !== false 73 | ) { 74 | $lineNumber = $line[0]; 75 | $currentKey = array_shift($keys); 76 | } 77 | 78 | if ($currentKey === null) { 79 | break; 80 | } 81 | } 82 | 83 | return [ 84 | 'v' => $value, 85 | 'p' => $file, 86 | 'li' => $lineNumber, 87 | 'pa' => preg_match_all("/\:([A-Za-z0-9_]+)/", $value, $matches) 88 | ? $matches[1] 89 | : [], 90 | ]; 91 | } 92 | 93 | function vscodeCollectTranslations(string $path, ?string $namespace = null) 94 | { 95 | $realPath = realpath($path); 96 | 97 | if (!is_dir($realPath)) { 98 | return collect(); 99 | } 100 | 101 | return collect(Illuminate\Support\Facades\File::allFiles($realPath))->map( 102 | fn ($file) => vsCodeGetTranslationsFromFile($file, $path, $namespace) 103 | ); 104 | } 105 | 106 | $loader = app('translator')->getLoader(); 107 | $namespaces = $loader->namespaces(); 108 | 109 | $reflection = new ReflectionClass($loader); 110 | $property = $reflection->hasProperty('paths') 111 | ? $reflection->getProperty('paths') 112 | : $reflection->getProperty('path'); 113 | $property->setAccessible(true); 114 | 115 | $paths = Illuminate\Support\Arr::wrap($property->getValue($loader)); 116 | 117 | $default = collect($paths)->flatMap( 118 | fn ($path) => vscodeCollectTranslations($path) 119 | ); 120 | 121 | $namespaced = collect($namespaces)->flatMap( 122 | fn ($path, $namespace) => vscodeCollectTranslations($path, $namespace) 123 | ); 124 | 125 | $final = []; 126 | 127 | foreach ($default->merge($namespaced) as $value) { 128 | foreach ($value['vs'] as $key => $v) { 129 | $dotKey = "{$value['k']}.{$key}"; 130 | 131 | if (!isset($final[$dotKey])) { 132 | $final[$dotKey] = []; 133 | } 134 | 135 | $final[$dotKey][$value['la']] = $v; 136 | 137 | if ($value['la'] === Illuminate\Support\Facades\App::currentLocale()) { 138 | $final[$dotKey]['default'] = $v; 139 | } 140 | } 141 | } 142 | 143 | return collect($final); 144 | -------------------------------------------------------------------------------- /php-templates/views.php: -------------------------------------------------------------------------------- 1 | files() 14 | ->name('*.blade.php') 15 | ->in($path) as $file 16 | ) { 17 | $paths[] = [ 18 | 'path' => str_replace(base_path(DIRECTORY_SEPARATOR), '', $file->getRealPath()), 19 | 'isVendor' => str_contains($file->getRealPath(), base_path('vendor')), 20 | 'key' => Illuminate\Support\Str::of($file->getRealPath()) 21 | ->replace(realpath($path), '') 22 | ->replace('.blade.php', '') 23 | ->ltrim(DIRECTORY_SEPARATOR) 24 | ->replace(DIRECTORY_SEPARATOR, '.'), 25 | ]; 26 | } 27 | 28 | return $paths; 29 | } 30 | $paths = collect( 31 | app('view') 32 | ->getFinder() 33 | ->getPaths() 34 | )->flatMap(function ($path) { 35 | return vsCodeFindBladeFiles($path); 36 | }); 37 | 38 | $hints = collect( 39 | app('view') 40 | ->getFinder() 41 | ->getHints() 42 | )->flatMap(function ($paths, $key) { 43 | return collect($paths)->flatMap(function ($path) use ($key) { 44 | return collect(vsCodeFindBladeFiles($path))->map(function ($value) use ( 45 | $key 46 | ) { 47 | return array_merge($value, ['key' => "{$key}::{$value['key']}"]); 48 | }); 49 | }); 50 | }); 51 | 52 | [$local, $vendor] = $paths 53 | ->merge($hints) 54 | ->values() 55 | ->partition(function ($v) { 56 | return !$v['isVendor']; 57 | }); 58 | 59 | return $local 60 | ->sortBy('key', SORT_NATURAL) 61 | ->merge($vendor->sortBy('key', SORT_NATURAL)); 62 | -------------------------------------------------------------------------------- /resources/views/helper.php: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | /* @noinspection ALL */ 12 | // @formatter:off 13 | // phpcs:ignoreFile 14 | 15 | /** 16 | * A helper file for Laravel, to provide autocomplete information to your IDE 17 | * Generated for Laravel version() ?>. 18 | * 19 | * This file should not be included in your code, only analyzed by your IDE! 20 | * 21 | * @author Barry vd. Heuvel 22 | * @see https://github.com/barryvdh/laravel-ide-helper 23 | */ 24 | 29 | $aliases) : ?> 30 | namespace { 31 | 32 | getDocComment($s1)) . "\n{$s1}" . $alias->getClassType() ?> getExtendsClass() ?>shouldExtendParentClass()): ?> extends getParentClass() ?> { 33 | getMethods() as $method) : ?> 34 | getDocComment($s2)) . "\n{$s2}" ?>public static function getName() ?>(getParamsWithDefault() ?>) 35 | {getDeclaringClass() !== $method->getRoot()) : ?> 36 | //Method inherited from getDeclaringClass() ?> 37 | 38 | 39 | isInstanceCall()) : ?> 40 | /** @var getRoot()?> $instance */ 41 | 42 | shouldReturn() ? 'return ' : '') ?>getRootMethodCall() ?>; 43 | } 44 | 45 | 46 | } 47 | 48 | } 49 | 50 | 51 | 52 | $aliases) : ?> 53 | namespace { 54 | 55 | getExtendsNamespace() === '\Illuminate\Database\Eloquent') : ?> 56 | getPhpDocTemplates($s1) . "\n" ?> 57 | 58 | getClassType() ?> getShortName() ?> extends getExtends() ?> {getExtendsNamespace() === '\Illuminate\Database\Eloquent') : ?> 59 | getMethods() as $method) : ?> 60 | getDocComment($s2)) . "\n" ?> 61 | public static function getName() ?>(getParamsWithDefault() ?>) 62 | {getDeclaringClass() !== $method->getRoot()) : ?> 63 | //Method inherited from getDeclaringClass() ?> 64 | 65 | 66 | isInstanceCall()) : ?> 67 | /** @var getRoot()?> $instance */ 68 | 69 | shouldReturn() ? 'return ' : '') ?>getRootMethodCall() ?>; 70 | } 71 | 72 | 73 | } 74 | 75 | } 76 | 77 | 78 | 79 | 80 | 81 | namespace { 82 | /** 83 | * @mixin 84 | */ 85 | class extends {} 86 | } 87 | 88 | 89 | 90 | namespace { 91 | 92 | } 93 | 94 | 95 | 96 | namespace Illuminate\Support { 97 | /** 98 | * Methods commonly used in migrations 99 | * 100 | * @method Fluent after(string $column) Add the after modifier 101 | * @method Fluent charset(string $charset) Add the character set modifier 102 | * @method Fluent collation(string $collation) Add the collation modifier 103 | * @method Fluent comment(string $comment) Add comment 104 | * @method Fluent default($value) Add the default modifier 105 | * @method Fluent first() Select first row 106 | * @method Fluent index(string $name = null) Add the in dex clause 107 | * @method Fluent on(string $table) `on` of a foreign key 108 | * @method Fluent onDelete(string $action) `on delete` of a foreign key 109 | * @method Fluent onUpdate(string $action) `on update` of a foreign key 110 | * @method Fluent primary() Add the primary key modifier 111 | * @method Fluent references(string $column) `references` of a foreign key 112 | * @method Fluent nullable(bool $value = true) Add the nullable modifier 113 | * @method Fluent unique(string $name = null) Add unique index clause 114 | * @method Fluent unsigned() Add the unsigned modifier 115 | * @method Fluent useCurrent() Add the default timestamp value 116 | * @method Fluent change() Add the change modifier 117 | */ 118 | class Fluent {} 119 | } 120 | 121 | 122 | 123 | namespace getNamespaceName()?> { 124 | /** 125 | * @method \Illuminate\Database\Eloquent\Collection|getShortName()?>[]|getShortName()?> create($attributes = []) 126 | * @method \Illuminate\Database\Eloquent\Collection|getShortName()?>[]|getShortName()?> make($attributes = []) 127 | */ 128 | class getShortName()?>FactoryBuilder extends \Illuminate\Database\Eloquent\FactoryBuilder {} 129 | } 130 | 131 | -------------------------------------------------------------------------------- /resources/views/meta.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* @noinspection ALL */ 4 | // @formatter:off 5 | // phpcs:ignoreFile 6 | 7 | namespace PHPSTORM_META { 8 | 9 | /** 10 | * PhpStorm Meta file, to provide autocomplete information for PhpStorm 11 | * 12 | * @author Barry vd. Heuvel 13 | * @see https://github.com/barryvdh/laravel-ide-helper 14 | */ 15 | 16 | override(, map([ 17 | '' => '@', 18 | $class) : ?> 19 | '' => \::class, 20 | 21 | ])); 22 | 23 | 24 | 25 | override(, map([ 26 | '' => \::class, 27 | ])); 28 | 29 | 30 | 31 | override(, map([ 32 | $value) : ?> 33 | '' => '', 34 | 35 | ])); 36 | 37 | 38 | 39 | override(\factory(0), map([ 40 | '' => '@FactoryBuilder', 41 | 42 | 'getName() ?>' => \getName() ?>FactoryBuilder::class, 43 | 44 | ])); 45 | 46 | 47 | override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::mock(0), map(["" => "@&\Mockery\MockInterface"])); 48 | override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::partialMock(0), map(["" => "@&\Mockery\MockInterface"])); 49 | override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::instance(0), type(1)); 50 | override(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::spy(0), map(["" => "@&\Mockery\MockInterface"])); 51 | override(\Illuminate\Support\Arr::add(0), type(0)); 52 | override(\Illuminate\Support\Arr::except(0), type(0)); 53 | override(\Illuminate\Support\Arr::first(0), elementType(0)); 54 | override(\Illuminate\Support\Arr::last(0), elementType(0)); 55 | override(\Illuminate\Support\Arr::get(0), elementType(0)); 56 | override(\Illuminate\Support\Arr::only(0), type(0)); 57 | override(\Illuminate\Support\Arr::prepend(0), type(0)); 58 | override(\Illuminate\Support\Arr::pull(0), elementType(0)); 59 | override(\Illuminate\Support\Arr::set(0), type(0)); 60 | override(\Illuminate\Support\Arr::shuffle(0), type(0)); 61 | override(\Illuminate\Support\Arr::sort(0), type(0)); 62 | override(\Illuminate\Support\Arr::sortRecursive(0), type(0)); 63 | override(\Illuminate\Support\Arr::where(0), type(0)); 64 | override(\array_add(0), type(0)); 65 | override(\array_except(0), type(0)); 66 | override(\array_first(0), elementType(0)); 67 | override(\array_last(0), elementType(0)); 68 | override(\array_get(0), elementType(0)); 69 | override(\array_only(0), type(0)); 70 | override(\array_prepend(0), type(0)); 71 | override(\array_pull(0), elementType(0)); 72 | override(\array_set(0), type(0)); 73 | override(\array_sort(0), type(0)); 74 | override(\array_sort_recursive(0), type(0)); 75 | override(\array_where(0), type(0)); 76 | override(\head(0), elementType(0)); 77 | override(\last(0), elementType(0)); 78 | override(\with(0), type(0)); 79 | override(\tap(0), type(0)); 80 | override(\optional(0), type(0)); 81 | 82 | 83 | $argumentsList) : ?> 84 | registerArgumentsSet('', $arg) : ?>,); 87 | 88 | 89 | 90 | 91 | 92 | 103 | 104 | expectedArguments(, , argumentsSet('')); 105 | 106 | 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/Alias.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) 8 | * @license http://www.opensource.org/licenses/mit-license.php MIT 9 | * @link https://github.com/barryvdh/laravel-ide-helper 10 | */ 11 | 12 | namespace Barryvdh\LaravelIdeHelper; 13 | 14 | use Barryvdh\Reflection\DocBlock; 15 | use Barryvdh\Reflection\DocBlock\Context; 16 | use Barryvdh\Reflection\DocBlock\ContextFactory; 17 | use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; 18 | use Barryvdh\Reflection\DocBlock\Tag\MethodTag; 19 | use Barryvdh\Reflection\DocBlock\Tag\TemplateTag; 20 | use Closure; 21 | use Illuminate\Config\Repository as ConfigRepository; 22 | use Illuminate\Database\Eloquent\Builder as EloquentBuilder; 23 | use Illuminate\Database\Query\Builder as QueryBuilder; 24 | use Illuminate\Support\Facades\Facade; 25 | use Illuminate\Support\Traits\Macroable; 26 | use ReflectionClass; 27 | use Throwable; 28 | 29 | class Alias 30 | { 31 | protected $alias; 32 | /** @psalm-var class-string $facade */ 33 | protected $facade; 34 | protected $extends = null; 35 | protected $extendsClass = null; 36 | protected $extendsNamespace = null; 37 | protected $classType = 'class'; 38 | protected $short; 39 | protected $namespace = '__root'; 40 | protected $parentClass; 41 | protected $root = null; 42 | protected $classes = []; 43 | protected $methods = []; 44 | protected $usedMethods = []; 45 | protected $valid = false; 46 | protected $magicMethods = []; 47 | protected $interfaces = []; 48 | protected $phpdoc = null; 49 | protected $classAliases = []; 50 | 51 | /** @var ConfigRepository */ 52 | protected $config; 53 | 54 | /** @var string[] */ 55 | protected $templateNames; 56 | 57 | /** 58 | * @param ConfigRepository $config 59 | * @param string $alias 60 | * @psalm-param class-string $facade 61 | * @param string $facade 62 | * @param array $magicMethods 63 | * @param array $interfaces 64 | */ 65 | public function __construct($config, $alias, $facade, $magicMethods = [], $interfaces = []) 66 | { 67 | $this->alias = $alias; 68 | $this->magicMethods = $magicMethods; 69 | $this->interfaces = $interfaces; 70 | $this->config = $config; 71 | 72 | // Make the class absolute 73 | $facade = '\\' . ltrim($facade, '\\'); 74 | $this->facade = $facade; 75 | 76 | $this->detectRoot(); 77 | 78 | if (!$this->root || $this->isTrait()) { 79 | return; 80 | } 81 | 82 | $this->valid = true; 83 | 84 | $this->addClass($this->root); 85 | $this->detectFake(); 86 | $this->detectNamespace(); 87 | $this->detectClassType(); 88 | $this->detectExtendsNamespace(); 89 | $this->detectParentClass(); 90 | 91 | if (!empty($this->namespace)) { 92 | try { 93 | $this->classAliases = (new ContextFactory())->createFromReflector(new ReflectionClass($this->root))->getNamespaceAliases(); 94 | } catch (Throwable $e) { 95 | $this->classAliases = []; 96 | } 97 | 98 | 99 | //Create a DocBlock and serializer instance 100 | $this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace, $this->classAliases)); 101 | } 102 | 103 | if ($facade === '\Illuminate\Database\Eloquent\Model') { 104 | $this->usedMethods = ['decrement', 'increment']; 105 | } 106 | } 107 | 108 | /** 109 | * Add one or more classes to analyze 110 | * 111 | * @param array|string $classes 112 | */ 113 | public function addClass($classes) 114 | { 115 | $classes = (array)$classes; 116 | foreach ($classes as $class) { 117 | if (class_exists($class) || interface_exists($class)) { 118 | $this->classes[] = $class; 119 | } else { 120 | echo "Class not exists: $class\r\n"; 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * Check if this class is valid to process. 127 | * @return bool 128 | */ 129 | public function isValid() 130 | { 131 | return $this->valid; 132 | } 133 | 134 | /** 135 | * Get the classtype, 'interface' or 'class' 136 | * 137 | * @return string 138 | */ 139 | public function getClasstype() 140 | { 141 | return $this->classType; 142 | } 143 | 144 | /** 145 | * Get the class which this alias extends 146 | * 147 | * @return null|string 148 | */ 149 | public function getExtends() 150 | { 151 | return $this->extends; 152 | } 153 | 154 | /** 155 | * Get the class short name which this alias extends 156 | * 157 | * @return null|string 158 | */ 159 | public function getExtendsClass() 160 | { 161 | return $this->extendsClass; 162 | } 163 | 164 | /** 165 | * Get the namespace of the class which this alias extends 166 | * 167 | * @return null|string 168 | */ 169 | public function getExtendsNamespace() 170 | { 171 | return $this->extendsNamespace; 172 | } 173 | 174 | /** 175 | * Get the parent class of the class which this alias extends 176 | * 177 | * @return null|string 178 | */ 179 | public function getParentClass() 180 | { 181 | return $this->parentClass; 182 | } 183 | 184 | /** 185 | * Check if this class should extend the parent class 186 | */ 187 | public function shouldExtendParentClass() 188 | { 189 | return $this->parentClass 190 | && $this->getExtendsNamespace() !== '\\Illuminate\\Support\\Facades'; 191 | } 192 | 193 | /** 194 | * Get the Alias by which this class is called 195 | * 196 | * @return string 197 | */ 198 | public function getAlias() 199 | { 200 | return $this->alias; 201 | } 202 | 203 | /** 204 | * Return the short name (without namespace) 205 | */ 206 | public function getShortName() 207 | { 208 | return $this->short; 209 | } 210 | /** 211 | * Get the namespace from the alias 212 | * 213 | * @return string 214 | */ 215 | public function getNamespace() 216 | { 217 | return $this->namespace; 218 | } 219 | 220 | /** 221 | * Get the methods found by this Alias 222 | * 223 | * @return array|Method[] 224 | */ 225 | public function getMethods() 226 | { 227 | if (count($this->methods) > 0) { 228 | return $this->methods; 229 | } 230 | 231 | $this->addMagicMethods(); 232 | $this->detectMethods(); 233 | return $this->methods; 234 | } 235 | 236 | /** 237 | * Detect class returned by ::fake() 238 | */ 239 | protected function detectFake() 240 | { 241 | $facade = $this->facade; 242 | 243 | if (!is_subclass_of($facade, Facade::class)) { 244 | return; 245 | } 246 | 247 | if (!method_exists($facade, 'fake')) { 248 | return; 249 | } 250 | 251 | $real = $facade::getFacadeRoot(); 252 | 253 | try { 254 | $facade::fake(); 255 | $fake = $facade::getFacadeRoot(); 256 | if ($fake !== $real) { 257 | $this->addClass(get_class($fake)); 258 | } 259 | } finally { 260 | $facade::swap($real); 261 | } 262 | } 263 | 264 | /** 265 | * Detect the namespace 266 | */ 267 | protected function detectNamespace() 268 | { 269 | if (strpos($this->alias, '\\')) { 270 | $nsParts = explode('\\', $this->alias); 271 | $this->short = array_pop($nsParts); 272 | $this->namespace = implode('\\', $nsParts); 273 | } else { 274 | $this->short = $this->alias; 275 | } 276 | } 277 | 278 | /** 279 | * Detect the extends namespace 280 | */ 281 | protected function detectExtendsNamespace() 282 | { 283 | if (strpos($this->extends, '\\') !== false) { 284 | $nsParts = explode('\\', $this->extends); 285 | $this->extendsClass = array_pop($nsParts); 286 | $this->extendsNamespace = implode('\\', $nsParts); 287 | } 288 | } 289 | 290 | /** 291 | * Detect the parent class 292 | */ 293 | protected function detectParentClass() 294 | { 295 | $reflection = new ReflectionClass($this->root); 296 | 297 | $parentClass = $reflection->getParentClass(); 298 | 299 | $this->parentClass = $parentClass ? '\\' . $parentClass->getName() : null; 300 | } 301 | 302 | /** 303 | * Detect the class type 304 | */ 305 | protected function detectClassType() 306 | { 307 | //Some classes extend the facade 308 | if (interface_exists($this->facade)) { 309 | $this->classType = 'interface'; 310 | $this->extends = $this->facade; 311 | } else { 312 | $this->classType = 'class'; 313 | if (class_exists($this->facade)) { 314 | $this->extends = $this->facade; 315 | } 316 | } 317 | } 318 | 319 | /** 320 | * Get the real root of a facade 321 | * 322 | * @return bool|string 323 | */ 324 | protected function detectRoot() 325 | { 326 | $facade = $this->facade; 327 | 328 | try { 329 | //If possible, get the facade root 330 | if (method_exists($facade, 'getFacadeRoot')) { 331 | $root = get_class($facade::getFacadeRoot()); 332 | } else { 333 | $root = $facade; 334 | } 335 | 336 | //If it doesn't exist, skip it 337 | if (!class_exists($root) && !interface_exists($root)) { 338 | return; 339 | } 340 | 341 | $this->root = $root; 342 | 343 | //When the database connection is not set, some classes will be skipped 344 | } catch (\PDOException $e) { 345 | $this->error( 346 | 'PDOException: ' . $e->getMessage() . 347 | "\nPlease configure your database connection correctly, or use the sqlite memory driver (-M)." . 348 | " Skipping $facade." 349 | ); 350 | } catch (Throwable $e) { 351 | $this->error('Exception: ' . $e->getMessage() . "\nSkipping $facade."); 352 | } 353 | } 354 | 355 | /** 356 | * Detect if this class is a trait or not. 357 | * 358 | * @return bool 359 | */ 360 | protected function isTrait() 361 | { 362 | // Check if the facade is not a Trait 363 | return trait_exists($this->facade); 364 | } 365 | 366 | /** 367 | * Add magic methods, as defined in the configuration files 368 | */ 369 | protected function addMagicMethods() 370 | { 371 | foreach ($this->magicMethods as $magic => $real) { 372 | [$className, $name] = explode('::', $real); 373 | if ((!class_exists($className) && !interface_exists($className)) || !method_exists($className, $name)) { 374 | continue; 375 | } 376 | $method = new \ReflectionMethod($className, $name); 377 | $class = new ReflectionClass($className); 378 | 379 | if (!in_array($magic, $this->usedMethods)) { 380 | if ($class !== $this->root) { 381 | $this->methods[] = new Method( 382 | $method, 383 | $this->alias, 384 | $class, 385 | $magic, 386 | $this->interfaces, 387 | $this->classAliases, 388 | $this->getReturnTypeNormalizers($class), 389 | $this->getTemplateNames() 390 | ); 391 | } 392 | $this->usedMethods[] = $magic; 393 | } 394 | } 395 | } 396 | 397 | /** 398 | * Get the methods for one or multiple classes. 399 | * 400 | * @return string 401 | */ 402 | protected function detectMethods() 403 | { 404 | foreach ($this->classes as $class) { 405 | $reflection = new ReflectionClass($class); 406 | 407 | $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); 408 | if ($methods) { 409 | foreach ($methods as $method) { 410 | if (!in_array($method->name, $this->usedMethods)) { 411 | // Only add the methods to the output when the root is not the same as the class. 412 | // And don't add the __*() methods 413 | if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') { 414 | $this->methods[] = new Method( 415 | $method, 416 | $this->alias, 417 | $reflection, 418 | $method->name, 419 | $this->interfaces, 420 | $this->classAliases, 421 | $this->getReturnTypeNormalizers($reflection), 422 | $this->getTemplateNames(), 423 | ); 424 | } 425 | $this->usedMethods[] = $method->name; 426 | } 427 | } 428 | } 429 | 430 | // Check if the class is macroable 431 | // (Eloquent\Builder is also macroable but doesn't use Macroable trait) 432 | if ($class === EloquentBuilder::class || in_array(Macroable::class, $reflection->getTraitNames())) { 433 | $properties = $reflection->getStaticProperties(); 434 | $macros = isset($properties['macros']) ? $properties['macros'] : []; 435 | foreach ($macros as $macro_name => $macro_func) { 436 | if (!in_array($macro_name, $this->usedMethods)) { 437 | try { 438 | $method = $this->getMacroFunction($macro_func); 439 | } catch (Throwable $e) { 440 | // Invalid method, skip 441 | continue; 442 | } 443 | // Add macros 444 | $this->methods[] = new Macro( 445 | $method, 446 | $this->alias, 447 | $reflection, 448 | $macro_name, 449 | $this->interfaces, 450 | $this->classAliases, 451 | $this->getReturnTypeNormalizers($reflection) 452 | ); 453 | $this->usedMethods[] = $macro_name; 454 | } 455 | } 456 | } 457 | } 458 | } 459 | 460 | /** 461 | * @param ReflectionClass $class 462 | * @return array 463 | */ 464 | protected function getReturnTypeNormalizers($class) 465 | { 466 | if ($this->alias === 'Eloquent' && in_array($class->getName(), [EloquentBuilder::class, QueryBuilder::class])) { 467 | return [ 468 | '$this' => '\\' . EloquentBuilder::class . ($this->config->get('ide-helper.use_generics_annotations') ? '' : '|static'), 469 | ]; 470 | } 471 | 472 | return []; 473 | } 474 | 475 | /** 476 | * @param $macro_func 477 | * 478 | * @return \ReflectionFunctionAbstract 479 | * @throws \ReflectionException 480 | */ 481 | protected function getMacroFunction($macro_func) 482 | { 483 | if (is_array($macro_func) && is_callable($macro_func)) { 484 | return new \ReflectionMethod($macro_func[0], $macro_func[1]); 485 | } 486 | 487 | if (is_object($macro_func) && is_callable($macro_func) && !$macro_func instanceof Closure) { 488 | return new \ReflectionMethod($macro_func, '__invoke'); 489 | } 490 | 491 | return new \ReflectionFunction($macro_func); 492 | } 493 | 494 | /* 495 | * Get the docblock for this alias 496 | * 497 | * @param string $prefix 498 | * @return mixed 499 | */ 500 | public function getDocComment($prefix = "\t\t") 501 | { 502 | $serializer = new DocBlockSerializer(1, $prefix); 503 | 504 | if (!$this->phpdoc) { 505 | return ''; 506 | } 507 | 508 | if ($this->config->get('ide-helper.include_class_docblocks')) { 509 | // if a class doesn't expose any DocBlock tags 510 | // we can perform reflection on the class and 511 | // add in the original class DocBlock 512 | if (count($this->phpdoc->getTags()) === 0) { 513 | $class = new ReflectionClass($this->root); 514 | $this->phpdoc = new DocBlock($class->getDocComment()); 515 | } 516 | } 517 | 518 | $this->removeDuplicateMethodsFromPhpDoc(); 519 | return $serializer->getDocComment($this->phpdoc); 520 | } 521 | 522 | /** 523 | * @param $prefix 524 | * @return string 525 | */ 526 | public function getPhpDocTemplates($prefix = "\t\t") 527 | { 528 | $templateDoc = new DocBlock(''); 529 | $serializer = new DocBlockSerializer(1, $prefix); 530 | 531 | foreach ($this->getTemplateNames() as $templateName) { 532 | $template = new TemplateTag('template', $templateName); 533 | $template->setBound('static'); 534 | $template->setDocBlock($templateDoc); 535 | $templateDoc->appendTag($template); 536 | } 537 | 538 | return $serializer->getDocComment($templateDoc); 539 | } 540 | 541 | /** 542 | * @return string[] 543 | */ 544 | public function getTemplateNames() 545 | { 546 | if (!isset($this->templateNames)) { 547 | $this->detectTemplateNames(); 548 | } 549 | return $this->templateNames; 550 | } 551 | 552 | /** 553 | * @return void 554 | * @throws \ReflectionException 555 | */ 556 | protected function detectTemplateNames() 557 | { 558 | $templateNames = []; 559 | foreach ($this->classes as $class) { 560 | $reflection = new ReflectionClass($class); 561 | $traits = collect($reflection->getTraitNames()); 562 | 563 | $phpdoc = new DocBlock($reflection); 564 | $templates = $phpdoc->getTagsByName('template'); 565 | /** @var TemplateTag $template */ 566 | foreach ($templates as $template) { 567 | $templateNames[] = $template->getTemplateName(); 568 | } 569 | 570 | foreach ($traits as $trait) { 571 | $phpdoc = new DocBlock(new ReflectionClass($trait)); 572 | $templates = $phpdoc->getTagsByName('template'); 573 | 574 | /** @var TemplateTag $template */ 575 | foreach ($templates as $template) { 576 | $templateNames[] = $template->getTemplateName(); 577 | } 578 | } 579 | } 580 | $this->templateNames = $templateNames; 581 | } 582 | 583 | /** 584 | * Removes method tags from the doc comment that already appear as functions inside the class. 585 | * This prevents duplicate function errors in the IDE. 586 | * 587 | * @return void 588 | */ 589 | protected function removeDuplicateMethodsFromPhpDoc() 590 | { 591 | $methodNames = array_map(function (Method $method) { 592 | return $method->getName(); 593 | }, $this->getMethods()); 594 | 595 | foreach ($this->phpdoc->getTags() as $tag) { 596 | if ($tag instanceof MethodTag && in_array($tag->getMethodName(), $methodNames)) { 597 | $this->phpdoc->deleteTag($tag); 598 | } 599 | } 600 | } 601 | 602 | /** 603 | * Output an error. 604 | * 605 | * @param string $string 606 | * @return void 607 | */ 608 | protected function error($string) 609 | { 610 | echo $string . "\r\n"; 611 | } 612 | } 613 | -------------------------------------------------------------------------------- /src/Console/EloquentCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Charles A. Peterson / Fruitcake Studio (http://www.fruitcakestudio.nl) 8 | * @license http://www.opensource.org/licenses/mit-license.php MIT 9 | * @link https://github.com/barryvdh/laravel-ide-helper 10 | */ 11 | 12 | namespace Barryvdh\LaravelIdeHelper\Console; 13 | 14 | use Barryvdh\LaravelIdeHelper\Eloquent; 15 | use Illuminate\Console\Command; 16 | use Illuminate\Filesystem\Filesystem; 17 | 18 | /** 19 | * A command to add \Eloquent mixin to Eloquent\Model 20 | * 21 | * @author Charles A. Peterson 22 | */ 23 | class EloquentCommand extends Command 24 | { 25 | /** 26 | * The console command name. 27 | * 28 | * @var string 29 | */ 30 | protected $name = 'ide-helper:eloquent'; 31 | 32 | /** 33 | * @var Filesystem $files 34 | */ 35 | protected $files; 36 | 37 | /** 38 | * The console command description. 39 | * 40 | * @var string 41 | */ 42 | protected $description = 'Add \Eloquent helper to \Eloquent\Model'; 43 | 44 | /** 45 | * @param Filesystem $files 46 | */ 47 | public function __construct(Filesystem $files) 48 | { 49 | parent::__construct(); 50 | $this->files = $files; 51 | } 52 | 53 | /** 54 | * Execute the console command. 55 | * 56 | * @return void 57 | */ 58 | public function handle() 59 | { 60 | Eloquent::writeEloquentModelHelper($this, $this->files); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Console/GeneratorCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) 8 | * @license http://www.opensource.org/licenses/mit-license.php MIT 9 | * @link https://github.com/barryvdh/laravel-ide-helper 10 | */ 11 | 12 | namespace Barryvdh\LaravelIdeHelper\Console; 13 | 14 | use Barryvdh\LaravelIdeHelper\Eloquent; 15 | use Barryvdh\LaravelIdeHelper\Generator; 16 | use Illuminate\Console\Command; 17 | use Illuminate\Contracts\Config\Repository; 18 | use Illuminate\Filesystem\Filesystem; 19 | use Illuminate\View\Factory; 20 | use Symfony\Component\Console\Input\InputArgument; 21 | use Symfony\Component\Console\Input\InputOption; 22 | 23 | /** 24 | * A command to generate autocomplete information for your IDE 25 | * 26 | * @author Barry vd. Heuvel 27 | */ 28 | class GeneratorCommand extends Command 29 | { 30 | /** 31 | * The console command name. 32 | * 33 | * @var string 34 | */ 35 | protected $name = 'ide-helper:generate'; 36 | 37 | /** 38 | * The console command description. 39 | * 40 | * @var string 41 | */ 42 | protected $description = 'Generate a new IDE Helper file.'; 43 | 44 | /** @var \Illuminate\Config\Repository */ 45 | protected $config; 46 | 47 | /** @var Filesystem */ 48 | protected $files; 49 | 50 | /** @var Factory */ 51 | protected $view; 52 | 53 | protected $onlyExtend; 54 | 55 | 56 | /** 57 | * 58 | * @param \Illuminate\Config\Repository $config 59 | * @param Filesystem $files 60 | * @param Factory $view 61 | */ 62 | public function __construct( 63 | Repository $config, 64 | Filesystem $files, 65 | Factory $view 66 | ) { 67 | $this->config = $config; 68 | $this->files = $files; 69 | $this->view = $view; 70 | parent::__construct(); 71 | } 72 | 73 | /** 74 | * Execute the console command. 75 | * 76 | * @return void 77 | */ 78 | public function handle() 79 | { 80 | if ( 81 | file_exists(base_path() . '/vendor/compiled.php') || 82 | file_exists(base_path() . '/bootstrap/cache/compiled.php') || 83 | file_exists(base_path() . '/storage/framework/compiled.php') 84 | ) { 85 | $this->error( 86 | 'Error generating IDE Helper: first delete your compiled file (php artisan clear-compiled)' 87 | ); 88 | return; 89 | } 90 | 91 | $filename = $this->argument('filename'); 92 | 93 | // Add the php extension if missing 94 | // This is a backwards-compatible shim and can be removed in the future 95 | if (substr($filename, -4, 4) !== '.php') { 96 | $filename .= '.php'; 97 | } 98 | 99 | if ($this->option('memory')) { 100 | $this->useMemoryDriver(); 101 | } 102 | 103 | 104 | $helpers = ''; 105 | if ($this->option('helpers') || ($this->config->get('ide-helper.include_helpers'))) { 106 | foreach ($this->config->get('ide-helper.helper_files', []) as $helper) { 107 | if (file_exists($helper)) { 108 | $helpers .= str_replace([''], '', $this->files->get($helper)); 109 | } 110 | } 111 | } else { 112 | $helpers = ''; 113 | } 114 | 115 | $generator = new Generator($this->config, $this->view, $this->getOutput(), $helpers); 116 | if ($this->option('eloquent')) { 117 | $content = $generator->generateEloquent(); 118 | } else { 119 | $content = $generator->generate(); 120 | } 121 | 122 | $written = $this->files->put($filename, $content); 123 | 124 | if ($written !== false) { 125 | $this->info("A new helper file was written to $filename"); 126 | 127 | if ($this->option('write_mixins')) { 128 | Eloquent::writeEloquentModelHelper($this, $this->files); 129 | } 130 | } else { 131 | $this->error("The helper file could not be created at $filename"); 132 | } 133 | } 134 | 135 | protected function useMemoryDriver() 136 | { 137 | //Use a sqlite database in memory, to avoid connection errors on Database facades 138 | $this->config->set( 139 | 'database.connections.sqlite', 140 | [ 141 | 'driver' => 'sqlite', 142 | 'database' => ':memory:', 143 | ] 144 | ); 145 | $this->config->set('database.default', 'sqlite'); 146 | } 147 | 148 | /** 149 | * Get the console command arguments. 150 | * 151 | * @return array 152 | */ 153 | protected function getArguments() 154 | { 155 | $filename = $this->config->get('ide-helper.filename'); 156 | 157 | return [ 158 | [ 159 | 'filename', InputArgument::OPTIONAL, 'The path to the helper file', $filename, 160 | ], 161 | ]; 162 | } 163 | 164 | /** 165 | * Get the console command options. 166 | * 167 | * @return array 168 | */ 169 | protected function getOptions() 170 | { 171 | $writeMixins = $this->config->get('ide-helper.write_eloquent_model_mixins'); 172 | 173 | return [ 174 | ['write_mixins', 'W', InputOption::VALUE_OPTIONAL, 'Write mixins to Laravel Model?', $writeMixins], 175 | ['helpers', 'H', InputOption::VALUE_NONE, 'Include the helper files'], 176 | ['memory', 'M', InputOption::VALUE_NONE, 'Use sqlite memory driver'], 177 | ['eloquent', 'E', InputOption::VALUE_NONE, 'Only write Eloquent methods'], 178 | ]; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Console/MetaCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2015 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) 8 | * @license http://www.opensource.org/licenses/mit-license.php MIT 9 | * @link https://github.com/barryvdh/laravel-ide-helper 10 | */ 11 | 12 | namespace Barryvdh\LaravelIdeHelper\Console; 13 | 14 | use Barryvdh\LaravelIdeHelper\Factories; 15 | use Dotenv\Parser\Entry; 16 | use Dotenv\Parser\Parser; 17 | use Illuminate\Console\Command; 18 | use Illuminate\Contracts\Config\Repository; 19 | use Illuminate\Contracts\View\Factory; 20 | use Illuminate\Filesystem\Filesystem; 21 | use Illuminate\Support\Collection; 22 | use RuntimeException; 23 | use Symfony\Component\Console\Input\InputOption; 24 | use Symfony\Component\Console\Output\OutputInterface; 25 | 26 | /** 27 | * A command to generate phpstorm meta data 28 | * 29 | * @author Barry vd. Heuvel 30 | */ 31 | class MetaCommand extends Command 32 | { 33 | /** 34 | * The console command name. 35 | * 36 | * @var string 37 | */ 38 | protected $name = 'ide-helper:meta'; 39 | 40 | /** 41 | * The console command description. 42 | * 43 | * @var string 44 | */ 45 | protected $description = 'Generate metadata for PhpStorm'; 46 | 47 | /** @var \Illuminate\Contracts\Filesystem\Filesystem */ 48 | protected $files; 49 | 50 | /** @var Factory */ 51 | protected $view; 52 | 53 | /** @var Repository */ 54 | protected $config; 55 | 56 | protected $methods = [ 57 | 'new \Illuminate\Contracts\Container\Container', 58 | '\Illuminate\Container\Container::makeWith(0)', 59 | '\Illuminate\Contracts\Container\Container::get(0)', 60 | '\Illuminate\Contracts\Container\Container::make(0)', 61 | '\Illuminate\Contracts\Container\Container::makeWith(0)', 62 | '\App::get(0)', 63 | '\App::make(0)', 64 | '\App::makeWith(0)', 65 | '\app(0)', 66 | '\resolve(0)', 67 | '\Psr\Container\ContainerInterface::get(0)', 68 | ]; 69 | 70 | protected $configMethods = [ 71 | '\config()', 72 | '\Illuminate\Config\Repository::get()', 73 | '\Illuminate\Support\Facades\Config::get()', 74 | ]; 75 | 76 | protected $userMethods = [ 77 | '\auth()->user()', 78 | '\Illuminate\Contracts\Auth\Guard::user()', 79 | '\Illuminate\Support\Facades\Auth::user()', 80 | '\request()->user()', 81 | '\Illuminate\Http\Request::user()', 82 | '\Illuminate\Support\Facades\Request::user()', 83 | ]; 84 | 85 | protected $templateCache = []; 86 | 87 | /** 88 | * 89 | * @param Filesystem $files 90 | * @param Factory $view 91 | * @param Repository $config 92 | */ 93 | public function __construct( 94 | Filesystem $files, 95 | Factory $view, 96 | Repository $config 97 | ) { 98 | $this->files = $files; 99 | $this->view = $view; 100 | $this->config = $config; 101 | parent::__construct(); 102 | } 103 | 104 | /** 105 | * Execute the console command. 106 | * 107 | * @return void 108 | */ 109 | public function handle() 110 | { 111 | // Needs to run before exception handler is registered 112 | $factories = $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : []; 113 | 114 | $ourAutoloader = $this->registerClassAutoloadExceptions(); 115 | 116 | $bindings = []; 117 | foreach ($this->getAbstracts() as $abstract) { 118 | // Validator and seeder cause problems 119 | if (in_array($abstract, ['validator', 'seeder'])) { 120 | continue; 121 | } 122 | 123 | try { 124 | $concrete = $this->laravel->make($abstract); 125 | 126 | if ($concrete === null) { 127 | throw new RuntimeException("Cannot create instance for '$abstract', received 'null'"); 128 | } 129 | 130 | $reflectionClass = new \ReflectionClass($concrete); 131 | if (is_object($concrete) && !$reflectionClass->isAnonymous() && $abstract !== get_class($concrete)) { 132 | $bindings[$abstract] = get_class($concrete); 133 | } 134 | } catch (\Throwable $e) { 135 | if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { 136 | $this->comment("Cannot make '$abstract': " . $e->getMessage()); 137 | } 138 | } 139 | } 140 | 141 | $this->unregisterClassAutoloadExceptions($ourAutoloader); 142 | 143 | $configValues = $this->loadTemplate('configs')->pluck('value', 'name')->map(function ($value, $key) { 144 | return gettype($value); 145 | }); 146 | 147 | $defaultUserModel = $this->config->get('auth.providers.users.model', $this->config->get('auth.model', 'App\User')); 148 | 149 | $content = $this->view->make('ide-helper::meta', [ 150 | 'bindings' => $bindings, 151 | 'methods' => $this->methods, 152 | 'factories' => $factories, 153 | 'configMethods' => $this->configMethods, 154 | 'configValues' => $configValues, 155 | 'expectedArgumentSets' => $this->getExpectedArgumentSets(), 156 | 'expectedArguments' => $this->getExpectedArguments(), 157 | 'userModel' => $defaultUserModel, 158 | 'userMethods' => $this->userMethods, 159 | ])->render(); 160 | 161 | $filename = $this->option('filename'); 162 | $written = $this->files->put($filename, $content); 163 | 164 | if ($written !== false) { 165 | $this->info("A new meta file was written to $filename"); 166 | } else { 167 | $this->error("The meta file could not be created at $filename"); 168 | } 169 | } 170 | 171 | /** 172 | * Get a list of abstracts from the Laravel Application. 173 | * 174 | * @return array 175 | */ 176 | protected function getAbstracts() 177 | { 178 | $abstracts = $this->laravel->getBindings(); 179 | 180 | // Return the abstract names only 181 | $keys = array_keys($abstracts); 182 | 183 | sort($keys); 184 | 185 | return $keys; 186 | } 187 | 188 | /** 189 | * Register an autoloader the throws exceptions when a class is not found. 190 | * 191 | * @return callable 192 | */ 193 | protected function registerClassAutoloadExceptions(): callable 194 | { 195 | $aliases = array_filter([...$this->getAbstracts(), 'config'], fn ($abstract) => !str_contains($abstract, '\\')); 196 | 197 | $autoloader = function ($class) use ($aliases) { 198 | // ignore aliases as they're meant to be resolved elsewhere 199 | if (in_array($class, $aliases, true)) { 200 | return; 201 | } 202 | 203 | throw new \ReflectionException("Class '$class' not found."); 204 | }; 205 | 206 | spl_autoload_register($autoloader); 207 | 208 | return $autoloader; 209 | } 210 | 211 | protected function getExpectedArgumentSets() 212 | { 213 | return [ 214 | 'auth' => $this->loadTemplate('auth')->keys()->filter()->toArray(), 215 | 'configs' => $this->loadTemplate('configs')->pluck('name')->filter()->toArray(), 216 | 'middleware' => $this->loadTemplate('middleware')->keys()->filter()->toArray(), 217 | 'routes' => $this->loadTemplate('routes')->pluck('name')->filter()->toArray(), 218 | 'views' => $this->loadTemplate('views')->pluck('key')->filter()->map(function ($value) { 219 | return (string) $value; 220 | })->toArray(), 221 | 'translations' => $this->loadTemplate('translations')->filter()->keys()->toArray(), 222 | 'env' => $this->getEnv(), 223 | ]; 224 | } 225 | 226 | protected function getExpectedArguments() 227 | { 228 | return [ 229 | [ 230 | 'class' => '\Illuminate\Support\Facades\Gate', 231 | 'method' => [ 232 | 'has', 233 | 'allows', 234 | 'denies', 235 | 'check', 236 | 'any', 237 | 'none', 238 | 'authorize', 239 | 'inspect', 240 | ], 241 | 'argumentSet' => 'auth', 242 | ], 243 | [ 244 | 'class' => ['\Illuminate\Support\Facades\Route', '\Illuminate\Support\Facades\Auth', 'Illuminate\Foundation\Auth\Access\Authorizable'], 245 | 'method' => ['can', 'cannot', 'cant'], 246 | 'argumentSet' => 'auth', 247 | ], 248 | [ 249 | 'class' => ['Illuminate\Contracts\Auth\Access\Authorizable'], 250 | 'method' => ['can'], 251 | 'argumentSet' => 'auth', 252 | ], 253 | [ 254 | 'class' => ['\Illuminate\Config\Repository', '\Illuminate\Support\Facades\Config'], 255 | 'method' => [ 256 | // 'get', // config() and Config::Get() are added with return type hints already 257 | 'getMany', 258 | 'set', 259 | 'string', 260 | 'integer', 261 | 'boolean', 262 | 'float', 263 | 'array', 264 | 'prepend', 265 | 'push', 266 | ], 267 | 'argumentSet' => 'configs', 268 | ], 269 | [ 270 | 'class' => ['\Illuminate\Support\Facades\Route', '\Illuminate\Routing\Router'], 271 | 'method' => ['middleware', 'withoutMiddleware'], 272 | 'argumentSet' => 'middleware', 273 | ], 274 | [ 275 | 'method' => ['route', 'to_route', 'signedRoute'], 276 | 'argumentSet' => 'routes', 277 | ], 278 | [ 279 | 'class' => [ 280 | '\Illuminate\Support\Facades\Redirect', 281 | '\Illuminate\Support\Facades\URL', 282 | '\Illuminate\Routing\Redirector', 283 | '\Illuminate\Routing\UrlGenerator', 284 | ], 285 | 'method' => ['route', 'signedRoute', 'temporarySignedRoute'], 286 | 'argumentSet' => 'routes', 287 | ], 288 | [ 289 | 'method' => 'view', 290 | 'argumentSet' => 'views', 291 | ], 292 | [ 293 | 'class' => ['\Illuminate\Support\Facades\View', '\Illuminate\View\Factory'], 294 | 'method' => 'make', 295 | 'argumentSet' => 'views', 296 | ], 297 | [ 298 | 'method' => ['__', 'trans'], 299 | 'argumentSet' => 'translations', 300 | ], 301 | [ 302 | 'class' => ['\Illuminate\Contracts\Translation\Translator'], 303 | 'method' => ['get'], 304 | 'argumentSet' => 'translations', 305 | ], 306 | [ 307 | 'method' => 'env', 308 | 'argumentSet' => 'env', 309 | ], 310 | [ 311 | 'class' => '\Illuminate\Support\Env', 312 | 'method' => 'get', 313 | 'argumentSet' => 'env', 314 | ], 315 | ]; 316 | } 317 | 318 | /** 319 | * @return Collection 320 | */ 321 | protected function loadTemplate($name) 322 | { 323 | if (!isset($this->templateCache[$name])) { 324 | $file = __DIR__ . '/../../php-templates/' . basename($name) . '.php'; 325 | try { 326 | $value = $this->files->requireOnce($file) ?: []; 327 | } catch (\Throwable $e) { 328 | $value = []; 329 | $this->warn('Cannot load template for ' . $name . ': ' . $e->getMessage()); 330 | } 331 | 332 | if (!$value instanceof Collection) { 333 | $value = collect($value); 334 | } 335 | $this->templateCache[$name] = $value; 336 | } 337 | 338 | return $this->templateCache[$name]; 339 | } 340 | 341 | /** 342 | * Get the console command options. 343 | * 344 | * @return array 345 | */ 346 | protected function getOptions() 347 | { 348 | $filename = $this->config->get('ide-helper.meta_filename'); 349 | 350 | return [ 351 | ['filename', 'F', InputOption::VALUE_OPTIONAL, 'The path to the meta file', $filename], 352 | ]; 353 | } 354 | 355 | protected function getEnv() 356 | { 357 | $envPath = base_path('.env'); 358 | if (!file_exists($envPath)) { 359 | return []; 360 | } 361 | 362 | $parser = new Parser(); 363 | $entries = $parser->parse(file_get_contents($envPath)); 364 | 365 | return collect($entries)->map(function (Entry $entry) { 366 | return $entry->getName(); 367 | }); 368 | } 369 | 370 | /** 371 | * Remove our custom autoloader that we pushed onto the autoload stack 372 | * 373 | * @param callable $ourAutoloader 374 | */ 375 | private function unregisterClassAutoloadExceptions(callable $ourAutoloader): void 376 | { 377 | spl_autoload_unregister($ourAutoloader); 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/Contracts/ModelHookInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | namespace Barryvdh\LaravelIdeHelper; 10 | 11 | use Barryvdh\Reflection\DocBlock; 12 | use Barryvdh\Reflection\DocBlock\Context; 13 | use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; 14 | use Barryvdh\Reflection\DocBlock\Tag; 15 | use Illuminate\Console\Command; 16 | use Illuminate\Filesystem\Filesystem; 17 | 18 | class Eloquent 19 | { 20 | /** 21 | * Write mixin helper to the Eloquent\Model 22 | * This is needed since laravel/framework v5.4.29 23 | * 24 | * @param Command $command 25 | * @param Filesystem $files 26 | * 27 | * @return void 28 | */ 29 | public static function writeEloquentModelHelper(Command $command, Filesystem $files) 30 | { 31 | $class = 'Illuminate\Database\Eloquent\Model'; 32 | 33 | $reflection = new \ReflectionClass($class); 34 | $namespace = $reflection->getNamespaceName(); 35 | $originalDoc = $reflection->getDocComment(); 36 | 37 | if (!$originalDoc) { 38 | $command->info('Unexpected no document on ' . $class); 39 | } 40 | $phpdoc = new DocBlock($reflection, new Context($namespace)); 41 | 42 | $mixins = $phpdoc->getTagsByName('mixin'); 43 | $expectedMixins = [ 44 | '\Eloquent' => false, 45 | '\Illuminate\Database\Eloquent\Builder' => false, 46 | '\Illuminate\Database\Query\Builder' => false, 47 | ]; 48 | 49 | foreach ($mixins as $m) { 50 | $mixin = $m->getContent(); 51 | 52 | if (isset($expectedMixins[$mixin])) { 53 | $command->info('Tag Exists: @mixin ' . $mixin . ' in ' . $class); 54 | 55 | $expectedMixins[$mixin] = true; 56 | } 57 | } 58 | 59 | $changed = false; 60 | foreach ($expectedMixins as $expectedMixin => $present) { 61 | if ($present === false) { 62 | $phpdoc->appendTag(Tag::createInstance('@mixin ' . $expectedMixin, $phpdoc)); 63 | 64 | $changed = true; 65 | } 66 | } 67 | 68 | // If nothing's changed, stop here. 69 | if (!$changed) { 70 | return; 71 | } 72 | 73 | $serializer = new DocBlockSerializer(); 74 | $serializer->getDocComment($phpdoc); 75 | $docComment = $serializer->getDocComment($phpdoc); 76 | 77 | /* 78 | The new DocBlock is appended to the beginning of the class declaration. 79 | Since there is no DocBlock, the declaration is used as a guide. 80 | */ 81 | if (!$originalDoc) { 82 | $originalDoc = 'abstract class Model implements'; 83 | 84 | $docComment .= "\nabstract class Model implements"; 85 | } 86 | 87 | $filename = $reflection->getFileName(); 88 | if (!$filename) { 89 | $command->error('Filename not found ' . $class); 90 | return; 91 | } 92 | 93 | $contents = $files->get($filename); 94 | if (!$contents) { 95 | $command->error('No file contents found ' . $filename); 96 | return; 97 | } 98 | 99 | $count = 0; 100 | $contents = str_replace($originalDoc, $docComment, $contents, $count); 101 | if ($count <= 0) { 102 | $command->error('Content did not change ' . $contents); 103 | return; 104 | } 105 | 106 | if (!$files->put($filename, $contents)) { 107 | $command->error('File write failed to ' . $filename); 108 | return; 109 | } 110 | 111 | $command->info('Wrote expected docblock to ' . $filename); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Factories.php: -------------------------------------------------------------------------------- 1 | getProperty('definitions'); 19 | $definitions->setAccessible(true); 20 | 21 | foreach ($definitions->getValue($factory) as $factory_target => $config) { 22 | try { 23 | $factories[] = new ReflectionClass($factory_target); 24 | } catch (Exception $exception) { 25 | } 26 | } 27 | } 28 | 29 | return $factories; 30 | } 31 | 32 | protected static function isLaravelSevenOrLower() 33 | { 34 | return class_exists('Illuminate\Database\Eloquent\Factory'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Generator.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) 8 | * @license http://www.opensource.org/licenses/mit-license.php MIT 9 | * @link https://github.com/barryvdh/laravel-ide-helper 10 | */ 11 | 12 | namespace Barryvdh\LaravelIdeHelper; 13 | 14 | use Illuminate\Database\Eloquent\Model; 15 | use Illuminate\Foundation\AliasLoader; 16 | use Illuminate\Support\Collection; 17 | use Illuminate\Support\Facades\Facade; 18 | use Illuminate\Support\Str; 19 | use Illuminate\Support\Traits\Macroable; 20 | use ReflectionClass; 21 | use Symfony\Component\Console\Output\OutputInterface; 22 | 23 | class Generator 24 | { 25 | /** @var \Illuminate\Config\Repository */ 26 | protected $config; 27 | 28 | /** @var \Illuminate\View\Factory */ 29 | protected $view; 30 | 31 | /** @var OutputInterface */ 32 | protected $output; 33 | 34 | protected $extra = []; 35 | protected $magic = []; 36 | protected $interfaces = []; 37 | protected $helpers; 38 | 39 | /** 40 | * @param \Illuminate\Config\Repository $config 41 | * @param \Illuminate\View\Factory $view 42 | * @param ?OutputInterface $output 43 | * @param string $helpers 44 | */ 45 | public function __construct( 46 | /*ConfigRepository */ 47 | $config, 48 | /* Illuminate\View\Factory */ 49 | $view, 50 | ?OutputInterface $output = null, 51 | $helpers = '' 52 | ) { 53 | $this->config = $config; 54 | $this->view = $view; 55 | $this->output = $output; 56 | 57 | // Find the drivers to add to the extra/interfaces 58 | $this->detectDrivers(); 59 | 60 | $this->extra = array_merge($this->extra, $this->config->get('ide-helper.extra', [])); 61 | $this->magic = array_merge($this->magic, $this->config->get('ide-helper.magic', [])); 62 | $this->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces', [])); 63 | Macro::setDefaultReturnTypes($this->config->get('ide-helper.macro_default_return_types', [ 64 | \Illuminate\Http\Client\Factory::class => \Illuminate\Http\Client\PendingRequest::class, 65 | ])); 66 | 67 | // Make all interface classes absolute 68 | foreach ($this->interfaces as &$interface) { 69 | $interface = '\\' . ltrim($interface, '\\'); 70 | } 71 | $this->helpers = $helpers; 72 | } 73 | 74 | /** 75 | * Generate the helper file contents; 76 | * 77 | * @return string; 78 | */ 79 | public function generate() 80 | { 81 | $app = app(); 82 | return $this->view->make('ide-helper::helper') 83 | ->with('namespaces_by_extends_ns', $this->getAliasesByExtendsNamespace()) 84 | ->with('namespaces_by_alias_ns', $this->getAliasesByAliasNamespace()) 85 | ->with('real_time_facades', $this->getRealTimeFacades()) 86 | ->with('helpers', $this->detectHelpers()) 87 | ->with('include_fluent', $this->config->get('ide-helper.include_fluent', true)) 88 | ->with('factories', $this->config->get('ide-helper.include_factory_builders') ? Factories::all() : []) 89 | ->render(); 90 | } 91 | 92 | public function generateEloquent() 93 | { 94 | $name = 'Eloquent'; 95 | $facade = Model::class; 96 | $magicMethods = array_key_exists($name, $this->magic) ? $this->magic[$name] : []; 97 | $alias = new Alias($this->config, $name, $facade, $magicMethods, $this->interfaces); 98 | if (!$alias->isValid()) { 99 | throw new \RuntimeException('Cannot generate Eloquent helper'); 100 | } 101 | 102 | //Add extra methods, from other classes (magic static calls) 103 | if (array_key_exists($name, $this->extra)) { 104 | $alias->addClass($this->extra[$name]); 105 | } 106 | 107 | $app = app(); 108 | return $this->view->make('ide-helper::helper') 109 | ->with('namespaces_by_extends_ns', []) 110 | ->with('namespaces_by_alias_ns', ['__root' => [$alias]]) 111 | ->with('real_time_facades', []) 112 | ->with('helpers', '') 113 | ->with('include_fluent', false) 114 | ->with('factories', []) 115 | ->render(); 116 | } 117 | 118 | protected function detectHelpers() 119 | { 120 | $helpers = $this->helpers; 121 | 122 | $replacements = [ 123 | '($guard is null ? \Illuminate\Contracts\Auth\Factory : \Illuminate\Contracts\Auth\StatefulGuard)' => '\\Auth', 124 | ]; 125 | foreach ($replacements as $search => $replace) { 126 | $helpers = Str::replace( 127 | "@return {$search}", 128 | "@return $replace|$search", 129 | $helpers 130 | ); 131 | } 132 | 133 | return $helpers; 134 | } 135 | 136 | protected function detectDrivers() 137 | { 138 | $defaultUserModel = config('auth.providers.users.model', config('auth.model', 'App\User')); 139 | $this->interfaces['\Illuminate\Contracts\Auth\Authenticatable'] = $defaultUserModel; 140 | 141 | try { 142 | if ( 143 | class_exists('Auth') && is_a('Auth', '\Illuminate\Support\Facades\Auth', true) 144 | && app()->bound('auth') 145 | ) { 146 | $class = get_class(\Auth::guard()); 147 | $this->extra['Auth'] = [$class]; 148 | $this->interfaces['\Illuminate\Auth\UserProviderInterface'] = $class; 149 | } 150 | } catch (\Exception $e) { 151 | } 152 | 153 | try { 154 | if (class_exists('DB') && is_a('DB', '\Illuminate\Support\Facades\DB', true)) { 155 | $class = get_class(\DB::connection()); 156 | $this->extra['DB'] = [$class]; 157 | $this->interfaces['\Illuminate\Database\ConnectionInterface'] = $class; 158 | } 159 | } catch (\Exception $e) { 160 | } 161 | 162 | try { 163 | if (class_exists('Cache') && is_a('Cache', '\Illuminate\Support\Facades\Cache', true)) { 164 | $driver = get_class(\Cache::driver()); 165 | $store = get_class(\Cache::getStore()); 166 | $this->extra['Cache'] = [$driver, $store]; 167 | $this->interfaces['\Illuminate\Cache\StoreInterface'] = $store; 168 | } 169 | } catch (\Exception $e) { 170 | } 171 | 172 | try { 173 | if (class_exists('Queue') && is_a('Queue', '\Illuminate\Support\Facades\Queue', true)) { 174 | $class = get_class(\Queue::connection()); 175 | $this->extra['Queue'] = [$class]; 176 | $this->interfaces['\Illuminate\Queue\QueueInterface'] = $class; 177 | } 178 | } catch (\Exception $e) { 179 | } 180 | 181 | try { 182 | if (class_exists('Storage') && is_a('Storage', '\Illuminate\Support\Facades\Storage', true)) { 183 | $class = get_class(\Storage::disk()); 184 | $this->extra['Storage'] = [$class]; 185 | $this->interfaces['\Illuminate\Contracts\Filesystem\Filesystem'] = $class; 186 | } 187 | } catch (\Exception $e) { 188 | } 189 | } 190 | 191 | /** 192 | * Find all aliases that are valid for us to render 193 | * 194 | * @return Collection 195 | */ 196 | protected function getValidAliases() 197 | { 198 | $aliases = new Collection(); 199 | 200 | // Get all aliases 201 | foreach ($this->getAliases() as $name => $facade) { 202 | // Skip the Redis facade, if not available (otherwise Fatal PHP Error) 203 | if ($facade == 'Illuminate\Support\Facades\Redis' && $name == 'Redis' && !class_exists('Predis\Client')) { 204 | continue; 205 | } 206 | 207 | // Skip the swoole 208 | if ($facade == 'SwooleTW\Http\Server\Facades\Server' && $name == 'Server' && !class_exists('Swoole\Http\Server')) { 209 | continue; 210 | } 211 | 212 | $magicMethods = array_key_exists($name, $this->magic) ? $this->magic[$name] : []; 213 | $alias = new Alias($this->config, $name, $facade, $magicMethods, $this->interfaces); 214 | if ($alias->isValid()) { 215 | //Add extra methods, from other classes (magic static calls) 216 | if (array_key_exists($name, $this->extra)) { 217 | $alias->addClass($this->extra[$name]); 218 | } 219 | 220 | $aliases[] = $alias; 221 | } 222 | } 223 | 224 | return $aliases; 225 | } 226 | 227 | protected function getRealTimeFacades() 228 | { 229 | $facades = []; 230 | $realTimeFacadeFiles = glob(storage_path('framework/cache/facade-*.php')); 231 | foreach ($realTimeFacadeFiles as $file) { 232 | try { 233 | $name = $this->getFullyQualifiedClassNameInFile($file); 234 | if ($name) { 235 | $facades[$name] = $name; 236 | } 237 | } catch (\Throwable $e) { 238 | continue; 239 | } 240 | } 241 | 242 | return $facades; 243 | } 244 | 245 | protected function getFullyQualifiedClassNameInFile(string $path) 246 | { 247 | $contents = file_get_contents($path); 248 | 249 | // Match namespace 250 | preg_match('/namespace\s+([^;]+);/', $contents, $namespaceMatch); 251 | $namespace = isset($namespaceMatch[1]) ? $namespaceMatch[1] : ''; 252 | 253 | // Match class name 254 | preg_match('/class\s+([a-zA-Z0-9_]+)/', $contents, $classMatch); 255 | $className = isset($classMatch[1]) ? $classMatch[1] : ''; 256 | 257 | // Combine namespace and class name 258 | if ($namespace && $className) { 259 | return $namespace . '\\' . $className; 260 | } 261 | } 262 | 263 | 264 | 265 | /** 266 | * Regroup aliases by namespace of extended classes 267 | * 268 | * @return Collection 269 | */ 270 | protected function getAliasesByExtendsNamespace() 271 | { 272 | $aliases = $this->getValidAliases()->filter(static function (Alias $alias) { 273 | return is_subclass_of($alias->getExtends(), Facade::class); 274 | }); 275 | 276 | $this->addMacroableClasses($aliases); 277 | 278 | return $aliases->groupBy(function (Alias $alias) { 279 | return $alias->getExtendsNamespace(); 280 | }); 281 | } 282 | 283 | /** 284 | * Regroup aliases by namespace of alias 285 | * 286 | * @return Collection 287 | */ 288 | protected function getAliasesByAliasNamespace() 289 | { 290 | return $this->getValidAliases()->groupBy(function (Alias $alias) { 291 | return $alias->getNamespace(); 292 | }); 293 | } 294 | 295 | protected function getAliases() 296 | { 297 | // For Laravel, use the AliasLoader 298 | if (class_exists('Illuminate\Foundation\AliasLoader')) { 299 | return AliasLoader::getInstance()->getAliases(); 300 | } 301 | 302 | $facades = [ 303 | 'App' => 'Illuminate\Support\Facades\App', 304 | 'Auth' => 'Illuminate\Support\Facades\Auth', 305 | 'Bus' => 'Illuminate\Support\Facades\Bus', 306 | 'DB' => 'Illuminate\Support\Facades\DB', 307 | 'Cache' => 'Illuminate\Support\Facades\Cache', 308 | 'Cookie' => 'Illuminate\Support\Facades\Cookie', 309 | 'Crypt' => 'Illuminate\Support\Facades\Crypt', 310 | 'Event' => 'Illuminate\Support\Facades\Event', 311 | 'Hash' => 'Illuminate\Support\Facades\Hash', 312 | 'Log' => 'Illuminate\Support\Facades\Log', 313 | 'Mail' => 'Illuminate\Support\Facades\Mail', 314 | 'Queue' => 'Illuminate\Support\Facades\Queue', 315 | 'Request' => 'Illuminate\Support\Facades\Request', 316 | 'Schema' => 'Illuminate\Support\Facades\Schema', 317 | 'Session' => 'Illuminate\Support\Facades\Session', 318 | 'Storage' => 'Illuminate\Support\Facades\Storage', 319 | 'Validator' => 'Illuminate\Support\Facades\Validator', 320 | 'Gate' => 'Illuminate\Support\Facades\Gate', 321 | ]; 322 | 323 | $facades = array_merge($facades, $this->config->get('app.aliases', [])); 324 | 325 | // Only return the ones that actually exist 326 | return array_filter( 327 | $facades, 328 | function ($alias) { 329 | return class_exists($alias); 330 | }, 331 | ARRAY_FILTER_USE_KEY 332 | ); 333 | } 334 | 335 | /** 336 | * Write a string as error output. 337 | * 338 | * @param string $string 339 | * @return void 340 | */ 341 | protected function error($string) 342 | { 343 | if ($this->output) { 344 | $this->output->writeln("$string"); 345 | } else { 346 | echo $string . "\r\n"; 347 | } 348 | } 349 | 350 | /** 351 | * Add all macroable classes which are not already loaded as an alias and have defined macros. 352 | * 353 | * @param Collection $aliases 354 | */ 355 | protected function addMacroableClasses(Collection $aliases) 356 | { 357 | $macroable = $this->getMacroableClasses($aliases); 358 | 359 | foreach ($macroable as $class) { 360 | $reflection = new ReflectionClass($class); 361 | 362 | if (!$reflection->getStaticProperties()['macros']) { 363 | continue; 364 | } 365 | 366 | $aliases[] = new Alias($this->config, $class, $class, [], $this->interfaces); 367 | } 368 | } 369 | 370 | /** 371 | * Get all loaded macroable classes which are not loaded as an alias. 372 | * 373 | * @param Collection $aliases 374 | * @return Collection 375 | */ 376 | protected function getMacroableClasses(Collection $aliases) 377 | { 378 | return (new Collection(get_declared_classes())) 379 | ->filter(function ($class) { 380 | $reflection = new ReflectionClass($class); 381 | 382 | // Filter out internal classes and class aliases 383 | return !$reflection->isInternal() && $reflection->getName() === $class; 384 | }) 385 | ->filter(function ($class) { 386 | $traits = class_uses_recursive($class); 387 | 388 | // Filter only classes with the macroable trait 389 | return isset($traits[Macroable::class]); 390 | }) 391 | ->filter(function ($class) use ($aliases) { 392 | $class = Str::start($class, '\\'); 393 | 394 | // Filter out aliases 395 | return !$aliases->first(function (Alias $alias) use ($class) { 396 | return $alias->getExtends() === $class; 397 | }); 398 | }); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/IdeHelperServiceProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) 8 | * @license http://www.opensource.org/licenses/mit-license.php MIT 9 | * @link https://github.com/barryvdh/laravel-ide-helper 10 | */ 11 | 12 | namespace Barryvdh\LaravelIdeHelper; 13 | 14 | use Barryvdh\LaravelIdeHelper\Console\EloquentCommand; 15 | use Barryvdh\LaravelIdeHelper\Console\GeneratorCommand; 16 | use Barryvdh\LaravelIdeHelper\Console\MetaCommand; 17 | use Barryvdh\LaravelIdeHelper\Console\ModelsCommand; 18 | use Barryvdh\LaravelIdeHelper\Listeners\GenerateModelHelper; 19 | use Illuminate\Console\Events\CommandFinished; 20 | use Illuminate\Contracts\Support\DeferrableProvider; 21 | use Illuminate\Database\Events\MigrationsEnded; 22 | use Illuminate\Support\ServiceProvider; 23 | 24 | class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProvider 25 | { 26 | /** 27 | * Bootstrap the application events. 28 | * 29 | * @return void 30 | */ 31 | public function boot() 32 | { 33 | if (!$this->app->runningUnitTests() && $this->app['config']->get('ide-helper.post_migrate', [])) { 34 | $this->app['events']->listen(CommandFinished::class, GenerateModelHelper::class); 35 | $this->app['events']->listen(MigrationsEnded::class, function () { 36 | GenerateModelHelper::$shouldRun = true; 37 | }); 38 | } 39 | 40 | if ($this->app->has('view')) { 41 | $viewPath = __DIR__ . '/../resources/views'; 42 | $this->loadViewsFrom($viewPath, 'ide-helper'); 43 | } 44 | 45 | $configPath = __DIR__ . '/../config/ide-helper.php'; 46 | if (function_exists('config_path')) { 47 | $publishPath = config_path('ide-helper.php'); 48 | } else { 49 | $publishPath = base_path('config/ide-helper.php'); 50 | } 51 | $this->publishes([$configPath => $publishPath], 'config'); 52 | } 53 | 54 | /** 55 | * Register the service provider. 56 | * 57 | * @return void 58 | */ 59 | public function register() 60 | { 61 | $configPath = __DIR__ . '/../config/ide-helper.php'; 62 | $this->mergeConfigFrom($configPath, 'ide-helper'); 63 | 64 | $this->commands( 65 | [ 66 | GeneratorCommand::class, 67 | ModelsCommand::class, 68 | MetaCommand::class, 69 | EloquentCommand::class, 70 | ] 71 | ); 72 | } 73 | 74 | /** 75 | * Get the services provided by the provider. 76 | * 77 | * @return array 78 | */ 79 | public function provides() 80 | { 81 | return [ 82 | GeneratorCommand::class, 83 | ModelsCommand::class, 84 | MetaCommand::class, 85 | EloquentCommand::class, 86 | ]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Listeners/GenerateModelHelper.php: -------------------------------------------------------------------------------- 1 | artisan = $artisan; 32 | $this->config = $config; 33 | } 34 | 35 | /** 36 | * Handle the event. 37 | * 38 | * @param CommandFinished $event 39 | */ 40 | public function handle(CommandFinished $event) 41 | { 42 | if (!self::$shouldRun) { 43 | return; 44 | } 45 | 46 | self::$shouldRun = false; 47 | 48 | foreach ($this->config->get('ide-helper.post_migrate', []) as $command) { 49 | $this->artisan->call($command, [], $event->output); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Macro.php: -------------------------------------------------------------------------------- 1 | phpdoc = new DocBlock($method); 47 | 48 | $this->addLocationToPhpDoc(); 49 | 50 | // Add macro parameters if they are missed in original docblock 51 | if (!$this->phpdoc->hasTag('param')) { 52 | foreach ($method->getParameters() as $parameter) { 53 | $reflectionType = $parameter->getType(); 54 | 55 | $type = $this->concatReflectionTypes($reflectionType); 56 | 57 | /** @psalm-suppress UndefinedClass */ 58 | if ($reflectionType && !$reflectionType instanceof \ReflectionUnionType && $reflectionType->allowsNull()) { 59 | $type .= '|null'; 60 | } 61 | 62 | $type = $type ?: 'mixed'; 63 | 64 | $name = $parameter->isVariadic() ? '...' : ''; 65 | $name .= '$' . $parameter->getName(); 66 | 67 | $this->phpdoc->appendTag(Tag::createInstance("@param {$type} {$name}")); 68 | } 69 | } 70 | 71 | // Add macro return type if it missed in original docblock 72 | if ($method->hasReturnType() && !$this->phpdoc->hasTag('return')) { 73 | $builder = EloquentBuilder::class; 74 | $return = $method->getReturnType(); 75 | 76 | $type = $this->concatReflectionTypes($return); 77 | 78 | /** @psalm-suppress UndefinedClass */ 79 | if (!$return instanceof \ReflectionUnionType) { 80 | $type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : ''; 81 | $type .= $return->allowsNull() ? '|null' : ''; 82 | } 83 | 84 | $this->phpdoc->appendTag(Tag::createInstance("@return {$type}")); 85 | } 86 | 87 | $class = ltrim($this->declaringClassName, '\\'); 88 | if (!$this->phpdoc->hasTag('return') && isset(static::$macroDefaults[$class])) { 89 | $type = static::$macroDefaults[$class]; 90 | $this->phpdoc->appendTag(Tag::createInstance("@return {$type}")); 91 | } 92 | } 93 | 94 | protected function concatReflectionTypes(?\ReflectionType $type): string 95 | { 96 | /** @psalm-suppress UndefinedClass */ 97 | $returnTypes = $type instanceof \ReflectionUnionType 98 | ? $type->getTypes() 99 | : [$type]; 100 | 101 | return Collection::make($returnTypes) 102 | ->filter() 103 | ->map->getName() 104 | ->implode('|'); 105 | } 106 | 107 | protected function addLocationToPhpDoc() 108 | { 109 | if ($this->method->name === '__invoke') { 110 | $enclosingClass = $this->method->getDeclaringClass(); 111 | } else { 112 | $enclosingClass = $this->method->getClosureScopeClass(); 113 | } 114 | 115 | if (!$enclosingClass) { 116 | return; 117 | } 118 | /** @var \ReflectionMethod $enclosingMethod */ 119 | $enclosingMethod = Collection::make($enclosingClass->getMethods()) 120 | ->first(function (\ReflectionMethod $method) { 121 | return $method->getStartLine() <= $this->method->getStartLine() 122 | && $method->getEndLine() >= $this->method->getEndLine(); 123 | }); 124 | 125 | if ($enclosingMethod) { 126 | $this->phpdoc->appendTag(Tag::createInstance( 127 | '@see \\' . $enclosingClass->getName() . '::' . $enclosingMethod->getName() . '()' 128 | )); 129 | } 130 | } 131 | 132 | /** 133 | * @param \ReflectionFunctionAbstract $method 134 | * @param \ReflectionClass $class 135 | */ 136 | protected function initClassDefinedProperties($method, \ReflectionClass $class) 137 | { 138 | $this->namespace = $class->getNamespaceName(); 139 | $this->declaringClassName = '\\' . ltrim($class->name, '\\'); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Method.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) 8 | * @license http://www.opensource.org/licenses/mit-license.php MIT 9 | * @link https://github.com/barryvdh/laravel-ide-helper 10 | */ 11 | 12 | namespace Barryvdh\LaravelIdeHelper; 13 | 14 | use Barryvdh\Reflection\DocBlock; 15 | use Barryvdh\Reflection\DocBlock\Context; 16 | use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; 17 | use Barryvdh\Reflection\DocBlock\Tag; 18 | use Barryvdh\Reflection\DocBlock\Tag\ParamTag; 19 | use Barryvdh\Reflection\DocBlock\Tag\ReturnTag; 20 | 21 | class Method 22 | { 23 | /** @var DocBlock */ 24 | protected $phpdoc; 25 | 26 | /** @var \ReflectionMethod */ 27 | protected $method; 28 | 29 | protected $output = ''; 30 | protected $declaringClassName; 31 | protected $name; 32 | protected $namespace; 33 | protected $params = []; 34 | protected $params_with_default = []; 35 | protected $interfaces = []; 36 | protected $real_name; 37 | protected $return = null; 38 | protected $root; 39 | protected $classAliases; 40 | protected $returnTypeNormalizers; 41 | 42 | /** @var string[] */ 43 | protected $templateNames = []; 44 | 45 | /** 46 | * @param \ReflectionMethod|\ReflectionFunctionAbstract $method 47 | * @param string $alias 48 | * @param \ReflectionClass $class 49 | * @param string|null $methodName 50 | * @param array $interfaces 51 | * @param array $classAliases 52 | * @param array $returnTypeNormalizers 53 | * @param string[] $templateNames 54 | */ 55 | public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [], array $templateNames = []) 56 | { 57 | $this->method = $method; 58 | $this->interfaces = $interfaces; 59 | $this->classAliases = $classAliases; 60 | $this->returnTypeNormalizers = $returnTypeNormalizers; 61 | $this->name = $methodName ?: $method->name; 62 | $this->real_name = $method->isClosure() ? $this->name : $method->name; 63 | $this->templateNames = $templateNames; 64 | $this->initClassDefinedProperties($method, $class); 65 | 66 | //Reference the 'real' function in the declaring class 67 | $this->root = '\\' . ltrim($method->name === '__invoke' ? $method->getDeclaringClass()->getName() : $class->getName(), '\\'); 68 | 69 | //Create a DocBlock and serializer instance 70 | $this->initPhpDoc($method); 71 | 72 | //Normalize the description and inherit the docs from parents/interfaces 73 | try { 74 | $this->normalizeParams($this->phpdoc); 75 | $this->normalizeReturn($this->phpdoc); 76 | $this->normalizeDescription($this->phpdoc); 77 | } catch (\Exception $e) { 78 | } 79 | 80 | //Get the parameters, including formatted default values 81 | $this->getParameters($method); 82 | 83 | //Make the method static 84 | $this->phpdoc->appendTag(Tag::createInstance('@static', $this->phpdoc)); 85 | } 86 | 87 | /** 88 | * @param \ReflectionMethod $method 89 | */ 90 | protected function initPhpDoc($method) 91 | { 92 | $this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases, generics: $this->templateNames)); 93 | } 94 | 95 | /** 96 | * @param \ReflectionMethod $method 97 | * @param \ReflectionClass $class 98 | */ 99 | protected function initClassDefinedProperties($method, \ReflectionClass $class) 100 | { 101 | $declaringClass = $method->getDeclaringClass(); 102 | $this->namespace = $declaringClass->getNamespaceName(); 103 | $this->declaringClassName = '\\' . ltrim($declaringClass->name, '\\'); 104 | } 105 | 106 | /** 107 | * Get the class wherein the function resides 108 | * 109 | * @return string 110 | */ 111 | public function getDeclaringClass() 112 | { 113 | return $this->declaringClassName; 114 | } 115 | 116 | /** 117 | * Return the class from which this function would be called 118 | * 119 | * @return string 120 | */ 121 | public function getRoot() 122 | { 123 | return $this->root; 124 | } 125 | 126 | /** 127 | * @return bool 128 | */ 129 | public function isInstanceCall() 130 | { 131 | return !($this->method->isClosure() || $this->method->isStatic()); 132 | } 133 | 134 | /** 135 | * @return string 136 | */ 137 | public function getRootMethodCall() 138 | { 139 | if ($this->isInstanceCall()) { 140 | return "\$instance->{$this->getRealName()}({$this->getParams()})"; 141 | } else { 142 | return "{$this->getRoot()}::{$this->getRealName()}({$this->getParams()})"; 143 | } 144 | } 145 | 146 | /** 147 | * Get the docblock for this method 148 | * 149 | * @param string $prefix 150 | * @return mixed 151 | */ 152 | public function getDocComment($prefix = "\t\t") 153 | { 154 | $serializer = new DocBlockSerializer(1, $prefix); 155 | return $serializer->getDocComment($this->phpdoc); 156 | } 157 | 158 | /** 159 | * Get the method name 160 | * 161 | * @return string 162 | */ 163 | public function getName() 164 | { 165 | return $this->name; 166 | } 167 | 168 | /** 169 | * Get the real method name 170 | * 171 | * @return string 172 | */ 173 | public function getRealName() 174 | { 175 | return $this->real_name; 176 | } 177 | 178 | /** 179 | * Get the parameters for this method 180 | * 181 | * @param bool $implode Wether to implode the array or not 182 | * @return string 183 | */ 184 | public function getParams($implode = true) 185 | { 186 | return $implode ? implode(', ', $this->params) : $this->params; 187 | } 188 | 189 | /** 190 | * @param DocBlock|null $phpdoc 191 | * @return ReturnTag|null 192 | */ 193 | public function getReturnTag($phpdoc = null) 194 | { 195 | if ($phpdoc === null) { 196 | $phpdoc = $this->phpdoc; 197 | } 198 | 199 | $returnTags = $phpdoc->getTagsByName('return'); 200 | 201 | if (count($returnTags) === 0) { 202 | return null; 203 | } 204 | 205 | return reset($returnTags); 206 | } 207 | 208 | /** 209 | * Get the parameters for this method including default values 210 | * 211 | * @param bool $implode Wether to implode the array or not 212 | * @return string 213 | */ 214 | public function getParamsWithDefault($implode = true) 215 | { 216 | return $implode ? implode(', ', $this->params_with_default) : $this->params_with_default; 217 | } 218 | 219 | /** 220 | * Get the description and get the inherited docs. 221 | * 222 | * @param DocBlock $phpdoc 223 | */ 224 | protected function normalizeDescription(DocBlock $phpdoc) 225 | { 226 | //Get the short + long description from the DocBlock 227 | $description = $phpdoc->getText(); 228 | 229 | //Loop through parents/interfaces, to fill in {@inheritdoc} 230 | if (strpos($description, '{@inheritdoc}') !== false) { 231 | $inheritdoc = $this->getInheritDoc($this->method); 232 | $inheritDescription = $inheritdoc->getText(); 233 | 234 | $description = str_replace('{@inheritdoc}', $inheritDescription, $description); 235 | $phpdoc->setText($description); 236 | 237 | $this->normalizeParams($inheritdoc); 238 | $this->normalizeReturn($inheritdoc); 239 | 240 | //Add the tags that are inherited 241 | $inheritTags = $inheritdoc->getTags(); 242 | if ($inheritTags) { 243 | /** @var Tag $tag */ 244 | foreach ($inheritTags as $tag) { 245 | $tag->setDocBlock(); 246 | $phpdoc->appendTag($tag); 247 | } 248 | } 249 | } 250 | } 251 | 252 | /** 253 | * Normalize the parameters 254 | * 255 | * @param DocBlock $phpdoc 256 | */ 257 | protected function normalizeParams(DocBlock $phpdoc) 258 | { 259 | //Get the return type and adjust them for beter autocomplete 260 | $paramTags = $phpdoc->getTagsByName('param'); 261 | if ($paramTags) { 262 | /** @var ParamTag $tag */ 263 | foreach ($paramTags as $tag) { 264 | // Convert the keywords 265 | $content = $this->convertKeywords($tag->getContent()); 266 | $tag->setContent($content); 267 | 268 | // Get the expanded type and re-set the content 269 | $content = $tag->getType() . ' ' . $tag->getVariableName() . ' ' . $tag->getDescription(); 270 | $tag->setContent(trim($content)); 271 | } 272 | } 273 | } 274 | 275 | /** 276 | * Normalize the return tag (make full namespace, replace interfaces, resolve $this) 277 | * 278 | * @param DocBlock $phpdoc 279 | */ 280 | protected function normalizeReturn(DocBlock $phpdoc) 281 | { 282 | //Get the return type and adjust them for better autocomplete 283 | $tag = $this->getReturnTag($phpdoc); 284 | 285 | if ($tag === null) { 286 | $this->return = null; 287 | return; 288 | } 289 | 290 | // Get the expanded type 291 | $returnValue = $tag->getType(); 292 | 293 | if (array_key_exists($returnValue, $this->returnTypeNormalizers)) { 294 | $returnValue = $this->returnTypeNormalizers[$returnValue]; 295 | } 296 | 297 | if ($returnValue === '$this') { 298 | $returnValue = $this->root; 299 | } 300 | 301 | // Replace the interfaces 302 | foreach ($this->interfaces as $interface => $real) { 303 | $returnValue = str_replace($interface, $real, $returnValue); 304 | } 305 | 306 | // Set the changed content 307 | $tag->setContent($returnValue . ' ' . $tag->getDescription()); 308 | $this->return = $returnValue; 309 | } 310 | 311 | /** 312 | * Convert keywords that are incorrect. 313 | * 314 | * @param string $string 315 | * @return string 316 | */ 317 | protected function convertKeywords($string) 318 | { 319 | $string = str_replace('\Closure', 'Closure', $string); 320 | $string = str_replace('Closure', '\Closure', $string); 321 | $string = str_replace('dynamic', 'mixed', $string); 322 | 323 | return $string; 324 | } 325 | 326 | /** 327 | * Should the function return a value? 328 | * 329 | * @return bool 330 | */ 331 | public function shouldReturn() 332 | { 333 | if ($this->return !== 'void' && $this->method->name !== '__construct') { 334 | return true; 335 | } 336 | 337 | return false; 338 | } 339 | 340 | /** 341 | * Get the parameters and format them correctly 342 | * 343 | * @param \ReflectionMethod $method 344 | * @return void 345 | */ 346 | public function getParameters($method) 347 | { 348 | //Loop through the default values for parameters, and make the correct output string 349 | $params = []; 350 | $paramsWithDefault = []; 351 | foreach ($method->getParameters() as $param) { 352 | $paramStr = $param->isVariadic() ? '...$' . $param->getName() : '$' . $param->getName(); 353 | $params[] = $paramStr; 354 | if ($param->isOptional() && !$param->isVariadic()) { 355 | $default = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; 356 | if (is_bool($default)) { 357 | $default = $default ? 'true' : 'false'; 358 | } elseif (is_array($default)) { 359 | $default = '[]'; 360 | } elseif (is_null($default)) { 361 | $default = 'null'; 362 | } elseif (is_int($default)) { 363 | //$default = $default; 364 | } elseif (is_resource($default)) { 365 | //skip to not fail 366 | } else { 367 | $default = var_export($default, true); 368 | } 369 | $paramStr .= " = $default"; 370 | } 371 | $paramsWithDefault[] = $paramStr; 372 | } 373 | 374 | $this->params = $params; 375 | $this->params_with_default = $paramsWithDefault; 376 | } 377 | 378 | /** 379 | * @param \ReflectionMethod $reflectionMethod 380 | * @return DocBlock 381 | */ 382 | protected function getInheritDoc($reflectionMethod) 383 | { 384 | $parentClass = $reflectionMethod->getDeclaringClass()->getParentClass(); 385 | 386 | //Get either a parent or the interface 387 | if ($parentClass) { 388 | $method = $parentClass->getMethod($reflectionMethod->getName()); 389 | } else { 390 | $method = $reflectionMethod->getPrototype(); 391 | } 392 | if ($method) { 393 | $namespace = $method->getDeclaringClass()->getNamespaceName(); 394 | $phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases, generics: $this->templateNames)); 395 | 396 | if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) { 397 | //Not at the end yet, try another parent/interface.. 398 | return $this->getInheritDoc($method); 399 | } 400 | 401 | return $phpdoc; 402 | } 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /src/Parsers/PhpDocReturnTypeParser.php: -------------------------------------------------------------------------------- 1 | typeAlias = $typeAlias; 26 | $this->namespaceAliases = $namespaceAliases; 27 | } 28 | 29 | /** 30 | * @return string|null 31 | */ 32 | public function parse(): string|null 33 | { 34 | $matches = []; 35 | preg_match('/(\w+)(<.*>)/', $this->typeAlias, $matches); 36 | $matchCount = count($matches); 37 | 38 | if ($matchCount === 0 || $matchCount === 1) { 39 | return null; 40 | } 41 | 42 | if (empty($this->namespaceAliases[$matches[1]])) { 43 | return null; 44 | } 45 | 46 | return $this->namespaceAliases[$matches[1]] . $this->parseTemplate($matches[2] ?? null); 47 | } 48 | 49 | /** 50 | * @param string|null $template 51 | * @return string 52 | */ 53 | private function parseTemplate(string|null $template): string 54 | { 55 | if ($template === null || $template === '') { 56 | return ''; 57 | } 58 | 59 | $type = ''; 60 | $result = ''; 61 | 62 | foreach (str_split($template) as $char) { 63 | $match = preg_match('/[A-z]/', $char); 64 | 65 | if (!$match) { 66 | $type = $this->namespaceAliases[$type] ?? $type; 67 | $result .= $type; 68 | $result .= $char; 69 | $type = ''; 70 | 71 | continue; 72 | } 73 | 74 | $type .= $char; 75 | } 76 | 77 | return $result; 78 | } 79 | } 80 | --------------------------------------------------------------------------------