Skip to content

Commit 3e41e56

Browse files
committed
added basic support
1 parent 2618f6d commit 3e41e56

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
"email": "jakub.blaha@involve.cz"
1010
}
1111
],
12-
"require": {},
12+
"require": {
13+
"php": "^8.0",
14+
"laravel/framework": "^10.0"
15+
},
1316
"autoload": {
1417
"psr-4": {
1518
"BoredProgrammers\\LaravelEloquentMappable\\": "src/"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace BoredProgrammers\LaravelEloquentMappable\Models\Scopes;
4+
5+
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Scope;
8+
use Illuminate\Database\Query\Builder as QueryBuilder;
9+
use Illuminate\Database\Query\JoinClause;
10+
11+
class MappedColumnsScope implements Scope
12+
{
13+
14+
public function apply(EloquentBuilder $builder, Model $model): void
15+
{
16+
$builder->beforeQuery(function (QueryBuilder $builder) use ($model) {
17+
if (!$mappedColToDbCol = $model->columns ?? null) {
18+
return;
19+
}
20+
21+
$modelTable = $model->getTable();
22+
$mappedColToDbColWithTable = collect($mappedColToDbCol)
23+
->mapWithKeys(fn($value, $key) => [$modelTable . '.' . $key => $modelTable . '.' . $value])
24+
->merge($mappedColToDbCol);
25+
26+
$dbColToMappedCol = $mappedColToDbColWithTable->flip();
27+
28+
$builder->columns = $this->mapColumns(
29+
$builder->columns,
30+
$mappedColToDbColWithTable,
31+
$dbColToMappedCol,
32+
$model
33+
);
34+
35+
$builder->wheres = $this->mapColumns(
36+
$builder->wheres,
37+
$mappedColToDbColWithTable,
38+
$dbColToMappedCol,
39+
$model
40+
);
41+
42+
$builder->joins = $this->mapJoins($builder->joins, $mappedColToDbColWithTable);
43+
});
44+
}
45+
46+
private function mapColumns($array, $mappedColToDbColWithTable, $dbColToMappedCol, $model)
47+
{
48+
if (!$array) {
49+
return $array;
50+
}
51+
52+
foreach ($array as $key => $column) {
53+
$columnName = is_array($column) ? $column['column'] : $column;
54+
55+
if ($columnName === '*' || str_contains($columnName, ' as ')) { // todo: add support for column aliases
56+
continue;
57+
}
58+
59+
if ($newColumnName = $mappedColToDbColWithTable[$columnName] ?? null) {
60+
if (is_array($column)) {
61+
$array[$key]['column'] = $newColumnName;
62+
} else {
63+
$array[$key] = $newColumnName;
64+
}
65+
}
66+
}
67+
68+
return $array;
69+
}
70+
71+
private function mapJoins($joins, $mappedColToDbColWithTable)
72+
{
73+
if (!$joins) {
74+
return $joins;
75+
}
76+
77+
/** @var JoinClause $joinClause */
78+
foreach ($joins as $joinClause) {
79+
foreach ($joinClause->wheres as $whereKey => $whereData) {
80+
if ($whereData['type'] === 'Column') {
81+
if ($dbCol = $mappedColToDbColWithTable[$whereData['first']] ?? null) {
82+
$joinClause->wheres[$whereKey]['first'] = $dbCol;
83+
}
84+
85+
if ($dbCol = $mappedColToDbColWithTable[$whereData['second']] ?? null) {
86+
$joinClause->wheres[$whereKey]['second'] = $dbCol;
87+
}
88+
}
89+
}
90+
91+
if ($joinClause->joins) {
92+
$joinClause->joins = $this->mapJoins($joinClause->joins, $mappedColToDbColWithTable);
93+
}
94+
}
95+
96+
return $joins;
97+
}
98+
99+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace BoredProgrammers\LaravelEloquentMappable\Models\Traits;
4+
5+
use BoredProgrammers\LaravelEloquentMappable\Models\Scopes\MappedColumnsScope;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Support\Str;
8+
use Illuminate\Support\Traits\Macroable;
9+
10+
/**
11+
* @property array $columns
12+
*/
13+
trait HasMappableColumns
14+
{
15+
16+
use Macroable {
17+
__call as macroCall;
18+
}
19+
20+
protected static function bootHasMappableColumns(): void
21+
{
22+
static::addGlobalScope(new MappedColumnsScope());
23+
24+
foreach ((new static)->columns ?? [] as $mappedCol => $dbCol) {
25+
static::macro('get' . Str::studly($mappedCol) . 'Attribute', function () use ($dbCol) {
26+
return $this->$dbCol;
27+
});
28+
29+
static::macro('set' . Str::studly($mappedCol) . 'Attribute', function ($value) use ($dbCol) {
30+
$this->$dbCol = $value;
31+
});
32+
}
33+
}
34+
35+
public function newInstance($attributes = [], $exists = false): Model
36+
{
37+
/** @var Model $instance */
38+
$instance = parent::newInstance($attributes, $exists);
39+
40+
foreach ($instance->columns ?? [] as $mappedCol => $dbCol) {
41+
$instance->hidden[] = $dbCol;
42+
$instance->appends[] = $mappedCol;
43+
44+
if (in_array($mappedCol, $instance->guarded)) {
45+
$instance->guarded[] = $dbCol;
46+
}
47+
48+
if (in_array($mappedCol, $instance->fillable)) {
49+
$instance->fillable[] = $dbCol;
50+
}
51+
}
52+
53+
return $instance;
54+
}
55+
56+
public function hasGetMutator($key): bool
57+
{
58+
return parent::hasGetMutator($key) || static::hasMacro('get' . Str::studly($key) . 'Attribute');
59+
}
60+
61+
public function hasSetMutator($key): bool
62+
{
63+
return parent::hasSetMutator($key) || static::hasMacro('set' . Str::studly($key) . 'Attribute');
64+
}
65+
66+
public function __call($method, $parameters)
67+
{
68+
if (Str::startsWith($method, ['set', 'get']) && self::hasMacro($method)) {
69+
return self::macroCall($method, $parameters);
70+
}
71+
72+
return parent::__call($method, $parameters);
73+
}
74+
75+
public static function __callStatic($method, $parameters)
76+
{
77+
if (Str::startsWith($method, ['set', 'get']) && self::hasMacro($method)) {
78+
return self::macroCall($method, $parameters);
79+
}
80+
81+
return parent::__callStatic($method, $parameters);
82+
}
83+
84+
}

0 commit comments

Comments
 (0)