Skip to content

Commit

Permalink
add ModelInspector
Browse files Browse the repository at this point in the history
  • Loading branch information
ayrtonandino committed Nov 28, 2024
1 parent 6963f23 commit f324c1c
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 40 deletions.
40 changes: 14 additions & 26 deletions src/Actions/BuildModelDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

namespace FumeApp\ModelTyper\Actions;

use FumeApp\ModelTyper\Exceptions\AbstractModelException;
use FumeApp\ModelTyper\Exceptions\NestedCommandException;
use FumeApp\ModelTyper\Traits\ClassBaseName;
use FumeApp\ModelTyper\Traits\ModelRefClass;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use ReflectionException;
use Symfony\Component\Finder\SplFileInfo;

Expand Down Expand Up @@ -38,23 +37,18 @@ public function __invoke(SplFileInfo $modelFile, bool $resolveAbstract = false):
$columns = collect($modelDetails['attributes'])->filter(fn ($att) => in_array($att['name'], $databaseColumns));
$nonColumns = collect($modelDetails['attributes'])->filter(fn ($att) => ! in_array($att['name'], $databaseColumns));
$relations = collect($modelDetails['relations']);
$interfaces = collect($laravelModel->interfaces)->map(fn ($interface, $key) => [

$interfaces = collect($laravelModel->interfaces ?? [])->map(fn ($interface, $key) => [
'name' => $key,
'type' => $interface['type'] ?? 'unknown',
'nullable' => $interface['nullable'] ?? false,
'import' => $interface['import'] ?? null,
'forceType' => true,
]);

$imports = $interfaces->filter(function ($interface) {
return isset($interface['import']);
})
->map(function ($interface) {
return [
'import' => $interface['import'],
'type' => $interface['type'],
];
})
$imports = $interfaces
->filter(fn (array $interface): bool => isset($interface['import']))
->map(fn (array $interface): array => ['import' => $interface['import'], 'type' => $interface['type']])
->unique()
->values();

Expand All @@ -71,28 +65,22 @@ public function __invoke(SplFileInfo $modelFile, bool $resolveAbstract = false):
'columns' => $columns,
'nonColumns' => $nonColumns,
'relations' => $relations,
'interfaces' => $interfaces->values(),
'interfaces' => $interfaces,
'imports' => $imports,
];
}

/**
* @throws NestedCommandException
* @return array{"class": class-string<\Illuminate\Database\Eloquent\Model>, database: string, table: string, policy: class-string|null, attributes: \Illuminate\Support\Collection, relations: \Illuminate\Support\Collection, events: \Illuminate\Support\Collection, observers: \Illuminate\Support\Collection, collection: class-string<\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model>>, builder: class-string<\Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model>>}|null
*/
private function getModelDetails(SplFileInfo $modelFile, bool $resolveAbstract): ?array
{
$modelFileArg = $modelFile->getRelativePathname();
$modelFileArg = app()->getNamespace() . $modelFileArg;
$modelFileArg = str_replace('.php', '', $modelFileArg);

try {
return app(RunModelShowCommand::class)($modelFileArg, $resolveAbstract);
} catch (NestedCommandException $exception) {
if ($exception->wasCausedBy(AbstractModelException::class) && ! $resolveAbstract) {
return null;
}
throw $exception;
}
$modelFile = Str::of(app()->getNamespace())
->append($modelFile->getRelativePathname())
->replace('.php', '')
->toString();

return app(RunModelInspector::class)($modelFile, $resolveAbstract);
}

private function overrideCollectionWithInterfaces(Collection $columns, Collection $interfaces): Collection
Expand Down
18 changes: 9 additions & 9 deletions src/Actions/DetermineAccessorType.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Exception;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;

