loading...

Using Laravel's Collection into Symfony

juyn profile image Xavier Dubois πŸ‡«πŸ‡· ・4 min read

What is Collections ?

With PHP, we are all used to works with Arrays. And it's great for simple things, but it can become tricky pretty fast !

I mean, who has never struggle to make a Slice, an array_map or even an array_walker with the built in methods ?

Personally, I always have to check the doc to get it right.

Collections provides an alternative. Laravel documentation describes it like this :

The Illuminate\Support\Collection class provides a fluent, convenient wrapper for working with arrays of data

In other words, it provides a bunch of methods and helpers to access, transform and use arrays of data.
For example, you have a API endpoint that is in charge of sending emails. You get an array of Email object in input, but you have to check first if they passed some assertion before trying to send them.

    $emails->reject(function (Email $email) {
        return null === $email->subject;
    })->each(function (Email $email) {
        $this->send($email)
    });

Pretty neat, right ?
Yes, it's a really simple code, you might say it could have been done with a foreach and a if, but when you start to have serious business logic and a LOT of data to filter, it provides lisibility.

Symfony and the Collections

Before, I was talking about Laravel's implementation of collections.
It has a lot of methods and helpers to make your life easier.

But, profesionally I have to work with Symfony. Which is great, don't get me wrong, but its collection implementation is really basic.

ArrayCollection (it's his little name), provided by Doctrine allows you to filter, access and map array of data, and that's it.
Laravel, in the other hand, provides advanced method, like "first", "sum" or "toJson".

How can we get Laravel's Collection into Symfony ?

Well, that should be simple right ? Because, since Composer exist, all we have to do is

composer require laravel/collection

right ?

But, hey, life is not that easy all the time !
Actually, Collections are part of the Laravel Framework, so, if you want it, you'll have to extract it, copy it on your own repo, and watch Laravel changes to stay up to date.
No thanks !

1st attempt

At first, I got cocky. Why not write my own implementation ?

I mean, nothing complicated to do. Just a bunch of methods ... easy.
ExtendedArrayCollection was born. The name says it all. I based my project on Doctrine's ArrayCollection to minimise the job to do, but also to make sure it will works everywhere in Symfony.

And it did for a while. Until it didn't anymore. To obtain something as good as Laravel does, it demands a huge amount of time, and, I don't have it ...

So, why not use Laravel's Collection ?

Back to Google, looking for a guy with more bravour than me, with more time, maybe.
I found a package, made by Matt Stauffer (Tighten Co.), which is an export of Laravel's Collection.

This was PERFECT. Well, almost perfect.

Laravel's entities are a little bit different thant Symfony's ones.
In Laravel, properties are declared public where, in Symfony, they usually are private or protected, with a bunch of getters and setters.

As you can imagine, it was a problem since, in Laravel's Collection, object data is access like this:

    elseif (is_object($target) && isset($target->{$segment})) {
        target = $target->{$segment};
    }

Annnnd yup. That cannot works with a protected properties, since you have to use $taget->getSegment() to access the value.

Well, since I really hate ArrayCollection, I decided to not give up.
So, I created a fork from Tightenco/collect package to make some tweaks that allows me to use Laravel's Collection into Symfony.

Code modification

Alright, then all I needed to do was to find a way of accessing data no matter their visibility.

I had a few options:

  • Assert that "getSegment" method exist, and, if not, trying to access value with ->segment.
  • Always assume that since I'm in Symfony, getter will always be set
  • Use ReflectionObject

I chose to use the third option, with ReflectionObject because it provides a more secure way to do it.
The code became:

    elseif (is_object($target)) {
        $reflectedObject = new \ReflectionObject($target);

        // Use Reflection class to check if property even exist, otherwhise let's use default value

        if (!$reflectedObject->hasProperty($segment)) {
            return value($default);
        }

        $p = $reflectedObject->getProperty($segment);
        // If property is public, no need to get all the way down
        if ($p->isPublic() && isset($target->{$segment})) {

            $target = $target->{$segment};
        } else {
            // Make the property accessible before geting its value
            $p->setAccessible(true);
            $target = $p->getValue($target);
    }

And, that's it folks. I just have to make a Collection class on my own namespace, extend TightenCo version, and my Symfony is ready to use the amazing Laravel's Collection !

What's next ?

That is already pretty cool and handy, but I want more.
Next steps:

  • Override Doctrine to make it use Collection instead of ArrayCollection
  • Make a proper repository with documentation, example
  • Find a way to simplify the namespace management
  • make unit tests !

If any of you is interested by the package:

collect

Add support for Symfony on Laravel's awesome Collection

Posted on by:

juyn profile

Xavier Dubois πŸ‡«πŸ‡·

@juyn

Happy father, biker, developer, PHP lover. Gratuaded from ESGI Paris

Discussion

markdown guide
 

Why not to use standard PropertyAccess object instead of Reflection? This way you will be able to get even virtual properties presented by getters, isers, hasers... In other words, you will get all the power of PropertyAccessor.

 
 

Do the comparaison between Doctrine's ArrayCollection and Laravel's Collection.
There is a whole world !