Comments (23)
That's an issue we have both in Doctrine and Sonata: major versions are a big deal. They wouldn't be if we had them more frequently (or at all, really).
I saw multiple PR on master, so I took all my hopes and create one too with the methods I find useful: #252.
I know doctrine/orm next major is a big deal, but I have trouble to see what is big for doctrine/collections.
To me doctrine could
- Add the methods to the Collection interface and the ArrayCollection
- Release a new major
- Add the implementation on doctrine/orm (and so on) to add support to the new major.
from collections.
The Doctrine collection API is completely frozen for years and there won't be any major improvements (even not in 2.0) due to the ecosystem and other Doctrine projects relying on exactly that API according to what @Ocramius, @stof and @greg0ire said.
If you want a feature-rich and extensible collection API, you can try the PHP Map package in addition. Converting a Doctrine collection into a Map is just a matter of:
$m = map( $collection );
and you have all named methods available (and 100+ more):
$v = $m->each(function($val, $key) {
return -$val < 0;
})->reduce(fuction($result, $val) {
return $result + $val;
});
// or use
$v = $m->every(function($val, $key) {
return $val > 0;
})->sum();
You can also extend the Map objects dynamically by your own methods like @someniatko mentioned, e.g.
Map::method( 'strrev', function( $sep ) {
return strrev( join( '-', $this->list ) );
} );
Map::from( ['a', 'b'] )->strrev( '-' );
You can even operate on all objects in a map at once:
// MyClass implements setId() (returning $this) and getCode() (initialized by constructor)
$map = Map::from( ['a' => new MyClass( 'x' ), 'b' => new MyClass( 'y' )] );
$map->setStatus( 1 )->getCode()->toArray();
The full documentation of the PHP Map package is available at https://php-map.org/
from collections.
Any utility methods taking a closure as argument would be methods that cannot be implemented in optimized way by persistent collections (as the closure can do anything). And so having them in the Collection interface does not have much benefits over having a separate function taking the collection and the callable.
so I would not be in favor of adding such utilities in the Collection interface.
There are however a filter
or a map
function.
You can write
$collection->map($callable)
But have to
array_reduce($collection->toArray(), $callable)
It's not consistent :/
But, it has drawbacks: adding methods in an interface requires a new major version because it is a BC break.
It's not like it would be a big major version.
from collections.
If we do that, people will start relying on a concrete implementation rather than an abstraction, and that would be more harmful to the ecosystem
Alternatively, you can add a new interface, and merge it with the other on next major (or keep it separate if it makes sense).
from collections.
@soullivaneuh all the Doctrine O*M
projects are containing their own implementations of the collection API, and when using them, you have no guarantee about which implementation of the interface you will get. So adding a new method only in ArrayCollection here would make it unusable (if you use it, it will break when the ORM uses its own implementation).
And the different O*M
projects are maintained by different teams, making it harder to enforce implicit contracts
from collections.
I feel like it's too bad to not create a new major version with useful method added to the collection interface.
There is a method contains, exists or filter and a method first, but no method find, which could be more performant.
It could also avoid some usage of the 'toArray' method. For example if we add a reduce method.
from collections.
BC compat, mostly. Collection interface is closed for modification.
from collections.
Collection interface is closed for modification.
Why not start implementation and add the method to the interface on next major?
from collections.
It would be interesting, but it would also have a high impact on the ecosystem, making upgrades very painful.
The collections API as-is needs some sort of redesign also to get rid of this kind of major BC break bumps for every new feature that is introduced.
from collections.
making upgrades very painful.
Why? Just one method to add for people implementing the interface directly, nothing to do for people using doctrine classes.
The collections API as-is needs some sort of redesign also to get rid of this kind of major BC break bumps for every new feature that is introduced.
Which kind of redesign?
BTW, if you don't want to create a new major right now, you can implement the each
method on each abstract implementing the interface and add the method to the interface but commented with a remember comment.
Then when you will release the next major, uncomment the method.
Not a very beautiful method, but this is the best way we found on sonata projects for now (cc @greg0ire @core23).
from collections.
Which kind of redesign?
We looked a lot at things like https://github.com/morrisonlevi/Ardent - the collection API needs some sort of differentiation between dictionaries, arrays, stacks, linked lists, etc. The reason behind that being that the current logic is all crammed together into a single object that is just PHP arrays (again, sigh).
BTW, if you don't want to create a new major right now, you can implement the
each
method on each abstract implementing the interface and add the method to the interface but commented with a remember comment.
If we do that, people will start relying on a concrete implementation rather than an abstraction, and that would be more harmful to the ecosystem
from collections.
And the different O*M projects are maintained by different teams, making it harder to enforce implicit contracts
So I see two solutions:
- Add a new Interface as @greg0ire suggested and deprecate the old one
- Just add the method directly on next major
from collections.
Add a new Interface as @greg0ire suggested and deprecate the old one
I was Actually thinking of doing this on next minor
interface NewInterface
{
public function newMethod();
}
class ArrayCollection implements NewInterface, OldInterface
{
}
And optionally do this on next major, if keeping several interfaces makes no sense (Does Interface Segregation Principle apply here ?)
interface OldInterface
{
// other methods…
public function newMethod();
}
class ArrayCollection implements OldInterface
{
}
I can see how this is not great because NewInterface would be sort of instantly deprecated
Client code would then look like this :
public function myMethod(OldInterface $collection)
{
// do some stuff
if ($collection instanceof NewInterface) { // this line is not forward-compatible :(
// yaaaay, can use new method
} else {
// old method, to be removed on next major when we rely on doctrine/collection ^$nextMajor
}
}
Problem : this is not very compatible with deprecation warnings and smooth upgrade, unless you deprecate NewInterface
in next major instead of right now.
from collections.
If an "each" method does get added, it might be worth adding other helper methods. For example:
// implementation
public function sum(Closure $p)
{
$sum = 0;
$this->each(function($key, $element) use ($p, &$sum) {
$sum += $p($key, $element);
});
return $sum;
}
// usage
$sum = $this->collection->sum(function($key, $element) {
return $element->getSomeValue();
});
Although, this "sum" helper is only a slight improvement over:
$sum = 0;
$this->collection->each(function ($key, $element) use (&$sum) {
$sum += $element->getSomeValue();
});
from collections.
Hey @taylankasap !
I think the method your looking for is name forAll()
The method should be named forEach()
imho
from collections.
Any utility methods taking a closure as argument would be methods that cannot be implemented in optimized way by persistent collections (as the closure can do anything). And so having them in the Collection interface does not have much benefits over having a separate function taking the collection and the callable.
But, it has drawbacks: adding methods in an interface requires a new major version because it is a BC break.
so I would not be in favor of adding such utilities in the Collection interface.
from collections.
Hey @taylankasap !
I think the method your looking for is name
forAll()
The method should be named
forEach()
imho
I would just like to add a note for anyone who see this comment: forAll
is not equivalent of PHP foreach
.
forAll
goal is to compare if all itens in the collection satisfy a given predicate. So, it will execute the Closure on each item while true is being returned, if the closure returns something false or void the loop breaks and false is returned, which is in agreement with the docs:
Tests whether the given predicate holds for all elements of this collection.
from collections.
I feel like it's too bad to not create a new major version with useful method added to the collection interface.
That's an issue we have both in Doctrine and Sonata: major versions are a big deal. They wouldn't be if we had them more frequently (or at all, really).
from collections.
I have trouble to see what is big for doctrine/collections.
I don't see any blocker either :)
from collections.
forAll
goal is to compare if all itens in the collection satisfy a given predicate. So, it will execute the Closure on each item while true is being returned, if the closure returns something false or void the loop breaks and false is returned, which is in agreement with the docs:
then, the naming is bad. forAll
tells me that this executes the callback for all items, not that it checks that all items are accepted by the callback (the for
part of the name is the culprit here)
from collections.
forAll
goal is to compare if all items in the collection satisfy a given predicate. So, it will execute the Closure on each item while true is being returned, if the closure returns something false or void the loop breaks and false is returned, which is in agreement with the docs:then, the naming is bad.
forAll
tells me that this executes the callback for all items, not that it checks that all items are accepted by the callback (thefor
part of the name is the culprit here)
A lot of method could be improved.
The add
method is taking an $element
.
The contains
method too and there is a containsKey
method.
But the remove
method is taking a $key
as argument and there is a removeElement
method.
There should be a remove
method and a removeKey
method instead.
The method forAll
could be named every
.
from collections.
@VincentLanglet changing existing methods requires BC breaks for packages consuming collections, which is much harder for the ecosystem than BC breaks for packages implementing collections (which is the kind of BC break for adding a new method). So cleaning the existing API would be quite hard.
But that does not mean that any new method that would be added in a 2.x should not be careful about its naming and API.
from collections.
I wish PHP had something like extension methods from C#, or maybe the pipe operator would make a deal.
from collections.
Related Issues (20)
- Question: `Selectable` return value HOT 5
- instanceof checks in `ExpressionVisitor`
- Use splice instead of unset in ArrayCollection HOT 7
- Adding a ReadableCollection interface HOT 3
- Criteria::expr() can be null - IDE is warning us HOT 1
- Static analysis issue regarding ReadableCollection interface HOT 12
- Unable to resolve the template type TMaybeContained HOT 7
- Feature request: groupBy similar to orderBy Expression
- Feature request: add a `find` method HOT 3
- [2.1.1] Method `ArrayAccess::offsetExists()` might add `bool` as a native return type declaration in the future. HOT 3
- ReadableCollection filter method with phpstan in the context of a Collection instancied as ArrayCollection
- Psalm annotations ArrayCollection->contains() incorrect HOT 2
- Add unique() method HOT 1
- should the return type of ReadableCollection:filter() & ReadableCollection:map() not be static ? HOT 16
- Question: is there any way to reindex a collection using callable function that defines index? HOT 1
- Use looser `callable` type in place of `Closure` HOT 4
- Collection index field ignored when selecting things from a not-initialized extra lazy collection
- Enum `Order` should be correctly handled as an enum HOT 2
- Lets allow collections to be indexed by any \Stringable values
- RemoveElement return type nullable?
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from collections.