Skip to content

[12.x] Adds Cast to Collect into classes or callback #55377

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

Conversation

DarkGhostHunter
Copy link
Contributor

@DarkGhostHunter DarkGhostHunter commented Apr 12, 2025

What?

This PR adds a new AsCollectionMap castable that will allow the developer to create a collection mapped into a class, or to a callback. It's an alternative to use a custom Collection class that would map the items at construction time.

A developer may use this castable to map items into a given class using the into() static method:

use Illuminate\Database\Eloquent\Casts\AsCollectionMap;
use Illuminate\Support\Fluent;

public function casts()
{
    return [
        'colors' => AsCollectionMap::into(Fluent::class)
    ];
}

It's not limited to class names, but also callbacks that transform the item through the using() method. These callbacks should be either a string in class@method notation, a callable array, or a class and method set separately.

use Illuminate\Database\Eloquent\Casts\AsCollectionMap;
use App\ValueObject\Color;

public function casts()
{
    return [
        'colors' => AsCollectionMap::using(Color::class, 'make'), 
    ];
}

Caution

Because the nature of the casts declaration, there is no support to using a Closure. If a Closure is set, a descriptive exception will be thrown. Alternatively, the developer can use castUsing() directly with a closure.

The whole logic sits after the value decryption, so encryption can work as any monday morning with the AsEncryptedCollectionMap class.

use Illuminate\Database\Eloquent\Casts\AsEncryptedCollectionMap;
use App\ValueObject\Color;

public function casts()
{
    return [
        'colors' => AsEncryptedCollectionMap::using(Color::class, 'make'), 
    ];
}

@DarkGhostHunter DarkGhostHunter force-pushed the feat/12.x/collection-cast-map branch from c79449d to 6052629 Compare April 12, 2025 05:43
@DarkGhostHunter DarkGhostHunter force-pushed the feat/12.x/collection-cast-map branch from 6052629 to b4ac245 Compare April 12, 2025 05:49
@DarkGhostHunter DarkGhostHunter changed the title [12.x] Adds Collection mapping into classes [12.x] Adds Cast to Collect into classes or callback Apr 12, 2025
@antonkomarev
Copy link
Contributor

antonkomarev commented Apr 12, 2025

Maybe it's better to make method using to accept array? Then IDE will understand it's method of the concrete class.

AsEncryptedCollectionMap::using([Color::class, 'make'])

Edit: Ah I see it's supported. But I'd prefer to enforce this way, because it's better for static analysis.

@AndrewMast
Copy link
Contributor

I somewhat concur with Anton. Mostly just to simply the code in the castUsing methods here:

public function __construct(array $arguments)
{
    $arguments = array_values($arguments);

    if (empty($arguments) || empty($arguments[0])) {
        throw new InvalidArgumentException('No class or callable has been set to map the Collection.');
    }

    if (isset($arguments[1]) && ! is_array($arguments[0])) {
        $arguments = [$arguments[0], $arguments[1]];
        unset($arguments[1]);
    }

    $this->callable = $arguments[0];
}

What is $arguments = [$arguments[0], $arguments[1]]; supposed to accomplish? It just gets the first two elements of $arguments and assigns it to itself (could use array_slice($arguments, 0, 2), but I still don't understand why its used).

And then unset($arguments[1]); removes the second element, so we could have just done $arguments = [$arguments[0]], but since we are then taking the first element out for the callable, we don't even have to put it back into the array, we could have just done $this->callable = $arguments[0]. Which is exactly what is done below the if statement, so why even have the if statement?

@DarkGhostHunter
Copy link
Contributor Author

DarkGhostHunter commented Apr 12, 2025

@AndrewMast I just brainfaterd, so I fixed it.

Anyway, what are your thoughts guys? Should be this remain as its own Castable, or extend the base AsCollection to accept an map argument?

public function casts()
{
    return [
        'colors_map_into' => AsCollection::into(Color::class),
        'colors_collection' => AsCollection::using(ColorsCollection::class, Color::class),
        'colors_map' => AsCollection::map([Color::class, 'make']),
    ]
}

The benefit of the latter is allowing the developer to use its own Collection class but additionally mapping each item to a class or callback, which would be great for both scenarios without sacrifices.

This would mean the AsCollection and AsEncryptedCollection would grow a little in size to amend receiving a map class or callback, and properly normalizing the arguments.

@DarkGhostHunter
Copy link
Contributor Author

Reworked at #55383

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants