├── .phpunit.cache └── test-results ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── pint.json └── src ├── Attributes ├── Attribute.php └── AttributeFinder.php ├── ModelFinder.php ├── ModelInfo.php └── Relations ├── Relation.php └── RelationFinder.php /.phpunit.cache/test-results: -------------------------------------------------------------------------------- 1 | {"version":"pest_2.34.8","defects":[],"times":{"P\\Tests\\RelationTest::__pest_evaluable_it_will_find_no_relations_on_a_model_that_has_none":0.001,"P\\Tests\\RelationTest::__pest_evaluable_it_can_get_the_model_info_of_the_related_model":0.001,"P\\Tests\\RelationTest::__pest_evaluable_it_can_find_the_relations_on_a_model":0.001,"P\\Tests\\ModelFinderTest::__pest_evaluable_it_can_discover_all_models_in_a_directory":0.001,"P\\Tests\\AttributeTest::__pest_evaluable_it_can_get_extended_column_types_for_a_model":0.001,"P\\Tests\\AttributeTest::__pest_evaluable_it_can_get_the_attributes_of_a_model":0.002,"P\\Tests\\AttributeTest::__pest_evaluable_it_can_get_the_accessor_attributes_of_a_model":0.002,"P\\Tests\\AttributeTest::__pest_evaluable_it_can_get_the_mutator_attributes_of_a_model":0.001,"P\\Tests\\AttributeTest::__pest_evaluable_it_can_handle_virtual_attributes_of_a_model":0.001,"P\\Tests\\AttributeTest::__pest_evaluable_it_can_handle_accessor_mutator_combinations":0.001,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_can_get_meta_information_about_all_models":0.006,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_can_get_traits_from_a_model":0.001,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_it_will_return_null_when_getting_a_non_existing_attribute":0.001,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_can_get_a_specific_relation":0.002,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_can_get_extra_info_from_a_model":0.001,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_can_get_a_specific_attribute":0.033,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_it_will_return_null_when_getting_a_non_existing_relation":0.001,"P\\Tests\\ModelInfoTest::__pest_evaluable_it_can_get_meta_information_about_a_model":0.013,"P\\Tests\\AttributeTest::__pest_evaluable_it_phpType_attribute_from_cast_and_after_from_database":0.001,"P\\Tests\\AttributeTest::__pest_evaluable_it_retrieves_phpType_attribute_from_cast_and_falls_back_to_column_type":0.001}} -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-model-info` will be documented in this file. 4 | 5 | ## 2.0.4 - 2025-02-21 6 | 7 | ### What's Changed 8 | 9 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-model-info/pull/43 10 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/laravel-model-info/pull/45 11 | * Bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/spatie/laravel-model-info/pull/46 12 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-model-info/pull/47 13 | 14 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/2.0.3...2.0.4 15 | 16 | ## 2.0.3 - 2024-07-16 17 | 18 | ### What's Changed 19 | 20 | * Retrieve phpType Attribute from Cast, Fallback to Column Type by @clementbirkle in https://github.com/spatie/laravel-model-info/pull/42 21 | 22 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/2.0.2...2.0.3 23 | 24 | ## 2.0.2 - 2024-06-21 25 | 26 | ### What's Changed 27 | 28 | * Bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/spatie/laravel-model-info/pull/38 29 | * Fix phpType Handling for Big Integer Columns by @clementbirkle in https://github.com/spatie/laravel-model-info/pull/41 30 | 31 | ### New Contributors 32 | 33 | * @clementbirkle made their first contribution in https://github.com/spatie/laravel-model-info/pull/41 34 | 35 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/2.0.1...2.0.2 36 | 37 | ## 2.0.1 - 2024-03-07 38 | 39 | ### What's Changed 40 | 41 | * Remove Doctrine DBAL by @hafezdivandari in https://github.com/spatie/laravel-model-info/pull/36 42 | 43 | ### New Contributors 44 | 45 | * @hafezdivandari made their first contribution in https://github.com/spatie/laravel-model-info/pull/36 46 | 47 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/2.0.0...2.0.1 48 | 49 | ## 2.0.0 - 2024-03-04 50 | 51 | ### What's Changed 52 | 53 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-model-info/pull/35 54 | * Bump aglipanci/laravel-pint-action from 1.0.0 to 2.3.1 by @dependabot in https://github.com/spatie/laravel-model-info/pull/33 55 | 56 | ### New Contributors 57 | 58 | * @laravel-shift made their first contribution in https://github.com/spatie/laravel-model-info/pull/35 59 | 60 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.4.4...2.0.0 61 | 62 | ## 1.4.4 - 2024-02-12 63 | 64 | ### What's Changed 65 | 66 | * Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/laravel-model-info/pull/24 67 | * Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/laravel-model-info/pull/27 68 | * Bump dependabot/fetch-metadata from 1.4.0 to 1.5.1 by @dependabot in https://github.com/spatie/laravel-model-info/pull/29 69 | * Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/laravel-model-info/pull/30 70 | * The bug that occurred when any timestamp was defined as null by @MertcanDinler in https://github.com/spatie/laravel-model-info/pull/34 71 | 72 | ### New Contributors 73 | 74 | * @MertcanDinler made their first contribution in https://github.com/spatie/laravel-model-info/pull/34 75 | 76 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.4.3...1.4.4 77 | 78 | ## 1.4.3 - 2023-01-25 79 | 80 | - support L10 81 | 82 | ## 1.4.2 - 2022-09-22 83 | 84 | - fix syntax error 85 | 86 | ## 1.4.1 - 2022-09-21 87 | 88 | ### What's Changed 89 | 90 | - Add Citext support so package works w/ citext types from tpetry/laravel-postgres-enhanced. by @patrickcurl in https://github.com/spatie/laravel-model-info/pull/15 91 | 92 | ### New Contributors 93 | 94 | - @patrickcurl made their first contribution in https://github.com/spatie/laravel-model-info/pull/15 95 | 96 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.4.0...1.4.1 97 | 98 | ## 1.4.0 - 2022-09-19 99 | 100 | ### What's Changed 101 | 102 | - Detect mutators by @pikant in https://github.com/spatie/laravel-model-info/pull/13 103 | 104 | ### New Contributors 105 | 106 | - @pikant made their first contribution in https://github.com/spatie/laravel-model-info/pull/13 107 | 108 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.3.0...1.4.0 109 | 110 | ## 1.3.0 - 2022-09-16 111 | 112 | ### What's Changed 113 | 114 | - Finish implementation of hidden attributes by @Riley19280 in https://github.com/spatie/laravel-model-info/pull/11 115 | 116 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.2.0...1.3.0 117 | 118 | ## 1.2.0 - 2022-09-16 119 | 120 | ### What's Changed 121 | 122 | - Support additional doctrine types by @Riley19280 in https://github.com/spatie/laravel-model-info/pull/10 123 | 124 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.1.0...1.2.0 125 | 126 | ## 1.1.0 - 2022-09-15 127 | 128 | ### What's Changed 129 | 130 | - Add Trait information to model info by @Riley19280 in https://github.com/spatie/laravel-model-info/pull/9 131 | 132 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.0.3...1.1.0 133 | 134 | ## 1.0.3 - 2022-09-14 135 | 136 | ### What's Changed 137 | 138 | - Return 0-indexed array from ModelFinder by @Riley19280 in https://github.com/spatie/laravel-model-info/pull/6 139 | - Support for doctrine/dbal 2 by @Riley19280 in https://github.com/spatie/laravel-model-info/pull/7 140 | 141 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.0.2...1.0.3 142 | 143 | ## 1.0.2 - 2022-09-13 144 | 145 | ### What's Changed 146 | 147 | - Support any level of nested directories by @Riley19280 in https://github.com/spatie/laravel-model-info/pull/5 148 | 149 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.0.1...1.0.2 150 | 151 | ## 1.0.1 - 2022-09-13 152 | 153 | ### What's Changed 154 | 155 | - Bump aglipanci/laravel-pint-action from 0.1.0 to 1.0.0 by @dependabot in https://github.com/spatie/laravel-model-info/pull/3 156 | - Handle abstract model classes by @Riley19280 in https://github.com/spatie/laravel-model-info/pull/4 157 | 158 | ### New Contributors 159 | 160 | - @dependabot made their first contribution in https://github.com/spatie/laravel-model-info/pull/3 161 | - @Riley19280 made their first contribution in https://github.com/spatie/laravel-model-info/pull/4 162 | 163 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/1.0.0...1.0.1 164 | 165 | ## 1.0.0 - 2022-09-07 166 | 167 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/0.0.5...1.0.0 168 | 169 | ## 0.0.5 - 2022-09-07 170 | 171 | ### What's Changed 172 | 173 | - Add "php type" to attribute by @iDevelopThings in https://github.com/spatie/laravel-model-info/pull/2 174 | 175 | ### New Contributors 176 | 177 | - @iDevelopThings made their first contribution in https://github.com/spatie/laravel-model-info/pull/2 178 | 179 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/0.0.4...0.0.5 180 | 181 | ## 0.0.4 - 2022-08-09 182 | 183 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/0.0.3...0.0.4 184 | 185 | ## 0.0.3 - 2022-08-09 186 | 187 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/0.0.2...0.0.3 188 | 189 | ## 0.0.2 - 2022-08-09 190 | 191 | **Full Changelog**: https://github.com/spatie/laravel-model-info/compare/0.0.1...0.0.2 192 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 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 | # Get information about the models in your Laravel app 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-model-info.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-model-info) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-model-info.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-model-info) 5 | 6 | Using this package you can determine which attributes and relations your model classes have. 7 | 8 | ```php 9 | use Spatie\ModelInfo\ModelInfo; 10 | 11 | $modelInfo = ModelInfo::forModel(YourModel::class); 12 | 13 | $modelInfo->fileName; // returns the filename that contains your model 14 | $modelInfo->tableName; // returns the name of the table your models are stored in 15 | $modelInfo->attributes; // returns a collection of `Attribute` objects 16 | $modelInfo->relations; // returns a collection of `Relation` objects 17 | ``` 18 | 19 | Here's how you can get information about the attributes: 20 | 21 | ```php 22 | $modelInfo->attributes->first()->name; // returns the name of the first attribute 23 | $modelInfo->attributes->first()->type; // returns the type of the first attribute (string, integer, ...) 24 | ``` 25 | 26 | Here's how you can get information about the relations 27 | 28 | ```php 29 | // returns the name of the first relation, eg. `author` 30 | $modelInfo->relations->first()->name; 31 | 32 | // returns the type of the 33 | // first relation, eg. `BelongsTo` 34 | $modelInfo->relations->first()->type; 35 | 36 | // returns the related model of the 37 | // first relation, eg. `App\Models\User` 38 | $modelInfo->relations->first()->related; 39 | ``` 40 | 41 | Additionally, the package can also discover all the models in your application. 42 | 43 | ```php 44 | use Spatie\ModelInfo\ModelFinder; 45 | 46 | // returns a `Illuminate\Support\Collection` containing all 47 | // the class names of all your models. 48 | $models = ModelFinder::all(); 49 | ``` 50 | 51 | ## Support us 52 | 53 | [](https://spatie.be/github-ad-click/laravel-model-info) 54 | 55 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 56 | 57 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 58 | 59 | ## Installation 60 | 61 | You can install the package via composer: 62 | 63 | ```bash 64 | composer require spatie/laravel-model-info 65 | ``` 66 | 67 | ## Usage 68 | 69 | You can get information about a model by calling `forModel`: 70 | 71 | ```php 72 | use Spatie\ModelInfo\ModelInfo; 73 | 74 | $modelInfo = ModelInfo::forModel(YourModel::class); 75 | 76 | $modelInfo->fileName; // returns the filename that contains your model 77 | $modelInfo->tableName; // returns the name of the table your models are stored in 78 | $modelInfo->attributes; // returns a collection of `Attribute` objects 79 | $modelInfo->relations; // returns a collection of `Relation` objects 80 | ``` 81 | 82 | > **Note** 83 | > 84 | > This package discovers relationships by their return type. Make sure that, in all your models each method that returns a relation has a return type. 85 | 86 | ### Getting a specific attribute 87 | 88 | ```php 89 | use Spatie\ModelInfo\ModelInfo; 90 | 91 | // returns an instance of `Spatie\ModelInfo\Attributes\Attribute` 92 | ModelInfo::forModel(BlogPost::class)->attribute('name'); 93 | ``` 94 | 95 | ### Getting a specific relation 96 | 97 | You can get a specific relation using the `relation` method. 98 | 99 | ```php 100 | use Spatie\ModelInfo\ModelInfo; 101 | 102 | // returns an instance of `Spatie\ModelInfo\Relations\Relation` 103 | ModelInfo::forModel(BlogPost::class)->relation('user') 104 | ``` 105 | 106 | ### Attributes 107 | 108 | A `Spatie\ModelInfo\Attributes\Attribute` object has these properties: 109 | 110 | - `name` 111 | - `type` 112 | - `increments` 113 | - `nullable` 114 | - `default` 115 | - `unique` 116 | - `fillable` 117 | - `appended` 118 | - `cast` 119 | - `virtual` 120 | 121 | ### Relationships 122 | 123 | A `Spatie\ModelInfo\Relations\Relation` object has these properties: 124 | 125 | - `name` 126 | - `type` 127 | - `related` 128 | 129 | It also has a `relatedModelInfo()` method that gives a `ModelInfo` instance for the related model. 130 | 131 | ```php 132 | use Spatie\ModelInfo\ModelInfo; 133 | 134 | ModelInfo::forModel(BlogPost::class) 135 | ->relation('user') 136 | ->relatedModelInfo() // returns the `ModelInfo` for the `User` model 137 | ``` 138 | 139 | ## Discovering all models in your application 140 | 141 | Using this method we'll discover all methods in your project, no matter in which directory they are stored. 142 | 143 | ```php 144 | use Spatie\ModelInfo\ModelFinder; 145 | 146 | // returns a `Illuminate\Support\Collection` containing 147 | // all the class names of all your models. 148 | $models = ModelFinder::all(); 149 | ``` 150 | 151 | ## Getting information on all model in your application 152 | 153 | The `ModelInfo` class can get information about all models in your application. 154 | 155 | ```php 156 | use Spatie\ModelInfo\ModelInfo; 157 | 158 | ModelInfo::forAllModels(); // returns a collection of `ModelInfo` instances 159 | ``` 160 | 161 | ## Adding extra info on a model 162 | 163 | To add extra info on a model, add a method `extraModelInfo` to your model. It can return anything you want: an string, an object, an array. 164 | 165 | ```php 166 | // in your model 167 | 168 | public function extraModelInfo() 169 | { 170 | return 'anything you want'; 171 | } 172 | ``` 173 | 174 | The returned value will be available on the `extra` property of a `ModelInfo` instance. 175 | 176 | ```php 177 | use Spatie\ModelInfo\ModelInfo; 178 | 179 | $modelInfo = ModelInfo::forModel(YourModel::class); 180 | 181 | $modelInfo->extra; // returns 'anything you want' 182 | ``` 183 | 184 | ## Testing 185 | 186 | ```bash 187 | composer test 188 | ``` 189 | 190 | ## Changelog 191 | 192 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 193 | 194 | ## Contributing 195 | 196 | Please see [CONTRIBUTING](https://github.com/freekmurze/.github/blob/main/CONTRIBUTING.md) for details. 197 | 198 | ## Security Vulnerabilities 199 | 200 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 201 | 202 | ## Credits 203 | 204 | - [Freek Van der Herten](https://github.com/freekmurze) 205 | - [All Contributors](../../contributors) 206 | 207 | This package contains code taken from the `model:show` command of [Laravel](https://github.com/laravel/framework). 208 | 209 | ## License 210 | 211 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 212 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-model-info", 3 | "description": "Get information about the models in your Laravel app", 4 | "keywords": [ 5 | "spatie", 6 | "laravel", 7 | "laravel-model-info" 8 | ], 9 | "homepage": "https://github.com/spatie/laravel-model-info", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Freek Van der Herten", 14 | "email": "freek@spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "illuminate/database": "^11.0|^12.0", 21 | "spatie/laravel-package-tools": "^1.13.0" 22 | }, 23 | "require-dev": { 24 | "laravel/pint": "^1.0", 25 | "nunomaduro/collision": "^8.0", 26 | "orchestra/testbench": "^9.0|^10.0", 27 | "pestphp/pest": "^2.34|^3.7", 28 | "pestphp/pest-plugin-laravel": "^2.3|^3.1", 29 | "spatie/pest-plugin-snapshots": "^2.1", 30 | "symfony/serializer": "^6|^7.0" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Spatie\\ModelInfo\\": "src" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Spatie\\ModelInfo\\Tests\\": "tests" 40 | } 41 | }, 42 | "scripts": { 43 | "analyse": "vendor/bin/phpstan analyse", 44 | "test": "vendor/bin/pest", 45 | "test-coverage": "vendor/bin/pest --coverage", 46 | "format": "vendor/bin/pint" 47 | }, 48 | "config": { 49 | "sort-packages": true, 50 | "allow-plugins": { 51 | "pestphp/pest-plugin": true 52 | } 53 | }, 54 | "minimum-stability": "dev", 55 | "prefer-stable": true 56 | } 57 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "build" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/Attributes/Attribute.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function toArray(): array 32 | { 33 | return get_object_vars($this); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Attributes/AttributeFinder.php: -------------------------------------------------------------------------------- 1 | |Model $model 19 | * @return \Illuminate\Support\Collection 20 | */ 21 | public static function forModel(string|Model $model): Collection 22 | { 23 | if (is_string($model)) { 24 | $model = new $model; 25 | } 26 | 27 | return (new self)->attributes($model); 28 | } 29 | 30 | /** 31 | * @return \Illuminate\Support\Collection 32 | */ 33 | protected function attributes(Model $model): Collection 34 | { 35 | $schema = $model->getConnection()->getSchemaBuilder(); 36 | $table = $model->getTable(); 37 | 38 | $columns = $schema->getColumns($table); 39 | $indexes = $schema->getIndexes($table); 40 | 41 | return collect($columns) 42 | ->values() 43 | ->map(function (array $column) use ($model, $indexes) { 44 | $columnIndexes = $this->getIndexes($column['name'], $indexes); 45 | $cast = $this->getCastType($column['name'], $model); 46 | 47 | return new Attribute( 48 | name: $column['name'], 49 | phpType: $this->getPhpType($cast, $column), 50 | type: $column['type'], 51 | increments: $column['auto_increment'], 52 | nullable: $column['nullable'], 53 | default: $this->getColumnDefault($column, $model), 54 | primary: $columnIndexes->contains(fn (array $index) => $index['primary']), 55 | unique: $columnIndexes->contains(fn (array $index) => $index['unique']), 56 | fillable: $model->isFillable($column['name']), 57 | appended: null, 58 | cast: $cast, 59 | virtual: false, 60 | hidden: $this->attributeIsHidden($column['name'], $model) 61 | ); 62 | }) 63 | ->merge($this->getVirtualAttributes($model, $columns)); 64 | } 65 | 66 | protected function getPhpType(?string $cast, array $column): string 67 | { 68 | $type = $this->getPhpTypeFromCast($cast); 69 | 70 | $type ??= $this->getPhpTypeFromColumn($column); 71 | 72 | return $type; 73 | } 74 | 75 | protected function getPhpTypeFromCast(?string $cast): ?string 76 | { 77 | if (! $cast) { 78 | return null; 79 | } 80 | 81 | $castFirstPart = explode(':', $cast)[0]; 82 | 83 | $type = match ($castFirstPart) { 84 | 'array' => 'array', 85 | 'boolean' => 'bool', 86 | 'float', 'decimal', 'double', 'real' => 'float', 87 | 'integer' => 'int', 88 | 'object' => 'object', 89 | 'AsStringable' => '\\'.AsStringable::class, 90 | 'collection', 'AsEnumCollection' => '\\'.Collection::class, 91 | 'date', 'datetime', 'timestamp', 'immutable_date', 'immutable_datetime' => '\\'.CarbonInterface::class, 92 | default => null, 93 | }; 94 | 95 | $type ??= match ($cast) { 96 | 'encrypted:array' => 'array', 97 | 'encrypted:collection', 'AsEncryptedCollection' => '\\'.Collection::class, 98 | 'encrypted:object', 'AsEncryptedArrayObject' => 'object', 99 | default => null, 100 | }; 101 | 102 | $type ??= enum_exists($cast) ? '\\'.$cast : null; 103 | 104 | return $type; 105 | } 106 | 107 | protected function getPhpTypeFromColumn(array $column): string 108 | { 109 | $type = match ($column['type']) { 110 | 'tinyint(1)', 'bit' => 'bool', 111 | default => null, 112 | }; 113 | 114 | $type ??= match ($column['type_name']) { 115 | 'tinyint', 'integer', 'int', 'int4', 'smallint', 'int2', 'mediumint', 'bigint', 'int8' => 'int', 116 | 'float', 'real', 'float4', 'double', 'float8' => 'float', 117 | 'binary', 'varbinary', 'bytea', 'image', 'blob', 'tinyblob', 'mediumblob', 'longblob' => 'resource', 118 | 'boolean', 'bool' => 'bool', 119 | 'date', 'time', 'timetz', 'datetime', 'datetime2', 'smalldatetime', 'datetimeoffset', 'timestamp', 'timestamptz' => '\\'.CarbonInterface::class, 120 | 'json', 'jsonb' => 'mixed', 121 | // 'char', 'bpchar', 'nchar', 122 | // 'varchar', 'nvarchar', 123 | // 'text', 'tinytext', 'longtext', 'mediumtext', 'ntext', 124 | // 'decimal', 'numeric', 125 | // 'money', 'smallmoney', 126 | // 'uuid', 'uniqueidentifier', 127 | // 'enum', 128 | // 'set', 129 | // 'inet', 'inet4', 'inet6', 'cidr', 'macaddr', 'macaddr8', 130 | // 'xml', 131 | // 'bit', 'varbit', 132 | // 'year', 133 | // 'interval', 134 | // 'geometry', 'geometrycollection', 'linestring', 'multilinestring', 'multipoint', 'multipolygon', 'point', 'polygon', 135 | // 'box', 'circle', 'line', 'lseg', 'path', 136 | // 'geography', 137 | // 'tsvector', 'tsquery' => 'string', 138 | default => null, 139 | }; 140 | 141 | return $type ?? 'string'; 142 | } 143 | 144 | protected function getColumnDefault(array $column, Model $model): mixed 145 | { 146 | $attributeDefault = $model->getAttributes()[$column['name']] ?? null; 147 | 148 | return match (true) { 149 | $attributeDefault instanceof BackedEnum => $attributeDefault->value, 150 | $attributeDefault instanceof UnitEnum => $attributeDefault->name, 151 | default => $attributeDefault ?? $column['default'], 152 | }; 153 | } 154 | 155 | /** 156 | * @return Collection 157 | */ 158 | protected function getIndexes(string $column, array $indexes) 159 | { 160 | return collect($indexes) 161 | ->filter(fn (array $index) => count($index['columns']) === 1 && $index['columns'][0] === $column); 162 | } 163 | 164 | protected function attributeIsHidden(string $attribute, Model $model): bool 165 | { 166 | if (count($model->getHidden()) > 0) { 167 | return in_array($attribute, $model->getHidden()); 168 | } 169 | 170 | if (count($model->getVisible()) > 0) { 171 | return ! in_array($attribute, $model->getVisible()); 172 | } 173 | 174 | return false; 175 | } 176 | 177 | protected function getCastType(string $column, Model $model): ?string 178 | { 179 | if ($model->hasGetMutator($column) || $model->hasSetMutator($column)) { 180 | return 'accessor'; 181 | } 182 | 183 | if ($model->hasAttributeMutator($column)) { 184 | return 'attribute'; 185 | } 186 | 187 | return $this->getCastsWithDates($model)->get($column) ?? null; 188 | } 189 | 190 | protected function getCastsWithDates(Model $model): Collection 191 | { 192 | return collect($model->getDates()) 193 | ->whereNotNull() 194 | ->flip() 195 | ->map(fn () => 'datetime') 196 | ->merge($model->getCasts()); 197 | } 198 | 199 | /** 200 | * @return Collection 201 | */ 202 | protected function getVirtualAttributes(Model $model, array $columns): Collection 203 | { 204 | $class = new ReflectionClass($model); 205 | 206 | return collect($class->getMethods()) 207 | ->reject( 208 | fn (ReflectionMethod $method) => $method->isStatic() 209 | || $method->isAbstract() 210 | || $method->getDeclaringClass()->getName() !== get_class($model) 211 | ) 212 | ->map(function (ReflectionMethod $method) use ($model) { 213 | if (preg_match('/(?<=^|;)get([^;]+?)Attribute(;|$)/', $method->getName(), $matches) === 1) { 214 | return [ 215 | 'name' => Str::snake($matches[1]), 216 | 'cast_type' => 'accessor', 217 | 'php_type' => $method->getReturnType()?->getName(), 218 | ]; 219 | } 220 | 221 | if (preg_match('/(?<=^|;)set([^;]+?)Attribute(;|$)/', $method->getName(), $matches) === 1) { 222 | return [ 223 | 'name' => Str::snake($matches[1]), 224 | 'cast_type' => 'mutator', 225 | 'php_type' => collect($method->getParameters())->firstWhere('name', 'value')?->getType()?->__toString(), 226 | ]; 227 | } 228 | 229 | if ($model->hasAttributeMutator($method->getName())) { 230 | return [ 231 | 'name' => Str::snake($method->getName()), 232 | 'cast_type' => 'attribute', 233 | 'php_type' => null, 234 | ]; 235 | } 236 | 237 | return []; 238 | }) 239 | ->reject(fn ($cast) => ! isset($cast['name']) || collect($columns)->has($cast['name'])) 240 | ->map(fn ($cast) => new Attribute( 241 | name: $cast['name'], 242 | phpType: $cast['php_type'] ?? null, 243 | type: null, 244 | increments: false, 245 | nullable: null, 246 | default: null, 247 | primary: null, 248 | unique: null, 249 | fillable: $model->isFillable($cast['name']), 250 | appended: $model->hasAppended($cast['name']), 251 | cast: $cast['cast_type'], 252 | virtual: true, 253 | hidden: $this->attributeIsHidden($cast['name'], $model) 254 | )) 255 | // Convert duplicate entries for accessor-mutator combinations 256 | ->groupBy('name') 257 | ->flatMap(function (Collection $items) { 258 | if ($items->count() !== 2) { 259 | return $items; 260 | } 261 | 262 | if (! $items->firstWhere('cast', 'accessor') || ! $items->firstWhere('cast', 'mutator')) { 263 | return $items; 264 | } 265 | 266 | $attribute = $items->first(); 267 | $attribute->phpType = $items[0]->phpType ?? $items[1]->phpType; 268 | $attribute->cast = 'attribute'; 269 | 270 | return [$attribute]; 271 | }); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/ModelFinder.php: -------------------------------------------------------------------------------- 1 | > */ 18 | public static function all( 19 | ?string $directory = null, 20 | ?string $basePath = null, 21 | ?string $baseNamespace = null, 22 | ): Collection { 23 | $directory ??= app_path(); 24 | $basePath ??= base_path(); 25 | $baseNamespace ??= ''; 26 | 27 | return collect(static::getFilesRecursively($directory)) 28 | ->map(fn (string $class) => new SplFileInfo($class)) 29 | ->map(fn (SplFileInfo $file) => self::fullQualifiedClassNameFromFile($file, $basePath, $baseNamespace)) 30 | ->map(function (string $class) { 31 | try { 32 | return new ReflectionClass($class); 33 | } catch (Exception|Error) { 34 | return null; 35 | } 36 | }) 37 | ->filter() 38 | ->filter(fn (ReflectionClass $class) => $class->isSubclassOf(Model::class)) 39 | ->filter(fn (ReflectionClass $class) => ! $class->isAbstract()) 40 | ->map(fn (ReflectionClass $reflectionClass) => $reflectionClass->getName()) 41 | ->values(); 42 | } 43 | 44 | protected static function fullQualifiedClassNameFromFile( 45 | SplFileInfo $file, 46 | string $basePath, 47 | string $baseNamespace 48 | ): string { 49 | return Str::of($file->getRealPath()) 50 | ->replaceFirst($basePath, '') 51 | ->replaceLast('.php', '') 52 | ->trim(DIRECTORY_SEPARATOR) 53 | ->ucfirst() 54 | ->replace( 55 | [DIRECTORY_SEPARATOR, 'App\\'], 56 | ['\\', app()->getNamespace()], 57 | ) 58 | ->prepend($baseNamespace.'\\'); 59 | } 60 | 61 | protected static function getFilesRecursively(string $path): array 62 | { 63 | $rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); 64 | $files = []; 65 | 66 | foreach ($rii as $file) { 67 | if ($file->isDir()) { 68 | continue; 69 | } 70 | $files[] = $file->getPathname(); 71 | } 72 | 73 | return $files; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/ModelInfo.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public static function forAllModels( 19 | ?string $directory = null, 20 | ?string $basePath = null, 21 | ?string $baseNamespace = null 22 | ): Collection { 23 | return ModelFinder::all($directory, $basePath, $baseNamespace) 24 | ->map(function (string $model) { 25 | return self::forModel($model); 26 | }); 27 | } 28 | 29 | /** 30 | * @param class-string|Model|ReflectionClass $model 31 | */ 32 | public static function forModel(string|Model|ReflectionClass $model): self 33 | { 34 | if ($model instanceof ReflectionClass) { 35 | $model = $model->getName(); 36 | } 37 | 38 | if (is_string($model)) { 39 | $model = new $model; 40 | } 41 | 42 | return new self( 43 | $model::class, 44 | (new ReflectionClass($model))->getFileName(), 45 | $model->getConnection()->getName(), 46 | $model->getConnection()->getTablePrefix().$model->getTable(), 47 | RelationFinder::forModel($model), 48 | AttributeFinder::forModel($model), 49 | self::getTraits($model), 50 | self::getExtraModelInfo($model), 51 | ); 52 | } 53 | 54 | public function __construct( 55 | public string $class, 56 | public string $fileName, 57 | public string $connectionName, 58 | public string $tableName, 59 | public Collection $relations, 60 | public Collection $attributes, 61 | public Collection $traits, 62 | public mixed $extra = null, 63 | ) {} 64 | 65 | protected static function getExtraModelInfo(Model $model): mixed 66 | { 67 | if (method_exists($model, 'extraModelInfo')) { 68 | return $model->extraModelInfo(); 69 | } 70 | 71 | return null; 72 | } 73 | 74 | protected static function getTraits(Model $model): Collection 75 | { 76 | return collect(array_values(class_uses($model))); 77 | } 78 | 79 | public function toArray(): array 80 | { 81 | $properties = get_object_vars($this); 82 | $properties['relations'] = $properties['relations']->toArray(); 83 | $properties['attributes'] = $properties['attributes']->toArray(); 84 | 85 | return $properties; 86 | } 87 | 88 | public function attribute(string $name): ?Attribute 89 | { 90 | return $this->attributes->first( 91 | fn (Attribute $attribute) => $attribute->name === $name 92 | ); 93 | } 94 | 95 | public function relation(string $name): ?Relation 96 | { 97 | return $this->relations->first( 98 | fn (Relation $relation) => $relation->name === $name 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Relations/Relation.php: -------------------------------------------------------------------------------- 1 | related); 22 | } 23 | 24 | /** @return array */ 25 | public function toArray(): array 26 | { 27 | return get_object_vars($this); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Relations/RelationFinder.php: -------------------------------------------------------------------------------- 1 | |Model $model 18 | * @return Collection 19 | */ 20 | public static function forModel(string|Model $model): Collection 21 | { 22 | if (is_string($model)) { 23 | $model = new $model; 24 | } 25 | 26 | return (new self)->relations($model); 27 | } 28 | 29 | /** 30 | * @return Collection 31 | */ 32 | public function relations(Model $model): Collection 33 | { 34 | $class = new ReflectionClass($model); 35 | 36 | return collect($class->getMethods()) 37 | ->filter(fn (ReflectionMethod $method) => $this->hasRelationReturnType($method)) 38 | ->map(function (ReflectionMethod $method) use ($model) { 39 | /** @var \Illuminate\Database\Eloquent\Relations\BelongsTo $relation */ 40 | $relation = $method->invoke($model); 41 | 42 | return new Relation( 43 | $method->getName(), 44 | $method->getReturnType(), 45 | $relation->getRelated()::class, 46 | ); 47 | }); 48 | } 49 | 50 | protected function hasRelationReturnType( 51 | ReflectionMethod $method) 52 | { 53 | if ($method->getReturnType() instanceof ReflectionNamedType) { 54 | $returnType = $method->getReturnType()->getName(); 55 | 56 | return is_a($returnType, IlluminateRelation::class, true); 57 | } 58 | 59 | if ($method->getReturnType() instanceof ReflectionUnionType) { 60 | foreach ($method->getReturnType()->getTypes() as $type) { 61 | $returnType = $type->getName(); 62 | 63 | if (is_a($returnType, IlluminateRelation::class, true)) { 64 | return true; 65 | } 66 | } 67 | } 68 | 69 | return false; 70 | } 71 | } 72 | --------------------------------------------------------------------------------