This site has been archived and you can no longer log in or post new messages. For up-to-date community resources please visit ezplatform.com

eZ Community » Forums » eZ Publish 5 Platform » Semantics vs pragmatism in eZ Publish 5
expandshrink

Semantics vs pragmatism in eZ Publish 5

Semantics vs pragmatism in eZ Publish 5

Wednesday 06 March 2013 7:29:01 pm - 14 replies

Being in mid-week of a Symfony2/eZ Publish 5 training course, a concern as arisen that I can't get out of my head.

Due to separation of concerns it is no longer possible (or at least not encouraged) to interact directly with the content repository from the templates.  This is of course a good thing, but having at the same time discovered the level of verbosity (currently) required to accomplish the equivalent of a template content-fetch (check out http://partialcontent.com/code/working-with-ez-publish-5-subrequests if you don't know what I mean), I'm left feeling the need to sacrifice man hours for semantics in order to accomplish what we've been used to so far with 4.x.

My initial thought is that there should be a wrapper (service) layer between the API (at least as long as the API has the current verbosity level) and the controllers allowing one to do something like the following at the controller-level.

 $itemList = $contentRepo
    ->parent($parentLocationID)
    ->type(['folder', 'article'])
    ->search();

This is of course something that each eZ solution partner could (fairly easily) roll on his/her own, but that doesn't make sense to me as I assume it would be useful to most people developing with eZ Publish.

I also find myself tempted to create generic subrequests like "getChildren()", passing in a parent location and a list of acceptable content types, but this feels like trying to go the opposite way of what eZ is trying to accomplish (I could, BTW, use some best practice considerations from eZ on this). Still, it seems redundant to create separate controller actions for each and every content retrieval when in 80% of the cases you just want to get a children list.

More of a rant than a question this, but I would love to get someone elses 2 cents on this.

Wednesday 06 March 2013 9:37:59 pm

That's my impression exactly, Eirik. The Public API is so verbose that it's practically begging for someone to write a wedge (what I called 'syntactic sugar' in the article you linked) that makes the code simpler for normal operations. There are already a few of those sorts of things floating around, so far focused on parsing some query language.

I would much prefer to see a fluent interface like the one you've used as an example, similar to the DB query abstraction in Zeta components. 

This seems like a situation where eZ would want to release it and control the code so that there's not fragmentation at the controller level, or a third party bundle required to run most community code examples.

I tried to keep things simple for the tutorial, but Damien actually (correctly) broke this out as a service for planet-ezpublish.fr. It seems like far more work than it should be for such a key component as sub-content queries. 

Would something like that be a welcome pull request, eZ folks?

Wednesday 06 March 2013 10:24:16 pm

Thinking a bit more about this, you could actually do this without much change to the existing API by adding some methods to Query: 

$q = Query::create()
    ->criterion('subtree', $subTreeLocation->pathString)
    ->criterion('contentTypeIdentifier', $typeIdentifiers)
    ->sort('field', 'article', 'publication_date', Query::SORT_DESC)
    ->limit(10)
    ->offset(0);
 
$posts = $this->getRepository()->getSearchService()->findContent( $q ); 

 

The static method Query::create returns an instantiated Query object. The criterion method (separate from the property) treats the first parameter as a named criteria, and the other arguments are passed to the criteria's constructor. It instantiates the criterion, adds it to the Query, and returns the Query again. Logical And is implied for multiple calls. sort() and facet() would work similarly. All of the current properties would still work as-is, this would just be a convenience method for setting them in a concise way.

Two slightly harder bits: 

  • Service tags would probably need to be added for query extensions (custom facets, sorts, criterion), or any custom stuff would have to use the current syntax.
  • This would become a bit less elegant when logical And/Or/Not gets involved - not sure how to approach that in this example.

No idea if that would work, or be much better, but food for thought.

Modified on Wednesday 06 March 2013 10:26:10 pm by Joe Kepley

Wednesday 06 March 2013 10:55:12 pm

Quote from Joe Kepley :

I would much prefer to see a fluent interface like the one you've used as an example, similar to the DB query abstraction in Zeta components. 

At the very beginning we started a query builder, but we had to temporarily abandon it due to time constraints. Your example in your last reply is somewhat what we wanted to implement. Any contribution on this would be greatly appreciated, either by a PR or a custom bundle happy.gif Emoticon.

Wednesday 06 March 2013 11:08:57 pm

We have this on our radar as well, there is clearly a need for a helper / simple / high level api, both in terms of php code by also in terms of helpers in templates.

As pointed out by both of you, for search a query builder would be helpful (we actually had one a year ago. I'll see if we can see if I can find it, might be parts we can still reuse).
Query builder is something we should provide independently of a simple API. Below is a possible example that tries to keep the IDE & type friendliness of Erik's example, with the query centric logic of Joe's example:

$qb = $searchService->getQueryBuilder()
    ->parentLocation( $location )// Location|int
//  ->subTree( $location )// tree search, takes Location, path & id (looks up path for you)
    ->contentType( array( 'folder', 'article' ) )// array|string|int
    ->limit( 10 )
//  ->offset( 0 )// default value is 0
    ->sortByContentId( 'asc' );// = Query::SORT_ASC
 
$items = $searchService->findContent( $qb->query );
//$item = $searchService->findSingle( $qb->criterion );//Alias for $qb->query->criterion

 A simple API could then later come alone and allow you to avoid having to get searchService, and simplify the returned result object, which is quite verbose.

Modified on Thursday 07 March 2013 5:29:05 pm by André R

Wednesday 06 March 2013 11:21:16 pm

I think the query builder can be found here:

https://github.com/ezsystems/ezpublish-kernel/tree/before_ezp_removal/ezp/Content/Query

https://github.com/ezsystems/ezpublish-kernel/blob/before_ezp_removal/ezp/Content/CriterionFactory.php

 

It is not as simple as the examples here, but it covers more then just the equal() and in() operations.

Modified on Wednesday 06 March 2013 11:22:28 pm by André R

Wednesday 06 March 2013 11:27:31 pm

This is brilliant feedback, thanks Eirik and Joe for bringing this up!

Thursday 07 March 2013 5:02:12 pm

Quote from André R :

We have this on our radar as well, there is clearly a need for a helper / simple / high level api, both in terms of php code by also in terms of helpers in templates.

As pointed out by both of you, for search a query builder would helpful, we actually had one a year ago, I'll see if we can see if I can find it, might be parts we can still reuse.
Query builder is something we should provide independently of a simple API. Below is a possible example that tries to keep the IDE & type friendliness of Erik's example, with the query centric logic of Joe's example:

$qb = $searchService->getQueryBuilder()
    ->parentLocation( $location )// Location|int
//  ->subTree( $location )// tree search, takes Location, path & id (looks up path for you)
    ->contentType( array( 'folder', 'article' ) )// array|string|int
    ->limit( 10 )
//  ->offset( 0 )// default value is 0
    ->sortByContentId( 'asc' );// = Query::SORT_ASC
 
$items = $searchService->findContent( $qb->query );
//$item = $searchService->findSingle( $qb->criterion );//Alias for $qb->query->criterion

 A simple API could then later come alone and allow you to avoid having to get searchService, and simplify the returned result object, which is quite verbose.

I hadn't considered the IDE-friendliness, but it's important. Perhaps it could use Eirik and André's approach for built-in types, and provide an 'addCriterion','addSortClause', etc method for custom types. 

Thursday 07 March 2013 7:57:41 pm

As a matter of fact, the API that allowed the query builder to work is still in the Criterion classes !

It is based on two things:

  • getSpecifications
  •  createFromQueryBuilder

createFromQueryBuilder is called (statically) from Query\Builder. getSpecifications is called by the base Criterion class (eZ/Publish/API/Repository/Values/Content/Query/Criterion.php) to check that the provided parameters are semantically valid, based on the description provided by each component through that method.

The Query Builder API would in any case have to be moved around, but I think it would be quite easy to get in back in somehow.

It would be a first step to improve developer's life, especially with an idea we discussed today together with Jérôme and Damien.

In any case, the fact is that we had to make choices during this rewrite. And the choice was to keep the basis cleanly separated, and lower level than what we had in mind earlier in the project. The various layers the API relies on, along with the massive amount of testing (really, massive) unfortunately increase the cost of user-friendliness inside the API.

There are several ways around this, but the goal is still to provide our developers with a clear, user-friendly, stable API. So far, it is only clear and stable happy.gif Emoticon

Modified on Friday 08 March 2013 10:45:15 am by Bertrand Dunogier

Friday 08 March 2013 9:39:27 pm

OK, I couldn't sleep this morning, so I took a quick stab at this. 

https://github.com/blendinteractive/blendezquerybuilderbundle

I've only implemented a few of the methods and tests at the moment, but here's the general gist: 

$qb = new QueryBuilder();
$qb = $qb
        ->contentTypeIdentifier('blog_post')
        ->subtree($root->pathString)
        ->sortClause(new SortClause\Field('blog_post','publication_date', Query::SORT_DESC))
        ->limit($limit)
        ->offset($offset);
$postResults = $this->getRepository()->getSearchService()->findContent($qb->query); 

That's real code from one of my controllers. It replaced a sizable chunk of API code, and it's IDE-friendly.

The trick will be working in the logical operators in so as to keep things concise. One approach would be to have only the logical operators 'build' the query, and require their use rather than implying 'and':

$qb = new QueryBuilder();
$qb = $qb->and(
            $qb->contentTypeIdentifier('blog_post'),
            $qb->or( $qb->sectionId(5), $qb->creator(Operator::EQ, 5))
        )
        ->sortClause(new SortClause\DateModified(Query::SORT_DESC))
        ->limit($limit)
        ->offset($offset);
 
$postResults = $this->getRepository()->getSearchService()->findContent($qb->query);

That would allow for logical operators, but it's much less obvious to use. I'm open to suggestions. Maybe something with a closure?

Modified on Friday 08 March 2013 9:40:49 pm by Joe Kepley

Saturday 09 March 2013 8:50:23 am

Hi Joe,

This is looking very good, and very close to what I had in mind (you need to sleep less more often blunk.gif Emoticon. Here are a few suggestions (a lot of personal preference here, so feel free to ignore what doesn't make sense to you).

  • I would name the contentTypeIdentifier method just 'type' or have an alias by that name. I think the 'content' part is implied, and that the method should accept both IDs and identifiers, either as a single variable or an array.
  • A 'parent' method which accepts the location parent id.
  • I would name the sortClause method simply 'sort'.
  • I'm still very new to SF2, but I guess the query builder also would be accessible by doing $this->get('blend_ez_query_builder.builder'), right? A short and sweet identifier like 'ez_query_builder' (as an alias, perhaps?) would be preferable (provided it does not conflict with anything from eZ).
  • Could we perhaps also simplify the call to the findContent method of the search service by wrapping this in a find() or search() method in the query builder itself?

Here's an example making use of the suggestions:

$qb = $this->get('ez_query_builder');
$postResults = $qb
        ->type('blog_post')
        ->parent($locationId) // alternative to subtree
        // ->subtree($root->pathString)
        ->sort(new SortClause\Field('blog_post','publication_date', Query::SORT_DESC))
        ->limit($limit)
        ->offset($offset)
        ->search();

 

I would be happy to help out with this (if you'd like), but I think I need some more SF2 practice before I can contribute something useful.

PS. Regarding sorting, I think a default sorting method should be the sorting method set of the parent node (if no other sort method is applied). It's not evident to me (from the available SortClauses) how this would be accomplished, though.

Sunday 10 March 2013 11:54:29 am

I'm sure you want to include in your dev efforts the author of this extension as well: http://projects.ez.no/aw_ezp_fetch_bundle

And just ping me when it's ready, I'll add on top of that the parser for the jquery/css3 selector syntax that is REALLY going to make it easy for developers (see http://share.ez.no/blogs/gaetano-giunta/little-nemo-in-slumbercode-part-1-can-jquery-meet-the-ez-template-language)

Friday 14 February 2014 10:08:27 am

Hi all,

 

long time no see happy.gif Emoticon
This topic keeps on popping up so I took the liberty to create two Feature requests on this which I hope cover the needs, one for having a simplified query builder in controllers like we spoke about above, the second one to have a even simpler syntax in templates built on top of a query controller that reuses the "filter" naming convention used on the query builder.

 

#1 Query Builder: https://jira.ez.no/browse/EZP-22344

#2 Query Controller: https://jira.ez.no/browse/EZP-22348

Would this cover the needs for everyone?

Modified on Friday 14 February 2014 10:16:17 am by André R

Friday 14 February 2014 10:17:45 am

\o/

Friday 14 March 2014 4:21:35 pm

Note that a blog post was made on the Engineering blog, as the follow-up of the stories linked by André: http://share.ez.no/forums/discuss...back-the-query-builder/(offset)/last.

expandshrink

You must be logged in to post messages in this topic!

36 542 Users on board!

Forums menu

Proudly Developed with from