class DetermineAccessorType
Expand All @@ -13,26 +14,25 @@ class DetermineAccessorType
* Determine the type of accessor.
*
* @see https://laravel.com/docs/9.x/eloquent-mutators#defining-an-accessor
* @see https://laravel.com/docs/8.x/eloquent-mutators#defining-an-accessor
*
* @param \ReflectionClass<\Illuminate\Database\Eloquent\Model> $reflectionModel
*
* @throws Exception
*/
public function __invoke(ReflectionClass $reflectionModel, string $mutator): ReflectionMethod
{
$mutator = Str::studly($mutator);

// Try traditional
try {
$accessor = 'get' . Str::studly($mutator) . 'Attribute';

return $reflectionModel->getMethod($accessor);
} catch (Exception $e) {
return $reflectionModel->getMethod('get' . $mutator . 'Attribute');
} catch (ReflectionException $e) {
}

// Try new
try {
$method = Str::studly($mutator);

return $reflectionModel->getMethod($method);
} catch (Exception $e) {
return $reflectionModel->getMethod($mutator);
} catch (ReflectionException $e) {
}

throw new Exception('Accessor method for ' . $mutator . ' on model ' . $reflectionModel->getName() . ' does not exist');
Expand Down
26 changes: 26 additions & 0 deletions src/Actions/RunModelInspector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace FumeApp\ModelTyper\Actions;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Eloquent\ModelInspector;

class RunModelInspector
{
public function __construct(protected ?Application $app = null)
{
$this->app = $app ?? app();
}

/**
* Run internal Laravel ModelInspector class.
*
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate\Database\Eloquent\ModelInspector.php
*
* @return array{"class": class-string<\Illuminate\Database\Eloquent\Model>, database: string, table: string, policy: class-string|null, attributes: \Illuminate\Support\Collection, relations: \Illuminate\Support\Collection, events: \Illuminate\Support\Collection, observers: \Illuminate\Support\Collection, collection: class-string<\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model>>, builder: class-string<\Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model>>}|null
*/
public function __invoke(string $model, bool $resolveAbstract = false): ?array
{
return app(ModelInspector::class)->inspect($model);
}
}
1 change: 1 addition & 0 deletions src/Actions/WriteColumnAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class WriteColumnAttribute
/**
* Get model columns and attributes to the output.
*
* @param \ReflectionClass<\Illuminate\Database\Eloquent\Model> $reflectionModel
* @param array{name: string, type: string, increments: bool, nullable: bool, default: mixed, unique: bool, fillable: bool, hidden?: bool, appended: mixed, cast?: string|null, forceType?: bool} $attribute
* @param array<string, string> $mappings
* @return array{array{name: string, type: string}, ReflectionClass|null}|array{string, ReflectionClass|null}|array{null, null}
Expand Down
24 changes: 24 additions & 0 deletions src/Overrides/ModelInspector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace FumeApp\ModelTyper\Overrides;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Eloquent\ModelInspector as EloquentModelInspector;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class ModelInspector extends EloquentModelInspector
{
/**
* Create a new model inspector instance.
*/
public function __construct(?Application $app = null)
{
$this->relationMethods = collect(Arr::flatten(config('modeltyper.custom_relationships', [])))
->map(fn (string $method): string => Str::trim($method))
->merge($this->relationMethods)
->toArray();

parent::__construct($app ?? app());
}
}
7 changes: 3 additions & 4 deletions src/Traits/ModelRefClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ trait ModelRefClass
/**
* Get the reflection interface.
*
* @param array<string, mixed> $info
* @param array{"class": class-string<\Illuminate\Database\Eloquent\Model>, database: string, table: string, policy: class-string|null, attributes: \Illuminate\Support\Collection, relations: \Illuminate\Support\Collection, events: \Illuminate\Support\Collection, observers: \Illuminate\Support\Collection, collection: class-string<\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model>>, builder: class-string<\Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model>>} $info
* @return \ReflectionClass<\Illuminate\Database\Eloquent\Model>
*/
public function getRefInterface(array $info): ReflectionClass
{
$class = $info['class'];

return new ReflectionClass($class);
return new ReflectionClass($info['class']);
}
}
34 changes: 34 additions & 0 deletions test/Tests/Feature/Actions/RunModelInspectorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Tests\Feature\Actions;

use App\Models\AbstractModel;
use App\Models\User;
use FumeApp\ModelTyper\Actions\RunModelInspector;
use Tests\TestCase;

class RunModelInspectorTest extends TestCase
{
public function test_action_can_be_resolved_by_application()
{
$this->assertInstanceOf(RunModelInspector::class, resolve(RunModelInspector::class));
}

public function test_action_can_be_executed()
{
$action = app(RunModelInspector::class);
$result = $action(User::class);

$this->assertNotEmpty($result);
}

public function test_trying_to_execute_action_with_an_abstract_model_results_in_exception()
{
$this->markTestIncomplete();

// $action = app(RunModelInspector::class);

// $this->expectException(NestedCommandException::class);
// $action(AbstractModel::class);
}
}
3 changes: 2 additions & 1 deletion test/Tests/Traits/ResolveClassAsReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ trait ResolveClassAsReflection
/**
* Resolve a class as a ReflectionClass.
*
* @param class-string $model
* @param class-string<\Illuminate\Database\Eloquent\Model> $model
* @return \ReflectionClass<\Illuminate\Database\Eloquent\Model>
*/
public function resolveClassAsReflection(string $model): ReflectionClass
{
Expand Down

0 comments on commit f324c1c

Please sign in to comment.