Skip to content

Commit 99cc739

Browse files
committed
wip
1 parent 99a0422 commit 99cc739

File tree

14 files changed

+182
-69
lines changed

14 files changed

+182
-69
lines changed

src/Attributes/ResolveModel.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public function getMorphModel(string $fromPropertyKey, array $properties, array
8080
}
8181

8282
$morphMap = Relation::morphMap();
83+
8384
$modelModelClass = array_filter(
8485
array_map(
8586
fn (string $morphType) => $morphMap[$morphType] ?? null,

src/DataTransferObjects/MappingValue.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ final class MappingValue
2828
*/
2929
public function __construct(
3030
public readonly mixed $data,
31+
public readonly array $allMappingData,
3132
public readonly BuiltInType $typeFromData,
3233
public readonly ?string $objectClass = null,
3334
public readonly ?array $types = null,
@@ -38,6 +39,10 @@ public function __construct(
3839

3940
$this->preferredTypeClass = $this->preferredType ? ($this->preferredType->getClassName() ?: $objectClass) : $objectClass;
4041

41-
$this->attributes = Collection::make($class ? $class->getAttributes() : []);
42+
if ($property) {
43+
$this->attributes = Collection::make($property->getAttributes());
44+
} else {
45+
$this->attributes = Collection::make($class ? $class->getAttributes() : []);
46+
}
4247
}
4348
}

src/Mapper.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace OpenSoutheners\LaravelDto;
44

5+
use Illuminate\Database\Eloquent\Model;
56
use Illuminate\Foundation\Http\FormRequest;
67
use Illuminate\Http\Request;
78
use Illuminate\Support\Collection;
@@ -16,11 +17,13 @@ final class Mapper
1617

1718
protected ?string $dataClass = null;
1819

19-
protected ?string $parentClass = null;
20+
protected ?MappingValue $fromMappingValue = null;
2021

2122
protected ?string $property = null;
2223

2324
protected array $propertyTypes = [];
25+
26+
protected bool $runningFromMapper = false;
2427

2528
public function __construct(mixed $input)
2629
{
@@ -52,18 +55,26 @@ protected function takeDataFrom(mixed $input): mixed
5255
$input instanceof FormRequest ? $input->validated() : $input->all()
5356
),
5457
$input instanceof Collection => $input->all(),
58+
$input instanceof Model => $input,
5559
is_object($input) => $this->extractProperties($input),
5660
default => $input,
5761
};
5862
}
5963

60-
public function through(string|object $value, string $property, array $types): static
64+
/**
65+
* @param array<\Symfony\Component\PropertyInfo\Type> $types
66+
*
67+
* @internal
68+
*/
69+
public function through(MappingValue $mappingValue, string $property, array $types): static
6170
{
62-
$this->parentClass = is_object($value) ? get_class($value) : $value;
71+
$this->fromMappingValue = $mappingValue;
6372

6473
$this->property = $property;
6574

6675
$this->propertyTypes = $types;
76+
77+
$this->runningFromMapper = true;
6778

6879
return $this;
6980
}
@@ -96,16 +107,20 @@ public function to(?string $output = null)
96107

97108
$mappedValue = $this->data;
98109

99-
$reflectionClass = $this->parentClass || $output ? new ReflectionClass($this->parentClass ?: $output) : null;
100-
$reflectionProperty = $reflectionClass && $this->property ? $reflectionClass->getProperty($this->property) : null;
110+
if (is_array($mappedValue)) {
111+
$reflectionClass = $this->fromMappingValue?->class ?? $output ? new ReflectionClass($output) : null;
112+
} else {
113+
$reflectionClass = $output ? new ReflectionClass($output) : null;
114+
}
101115

102116
$mappingDataValue = new MappingValue(
103117
data: $this->data,
118+
allMappingData: ($this->runningFromMapper ? $this->fromMappingValue?->allMappingData : $this->data) ?? [],
104119
typeFromData: BuiltInType::guess($this->data),
105120
types: $this->propertyTypes,
106121
objectClass: $output,
107122
class: $reflectionClass,
108-
property: $reflectionProperty,
123+
property: $this->fromMappingValue?->property ?? ($this->property ? $reflectionClass->getProperty($this->property) : null)
109124
);
110125

