-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Apply casts when serializing embedded relations #2762
Comments
I have created two cast <?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Collection;
use MongoDB\Laravel\Eloquent\Model;
class EmbedManyCast implements Castable
{
public static function castUsing(array $arguments): CastsAttributes
{
return new class($arguments) implements CastsAttributes
{
protected array $arguments;
public function __construct(array $arguments)
{
$this->arguments = $arguments;
}
public function get($model, $key, $value, $attributes): ?Collection
{
if (! is_array($value)) {
return null;
}
$modelClass = $this->arguments[0];
if (! is_a($modelClass, Model::class, true)) {
throw new \InvalidArgumentException('The provided class must extend ['.Model::class.'].');
}
return (new Collection($value))->map(function ($item) use ($modelClass) {
return (new $modelClass)->setRawAttributes($item);
});
}
public function set($model, $key, $value, $attributes)
{
return $value;
}
};
}
/**
* Specify the collection for the cast.
*
* @param string $class
* @return string
*/
public static function using(string $class): string
{
return static::class.':'.$class;
}
} <?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use MongoDB\Laravel\Eloquent\Model;
class EmbedOneCast implements Castable
{
public static function castUsing(array $arguments): CastsAttributes
{
return new class($arguments) implements CastsAttributes
{
protected array $arguments;
public function __construct(array $arguments)
{
$this->arguments = $arguments;
}
public function get($model, $key, $value, $attributes): ?Model
{
if (! $value || ! is_array($value)) {
return null;
}
$modelClass = $this->arguments[0];
if (! is_a($modelClass, Model::class, true)) {
throw new \InvalidArgumentException('The provided class must extend ['.Model::class.'].');
}
return (new $modelClass)->setRawAttributes($value);
}
public function set($model, $key, $value, $attributes)
{
return $value;
}
};
}
/**
* Specify the collection for the cast.
*
* @param string $class
* @return string
*/
public static function using(string $class): string
{
return static::class.':'.$class;
}
} Use like this <?php
namespace App\Models;
use MongoDB\Laravel\Auth\User as Authenticatable;
use App\Casts\EmbedManyCast;
use App\Casts\EmbedOneCast;
use MongoDB\Laravel\Relations\EmbedsMany;
use MongoDB\Laravel\Relations\EmbedsOne;
class User extends Authenticatable
{
protected $connection = 'mongodb';
protected $collection = 'users';
protected $guarded = [];
protected function casts(): array
{
return [
'createdAt' => 'immutable_datetime',
'updatedAt' => 'immutable_datetime',
'books' => EmbedManyCast::using(Book::class),
'info' => EmbedOneCast::using(Info::class),
];
}
public function embedBooks(): EmbedsMany
{
return $this->embedsMany(Book::class, 'books');
}
public function embedInfo(): EmbedsOne
{
return $this->embedsOne(Info::class, 'info');
}
} Without the cast, here's the result of the following code: <?php
use App\Models\User;
/** @var User $user */
$user = User::query()->find('65f0c181843f4fa3560c9302');
dd($user->toArray()); Before => {
"_id": "65f0c181843f4fa3560c9302",
"email": "[email protected]",
"updated_at": "2024-03-12T20:56:33.074000Z",
"created_at": "2024-03-12T20:56:33.074000Z",
"books": [
{
"title": "mongodb",
"author": "florian",
"updated_at": {
"$date": {
"$numberLong": "1710277189764"
}
},
"created_at": {
"$date": {
"$numberLong": "1710277189764"
}
},
"_id": {
"$oid": "65f0c245530e10a4da0be372"
}
}
],
"info": {
"gender": "M",
"birthDate": {
"$date": {
"$numberLong": "598579200000"
}
},
"updated_at": {
"$date": {
"$numberLong": "1710277463652"
}
},
"created_at": {
"$date": {
"$numberLong": "1710277463652"
}
},
"_id": {
"$oid": "65f0c357e43b5e39160b2f52"
}
}
} After (with cast) {
"_id": "65f0c181843f4fa3560c9302",
"email": "[email protected]",
"updated_at": "2024-03-12T20:56:33.074000Z",
"created_at": "2024-03-12T20:56:33.074000Z",
"books": [
{
"title": "mongodb",
"author": "florian",
"updated_at": "2024-03-12T20:59:49.764000Z",
"created_at": "2024-03-12T20:59:49.764000Z",
"_id": "65f0c245530e10a4da0be372"
}
],
"info": {
"gender": "M",
"birthDate": "1988-12-20T00:00:00.000000Z",
"updated_at": "2024-03-12T21:04:23.652000Z",
"created_at": "2024-03-12T21:04:23.652000Z",
"_id": "65f0c357e43b5e39160b2f52"
}
} Do you think it's appropriate? |
florianJacques
changed the title
Cast embbeded relationship
Apply casts on serialize embeded relationships
Mar 12, 2024
florianJacques
changed the title
Apply casts on serialize embeded relationships
Apply casts when serializing embedded relations
Mar 12, 2024
Finally a think the better solution is this public function toArray(): array
{
$array = parent::toArray();
$array['books'] = $this-> embedBooks->toArray();
$array['info'] = $this-> embedInfo->toArray();
return $array;
} |
Very interesting approach, both solutions might be a good workaround |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Description:
Hello,
would it be possible to automatically cast embedded relations when calling the database?
Steps to reproduce
Add EmbedsMany relation on parent model.
Add ObjectIdCast on Child Model BSON property?
Expected behaviour
When calling toArray() or toJson() on the parent model, return all casted embedded properties.
Actual behaviour
All BSON types are incorrectly serialized
Logs:
Insert log.txt here (if necessary)The text was updated successfully, but these errors were encountered: