Skip to content

Commit c8890ee

Browse files
committed
chore: PHPStan fixes
1 parent 08a4fdf commit c8890ee

File tree

9 files changed

+259
-17
lines changed

9 files changed

+259
-17
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<img src="articulate.png">
1+
<img src="articulate.png" width="100%">
22

33
![Packagist Version](https://img.shields.io/packagist/v/articulate/concise)
44
![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/articulate/concise)
@@ -11,7 +11,11 @@
1111
![Static Analysis](https://github.com/articulate-laravel/concise/actions/workflows/static-analysis.yml/badge.svg)
1212

1313
# Articulate: Concise
14-
### A super lightweight data mapper ORM for Laravel
14+
Articulate: Concise is a super lightweight data mapper ORM for Laravel.
15+
It exists as a package entirely because I wondered how lightweight of a solution I can build, that gives me the
16+
benefit of a data mapper ORM, without using a full ORM.
1517

16-
This package is currently under development.
17-
Check back in the future for updates.
18+
Concise is extensible and flexible, and will most likely serve as the basis for Articulate in the future, but it comes
19+
with many sensible defaults, and a sprinkle of magic that'll simplify using it based on certain conventions.
20+
It also comes with a driver for Laravel's auth functionality,
21+
and route entity binding (route model binding but for concise entities).

src/Concerns/SupportsAuth.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ public function findByCredentials(#[SensitiveParameter] array $credentials): ?ob
9090
{
9191
$credentials = array_filter(
9292
$credentials,
93-
fn ($key) => ! str_contains($key, 'password'),
93+
static fn ($key) => ! str_contains($key, 'password'),
9494
ARRAY_FILTER_USE_KEY
9595
);
9696

9797
if (empty($credentials)) {
98-
return;
98+
return null;
9999
}
100100

101101
// First we will add each credential element to the query as a where clause.

src/Concise.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ public function identified(EntityMapper $mapper, array $data): object
142142
// Get the identity from the data
143143
$identity = $mapper->identity($data);
144144

145+
if ($identity === null) {
146+
throw new InvalidArgumentException('No identity could be found in the provided data.');
147+
}
148+
145149
// Retrieve an existing entity if one does exist
146150
$existing = $this->identities->get($mapper->class(), $identity);
147151

@@ -237,14 +241,7 @@ public function repository(string $class): Repository
237241
}
238242

239243
$repositoryClass = $mapper->repository();
240-
241-
/**
242-
* This has to be here because Application::make() expects a 'string',
243-
* not a 'class-string', which is...well, yeah, you know exactly what it
244-
* is.
245-
*
246-
* @phpstan-ignore argument.type
247-
*/
244+
248245
return $this->repositories[$class] = new $repositoryClass(
249246
concise : $this,
250247
mapper : $mapper,

src/Contracts/AuthRepository.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function findByRememberToken(#[SensitiveParameter] string $token): ?objec
4747
/**
4848
* Find an identity by its credentials
4949
*
50-
* @param array $credentials
50+
* @param array<string, mixed> $credentials
5151
*
5252
* @return object|null
5353
*

src/Contracts/EntityMapper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public function table(): string;
4242
* @phpstan-param array<string, mixed>|EntityType $data
4343
*
4444
* @return int|string|null
45+
*
46+
* @TODO Think about a better way to handle this
4547
*/
4648
public function identity(array|object $data): int|string|null;
4749
}

src/Contracts/Identity.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Articulate\Concise\Contracts;
4+
5+
use Stringable;
6+
7+
interface Identity extends Stringable
8+
{
9+
public function toKey(): string|int;
10+
}

src/Support/BaseEntityMapper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public function table(): string
6464
* @phpstan-param array<string, mixed>|EntityType $data
6565
*
6666
* @return int|string|null
67+
*
68+
* @TODO: Think about a better implementation for this
6769
*/
6870
public function identity(object|array $data): int|string|null
6971
{

src/Support/DiscoverMappers.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Articulate\Concise\Support;
5+
6+
use Articulate\Concise\Contracts\Mapper;
7+
use Illuminate\Support\Str;
8+
use ReflectionClass;
9+
use ReflectionException;
10+
use SplFileInfo;
11+
use Symfony\Component\Finder\Finder;
12+
13+
class DiscoverMappers
14+
{
15+
/**
16+
* The callback to be used to guess class names.
17+
*
18+
* @var callable(SplFileInfo, string): class-string|null
19+
*/
20+
public static $guessClassNamesUsingCallback;
21+
22+
/**
23+
* Get all the mappers by searching the given mapper directory.
24+
*
25+
* @param string $mapperPath
26+
* @param string $basePath
27+
*
28+
* @return array<class-string<\Articulate\Concise\Contracts\EntityMapper<*>>>
29+
*/
30+
public static function within(string $mapperPath, string $basePath): array
31+
{
32+
$possibleMappers = Finder::create()->files()->in($mapperPath);
33+
$foundMappers = [];
34+
35+
foreach ($possibleMappers as $possibleMapper) {
36+
try {
37+
$reflection = new ReflectionClass(
38+
static::classFromFile($possibleMapper, $basePath)
39+
);
40+
} catch (ReflectionException) {
41+
continue;
42+
}
43+
44+
if (! $reflection->isInstantiable()) {
45+
continue;
46+
}
47+
48+
if ($reflection->implementsInterface(Mapper::class)) {
49+
$foundMappers[] = $reflection->name;
50+
}
51+
}
52+
53+
/** @var array<class-string<\Articulate\Concise\Contracts\EntityMapper<*>>> $foundMappers */
54+
55+
return $foundMappers;
56+
}
57+
58+
/**
59+
* Extract the class name from the given file path.
60+
*
61+
* @param \SplFileInfo $file
62+
* @param string $basePath
63+
*
64+
* @return class-string
65+
*/
66+
protected static function classFromFile(SplFileInfo $file, string $basePath): string
67+
{
68+
if (static::$guessClassNamesUsingCallback) {
69+
return call_user_func(static::$guessClassNamesUsingCallback, $file, $basePath);
70+
}
71+
72+
$class = trim(Str::replaceFirst($basePath, '', $file->getRealPath()), DIRECTORY_SEPARATOR);
73+
74+
/** @phpstan-ignore return.type */
75+
return ucfirst(Str::camel(str_replace(
76+
[DIRECTORY_SEPARATOR, ucfirst(basename(app()->path())) . '\\'],
77+
['\\', app()->getNamespace()],
78+
ucfirst(Str::replaceLast('.php', '', $class))
79+
)));
80+
}
81+
82+
/**
83+
* Specify a callback to be used to guess class names.
84+
*
85+
* @param callable(SplFileInfo, string): class-string $callback
86+
*
87+
* @return void
88+
*/
89+
public static function guessClassNamesUsing(callable $callback): void
90+
{
91+
static::$guessClassNamesUsingCallback = $callback;
92+
}
93+
}

src/Support/MapperServiceProvider.php

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,155 @@
44
namespace Articulate\Concise\Support;
55

66
use Articulate\Concise\Concise;
7+
use Illuminate\Support\Arr;
8+
use Illuminate\Support\Collection;
79
use Illuminate\Support\ServiceProvider;
810

9-
abstract class MapperServiceProvider extends ServiceProvider
11+
class MapperServiceProvider extends ServiceProvider
1012
{
1113
/**
1214
* @var array<class-string, class-string<\Articulate\Concise\Contracts\EntityMapper<*>>>
1315
*/
1416
protected array $mappers = [];
1517

18+
/**
19+
* Indicates if mappers should be discovered.
20+
*
21+
* @var bool
22+
*/
23+
protected static bool $shouldDiscoverMappers = true;
24+
25+
/**
26+
* The configured mapper discovery paths.
27+
*
28+
* @var array<string>
29+
*/
30+
protected static array $mapperDiscoveryPaths = [];
31+
1632
public function register(): void
1733
{
1834
$this->app->afterResolving(Concise::class, function (Concise $concise) {
19-
foreach ($this->mappers as $mapper) {
35+
$mappers = $this->getMappers();
36+
37+
foreach ($mappers as $mapper) {
38+
/** @phpstan-ignore argument.type */
2039
$concise->register($mapper);
2140
}
2241
});
2342
}
43+
44+
/**
45+
* @return array<class-string<\Articulate\Concise\Contracts\Mapper<*>>>
46+
*/
47+
private function getMappers(): array
48+
{
49+
return array_merge(
50+
$this->discoveredMappers(),
51+
$this->mappers
52+
);
53+
}
54+
55+
/**
56+
* Get the discovered mappers for the application.
57+
*
58+
* @return array<class-string<\Articulate\Concise\Contracts\Mapper<*>>>
59+
*/
60+
protected function discoveredMappers(): array
61+
{
62+
return $this->shouldDiscoverMappers()
63+
? $this->discoverMappers()
64+
: [];
65+
}
66+
67+
/**
68+
* Determine if mappers should be automatically discovered.
69+
*
70+
* @return bool
71+
*/
72+
public function shouldDiscoverMappers(): bool
73+
{
74+
return get_class($this) === __CLASS__ && static::$shouldDiscoverMappers === true;
75+
}
76+
77+
/**
78+
* Discover the mappers for the application.
79+
*
80+
* @return array<class-string<\Articulate\Concise\Contracts\Mapper<*>>>
81+
*/
82+
public function discoverMappers(): array
83+
{
84+
return (new Collection($this->discoverMappersWithin()))
85+
/** @phpstan-ignore argument.type */
86+
->flatMap(function (string $directory) {
87+
return glob($directory, GLOB_ONLYDIR);
88+
})
89+
->reject(function (string $directory) {
90+
return ! is_dir($directory);
91+
})
92+
->reduce(function ($discovered, $directory) {
93+
return array_merge_recursive(
94+
$discovered,
95+
DiscoverMappers::within($directory, $this->mapperDiscoveryBasePath())
96+
);
97+
}, []);
98+
}
99+
100+
/**
101+
* Get the directories that should be used to discover mappers.
102+
*
103+
* @return array<string>
104+
*/
105+
protected function discoverMappersWithin(): array
106+
{
107+
return static::$mapperDiscoveryPaths ?? [
108+
$this->app->path('Mappers\Components'),
109+
$this->app->path('Mappers\Entities'),
110+
];
111+
}
112+
113+
/**
114+
* Add the given mapper discovery paths to the application's mapper discovery paths.
115+
*
116+
* @param string|array<string> $paths
117+
*
118+
* @return void
119+
*/
120+
public static function addMapperDiscoveryPaths(array|string $paths): void
121+
{
122+
static::$mapperDiscoveryPaths = array_values(array_unique(
123+
array_merge(static::$mapperDiscoveryPaths, Arr::wrap($paths))
124+
));
125+
}
126+
127+
/**
128+
* Set the globally configured mapper discovery paths.
129+
*
130+
* @param array<string> $paths
131+
*
132+
* @return void
133+
*/
134+
public static function setMapperDiscoveryPaths(array $paths): void
135+
{
136+
static::$mapperDiscoveryPaths = $paths;
137+
}
138+
139+
/**
140+
* Get the base path to be used during mapper discovery.
141+
*
142+
* @return string
143+
*/
144+
protected function mapperDiscoveryBasePath(): string
145+
{
146+
return base_path();
147+
}
148+
149+
/**
150+
* Disable mapper discovery for the application.
151+
*
152+
* @return void
153+
*/
154+
public static function disableMapperDiscovery(): void
155+
{
156+
static::$shouldDiscoverMappers = false;
157+
}
24158
}

0 commit comments

Comments
 (0)