111126
foreach (ServiceProvider::getMappers() as $mapper) {

src/Mappers/CarbonDataMapper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public function resolve(MappingValue $mappingValue): mixed
3030
default => Carbon::make($mappingValue->data),
3131
};
3232

33-
if ($mappingValue->preferredType === CarbonImmutable::class) {
34-
$dateValue->toImmutable();
33+
if ($mappingValue->preferredTypeClass === CarbonImmutable::class) {
34+
$dateValue = $dateValue->toImmutable();
3535
}
3636

3737
return $dateValue;

src/Mappers/CollectionDataMapper.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ public function resolve(MappingValue $mappingValue): mixed
3535
if ($mappingValue->objectClass === EloquentCollection::class) {
3636
return $mappingValue->data->toBase();
3737
}
38-
38+
3939
if (
4040
count(array_filter($mappingValue->types, fn (Type $type) => $type->getBuiltinType() === Type::BUILTIN_TYPE_STRING)) > 0
41-
&& ! str_contains($mappingValue->types, ',')
41+
&& ! str_contains($mappingValue->data, ',')
4242
) {
4343
return $mappingValue->data;
4444
}
@@ -59,10 +59,15 @@ public function resolve(MappingValue $mappingValue): mixed
5959

6060
if ($preferredCollectionType && $preferredCollectionType->getBuiltinType() === Type::BUILTIN_TYPE_OBJECT) {
6161
if (is_subclass_of($preferredCollectionTypeClass, Model::class)) {
62-
$collection = map($mappingValue->data)->to($preferredCollectionTypeClass);
62+
$collection = map($mappingValue->data)
63+
->through($mappingValue, $mappingValue->property->getName(), $collectionTypes)
64+
->to($preferredCollectionTypeClass);
6365
} else {
66+
dump($mappingValue->property->getName().' => '.$mappingValue->class->getName());
6467
$collection = $collection->map(
65-
fn ($item) => map($item)->to($preferredCollectionTypeClass)
68+
fn ($item) => map($item)
69+
->through($mappingValue, $mappingValue->property->getName(), $collectionTypes)
70+
->to($preferredCollectionTypeClass)
6671
);
6772
}
6873
}

src/Mappers/ModelDataMapper.php

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ public function assert(MappingValue $mappingValue): bool
2828
*/
2929
public function resolve(MappingValue $mappingValue): mixed
3030
{
31+
$data = $mappingValue->data;
32+
33+
if (is_string($data) && str_contains($data, ',')) {
34+
$data = array_filter(explode(',', $data));
35+
}
36+
3137
$resolveModelAttributeReflector = $mappingValue->property->getAttributes(ResolveModel::class);
3238

3339
/** @var \ReflectionAttribute<\OpenSoutheners\LaravelDto\Attributes\ResolveModel>|null $resolveModelAttributeReflector */
@@ -55,7 +61,7 @@ public function resolve(MappingValue $mappingValue): mixed
5561
->toArray();
5662

5763
if (is_array($modelType) && $mappingValue->objectClass === Collection::class) {
58-
$valueClass = get_class($mappingValue->data);
64+
$valueClass = get_class($data);
5965

6066
$modelType = $modelClass[array_search($valueClass, $modelClass)];
6167
}
@@ -66,33 +72,36 @@ public function resolve(MappingValue $mappingValue): mixed
6672
) {
6773
$modelType = $resolveModelAttribute->getMorphModel(
6874
$mappingValue->property->getName(),
69-
$mappingValue->class->getProperties(ReflectionProperty::IS_PUBLIC),
75+
$mappingValue->allMappingData,
7076
$mappingValue->types === Model::class ? [] : (array) $mappingValue->types
7177
);
7278
}
7379

7480
if (! is_countable($modelType) || count($modelType) === 1) {
7581
return $this->resolveIntoModelInstance(
76-
$mappingValue->data,
82+
$data,
7783
! is_countable($modelType) ? $modelType : $modelType[0],
7884
$mappingValue->property->getName(),
7985
$modelWithAttributes,
8086
$resolveModelAttribute
8187
);
8288
}
8389

84-
return Collection::make(array_map(
85-
function (mixed $valueA, mixed $valueB) use (&$lastNonValue): array {
86-
if (! is_null($valueB)) {
87-
$lastNonValue = $valueB;
88-
}
89-
90-
return [$valueA, $valueB ?? $lastNonValue];
91-
},
92-
$mappingValue->data instanceof Collection ? $mappingValue->data->all() : (array) $mappingValue->data,
93-
(array) $modelType
94-
))->mapToGroups(fn (array $value) => [$value[1] => $value[0]])->flatMap(fn (Collection $keys, string $model) => $this->resolveIntoModelInstance($keys, $model, $mappingValue->property->getName(), $modelWithAttributes, $resolveModelAttribute)
95-
);
90+
return Collection::make(
91+
array_map(
92+
function (mixed $valueA, mixed $valueB) use (&$lastNonValue): array {
93+
if (! is_null($valueB)) {
94+
$lastNonValue = $valueB;
95+
}
96+
97+
return [$valueA, $valueB ?? $lastNonValue];
98+
},
99+
$data,
100+
(array) $modelType
101+
)
102+
)
103+
->mapToGroups(fn (array $value) => [$value[1] => $value[0]])
104+
->flatMap(fn (Collection $keys, string $model) => $this->resolveIntoModelInstance($keys, $model, $mappingValue->property->getName(), $modelWithAttributes, $resolveModelAttribute));
96105
}
97106

