Doctrine-Specification
Doctrine-Specification copied to clipboard
Differences between this and the Happyr package
Tell me more about the differences between this and the Happyr package.
We are not stable yet and there are room for small and big changes. Maybe we can find a way to work together.
Sure, I would be glad to.
Some backstory I originally started with an implementation inside a prototype project after reading the same blogpost mentioned in the Happyr package a year ago. I always wanted to extract my implementation out of the project, since some colleagues saw the benefit of it and wanted to use it as well.
I started on extracting it, but during this time I also found the Happyr package. I saw a lot of similarities to the code I originally had (naming apart), but also a bigger level of abstraction. The code in my repository is basically a mix of my initial implementation, but also with concepts from the Happyr package I liked.
The actual differences
The first difference is I wanted to have the ability to receive the Query object after calling match()
on the repository. This meant I would be able to use a pagination library, which I was using in the prototype (a Symfony2 HATEOAS project). This was something I got used to and was frankly not ready to give up yet.
The second main difference is that the Specification class can take children directly, I did not want to have a separate class be responsible for that. This meant the specification had to contain all the logic from the Happyr LogicX class, making my AndX/OrX classes a little obsolete since they just extend Specification and call some setters. I do like to have them for readability when composing a spec though.
The third main difference is that the interface is the same between Filters and QueryModifiers unlike in the Happyr package. In my case it's all a SpecificationInterface, which solved an issue with nesting certain types I saw initially in the Happyr package (this has been solved now).
My specification interface only requires a modify()
method which can either return a string (making it a filter) or nothing (making it a query modifier), this means you also don't need special collection classes for _Filter_s or _QueryModifier_s. All a collection class needs to know is that it has to accept SpecificationInterface implementations, call the modify method on each child and if it returns something then combine results with 'AND' or 'OR'. I don't know if this is a good design choice, but I preferred this from my original implementation. I liked this interface better compared to separate getFilter
and getQueryModifier
methods, but this decision is not based on anything other than a personal opinion :)
The ResultModifier was an idea I liked and my original code lacked, so I shamelessly copied that idea. Also was playing with the tests and noticed the coverage, so I tried to get the test coverage up a little as well as trying out phpspec. I published the package since colleagues wanted to use this for an internal project as well and they also needed the pagination.
Disclaimer It's quite late and I'm mainly typing this from memory, so if I made some mistakes/typos or if something was unclear please let me know. I'm not opposed to the idea of working together somehow, I just felt it wasn't my place to make a PR with all these (opinionated) changes. Also: sorry for the wall of text.
Thank you for the long post explaining your thoughts. It looks like you have made lots of choices from opinions, which is a good thing. You created stuff that was easy to use. That has been one of our issues. We are more focused of doing things "The right way" which have made our DX suffer. If we work hard we could find a "right way" that is easy to use.
The first difference is I wanted to have the ability to receive the Query object after calling match() on the repository. This meant I would be able to use a pagination library, which I was using in the prototype (a Symfony2 HATEOAS project). This was something I got used to and was frankly not ready to give up yet.
I have considered the same. I got an open issue about this actually. It might be a better/more flexible way of doing it. I will not argue against it.
The second main difference is that the Specification class can take children directly, I did not want to have a separate class be responsible for that. This meant the specification had to contain all the logic from the Happyr LogicX class, making my AndX/OrX classes a little obsolete since they just extend Specification and call some setters. I do like to have them for readability when composing a spec though.
We have chosen to have a AndX/OrX class to follow the specification pattern. IMHO will this makes it easier to see how the Logic tree is built.
We recently added a feature which made the API more fluent by adding AndX::andX
and OrX::orX
.
The third main difference is that the interface is the same between Filters and QueryModifiers unlike in the Happyr package. In my case it's all a SpecificationInterface, which solved an issue with nesting certain types I saw initially in the Happyr package (this has been solved now).
The reason behind this is the separation of concerns principle. We did not wanted to have one method Specification::modify
that could both change the query (Join, OrderBy, Limit) and filter the result set (IsNull, Equals). This should (and is now) only an internal issue. Applications developers should not bother about this and the should always use the Specification
.
I have considered the same. I got an open issue about this actually. It might be a better/more flexible way of doing it. I will not argue against it.
I'm not sure, but I think if you have a service layer in your application, then it's no problem to chain the execute
method with the repositories match
-call, your service layer can still return the actual data.
However I can see that this normally is the task of the repository, and the service layer shouldn't know anything about Query-objects. I like the flexibility, but I know I am moving responsibility to retrieve the data one level up (the repositories calling class, instead of repository itself). This last part is something which always felt dirty, but haven't found a nice way around that.
The reason behind this is the separation of concerns principle. We did not wanted to have one method Specification::modify that could both change the query (Join, OrderBy, Limit) and filter the result set (IsNull, Equals). This should (and is now) only an internal issue. Applications developers should not bother about this and the should always use the Specification.
I agree with the separation of concerns. I was also planning to look into Doctrine's Criteria objects for creating the condition/filter and using the Criteria object in the where-part (instead of a string), however I haven't really looked into it. Originally I had to rewrite Yii ActiveRecord code to Doctrine, and had difficulty at the time to convert Yii criteria objects into Doctrine code. Doctrine at the time had no support (2.1/2.2), but now I noticed they do. This however would also mean the Doctrine version requirement would be bumped to 2.3+.
I didn't like the fact that both methods originally needed the QueryBuilder object, and there was nothing preventing you from using the getFilter
method as a QueryModifier. Thats why I got thinking about letting getFilter
modify/return a Criteria object, instead of working with the query builder object. Right now there is nothing preventing you from adding joins within a getFilter
method.
I'm not sure if this will cause issues in your case, but then it's more clear (IMO) that one method handles query building, the other handles query criteria. I will have to read the documentation about Criteria object about what methods it has.
I will investigate this further, since I forgot investigating this before (so thanks for reminding!).
However I can see that this normally is the task of the repository, and the service layer shouldn't know anything about Query-objects. I like the flexibility, but I know I am moving responsibility to retrieve the data one level up (the repositories calling class, instead of repository itself). This last part is something which always felt dirty, but haven't found a nice way around that.
We should be able to find a way around it. Or some way to use the Paginator. Do you know if there is some other feature like the Paginator that we should support?
I didn't like the fact that both methods originally needed the QueryBuilder object, and there was nothing preventing you from using the getFilter method as a QueryModifier. Thats why I got thinking about letting getFilter modify/return a Criteria object, instead of working with the query builder object. Right now there is nothing preventing you from adding joins within a getFilter method.
You are very correct. There is nothing stopping you from using getFilter as a QueryModifier. It should be though... I guess we could do something smart here. The issue to work around is the $qb->setParameter()
.
I'm not sure if this will cause issues in your case, but then it's more clear (IMO) that one method handles query building, the other handles query criteria.
I agree. If we rewrite the signature of getFilter we force the seperation of conserns which will make it more clear.