98107
/**

src/Mappers/ObjectDataMapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public function resolve(MappingValue $mappingValue): mixed
101101
}
102102

103103
$data[$key] = map($value)
104-
->through($mappingValue->preferredTypeClass, $key, $propertyTypes)
104+
->through($mappingValue, $key, $propertyTypes)
105105
->to($preferredTypeClass);
106106
}
107107

src/ServiceProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use OpenSoutheners\LaravelDto\Commands\DtoMakeCommand;
99
use OpenSoutheners\LaravelDto\Commands\DtoTypescriptGenerateCommand;
1010
use OpenSoutheners\LaravelDto\Contracts\RouteTransferableObject;
11-
use OpenSoutheners\LaravelDto\Mappers;
1211
use ReflectionClass;
1312

1413
class ServiceProvider extends BaseServiceProvider

tests/Integration/DataTransferObjectTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ public function test_data_transfer_object_with_morphs_gets_models_bound_of_each_
239239

240240
$response = $this->patchJson('tags/1', [
241241
'name' => 'Scary',
242-
'taggable' => '1, 1, 2',
242+
'taggable' => Collection::make([$myFilm->getKey(), $fooBarPost->getKey(), $helloWorldPost->getKey()])->join(', '),
243243
// TODO: Fix mapping by slug
244244
// 'taggable' => '1, foo-bar, hello-world',
245245
'taggable_type' => 'film, post',

tests/Unit/DataTransferObjectTest.php

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,17 @@
22

33
namespace OpenSoutheners\LaravelDto\Tests\Unit;
44

5-
use Illuminate\Auth\AuthManager;
6-
use Illuminate\Config\Repository;
7-
use Illuminate\Container\Container;
85
use Illuminate\Support\Carbon;
96
use Illuminate\Support\Collection;
10-
use Mockery;
11-
use OpenSoutheners\LaravelDto\Mappers;
12-
use OpenSoutheners\LaravelDto\ServiceProvider;
13-
use PHPUnit\Framework\TestCase;
147
use Workbench\App\DataTransferObjects\CreateComment;
158
use Workbench\App\DataTransferObjects\CreateManyPostData;
169
use Workbench\App\DataTransferObjects\CreatePostData;
1710
use Workbench\App\Enums\PostStatus;
1811

1912
use function OpenSoutheners\LaravelDto\map;
2013

21-
class DataTransferObjectTest extends TestCase
14+
class DataTransferObjectTest extends UnitTestCase
2215
{
23-
protected function setUp(): void
24-
{
25-
parent::setUp();
26-
27-
ServiceProvider::registerMapper([
28-
Mappers\CollectionDataMapper::class,
29-
Mappers\ModelDataMapper::class,
30-
31-
Mappers\CarbonDataMapper::class,
32-
Mappers\BackedEnumDataMapper::class,
33-
Mappers\GenericObjectDataMapper::class,
34-
Mappers\ObjectDataMapper::class,
35-
]);
36-
37-
$mockedConfig = Mockery::mock(Repository::class);
38-
39-
$mockedConfig->shouldReceive('get')->andReturn(true);
40-
41-
Container::getInstance()->bind('config', fn () => $mockedConfig);
42-
43-
$mockedAuth = Mockery::mock(AuthManager::class);
44-
45-
$mockedAuth->shouldReceive('check')->andReturn(false);
46-
$mockedAuth->shouldReceive('userResolver')->andReturn(fn () => null);
47-
48-
Container::getInstance()->bind('auth', fn () => $mockedAuth);
49-
50-
Container::getInstance()->bind('dto.context.booted', fn () => '');
51-
}
52-
5316
public function test_data_transfer_object_from_array()
5417
{
5518
$data = map([

tests/Unit/MapperTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace OpenSoutheners\LaravelDto\Tests\Unit;
4+
5+
use OpenSoutheners\LaravelDto\Tests\Integration\TestCase;
6+
use Workbench\App\Models\User;
7+
use Workbench\Database\Factories\UserFactory;
8+
9+
use function OpenSoutheners\LaravelDto\map;
10+
11+
class MapperTest extends TestCase
12+
{
13+
public function testMapNumericIdToModelResultsInModelInstance()
14+
{
15+
$user = UserFactory::new()->create();
16+
17+
$result = map('1')->to(User::class);
18+
19+
$this->assertInstanceOf(User::class, $result);
20+
$this->assertEquals($user->email, $result->email);
21+
}
22+
}

tests/Unit/UnitTestCase.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace OpenSoutheners\LaravelDto\Tests\Unit;
4+
5+
use Illuminate\Auth\AuthManager;
6+
use Illuminate\Config\Repository;
7+
use Illuminate\Container\Container;
8+
use Mockery;
9+
use OpenSoutheners\LaravelDto\Mappers;
10+
use OpenSoutheners\LaravelDto\ServiceProvider;
11+
use PHPUnit\Framework\TestCase;
12+
13+
abstract class UnitTestCase extends TestCase
14+
{
15+
protected function setUp(): void
16+
{
17+
parent::setUp();
18+
19+
ServiceProvider::registerMapper([
20+
Mappers\CollectionDataMapper::class,
21+
Mappers\ModelDataMapper::class,
22+
23+
Mappers\CarbonDataMapper::class,
24+
Mappers\BackedEnumDataMapper::class,
25+
Mappers\GenericObjectDataMapper::class,
26+
Mappers\ObjectDataMapper::class,
27+
]);
28+
29+
$mockedConfig = Mockery::mock(Repository::class);
30+
31+
$mockedConfig->shouldReceive('get')->andReturn(true);
32+
33+
Container::getInstance()->bind('config', fn () => $mockedConfig);
34+
}
35+
36+
public function actAsUser($user = null)
37+
{
38+
$mockedAuth = Mockery::mock(AuthManager::class);
39+
40+
$mockedAuth->shouldReceive('check')->andReturn(false);
41+
$mockedAuth->shouldReceive('userResolver')->andReturn(fn () => $user);
42+
43+
Container::getInstance()->bind('auth', fn () => $mockedAuth);
44+
}
45+
}

workbench/app/Models/User.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
namespace Workbench\App\Models;
44

5+
use Illuminate\Database\Eloquent\Factories\HasFactory;
56
use Illuminate\Foundation\Auth\User as Authenticatable;
67

78
class User extends Authenticatable
89
{
10+
use HasFactory;
11+
912
/**
1013
* The attributes that aren't mass assignable.
1114
*

0 commit comments

Comments
 (0)