How to Effectively use the Repository and Query Object of Extbase?

Extbase[1] supports very well the idea of Domain-Driven Design[2]. It supplies a Repository class to comfortably fetch your objects from. Some handy methods are already implemented. Let’s have a look at some example code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$blog = new Tx_BlogExample_Domain_Model_Blog('TYPO3 Development');
$blog->setDescription('A blog for professional TYPO3 developers.');

$administrator = new Tx_BlogExample_Domain_Model_Administrator();
$administrator->setName('Jochen Rau');
$administrator->setEmail('jochen.rau@example.com');
$blog->setAdministrator($administrator);

$post = new Tx_BlogExample_Domain_Model_Post('Effectively use the Query Object of Extbase');
$post->setContent('Extbase is a framework to develop ... hey, we\'re getting recursive now!');

$comment = new Tx_BlogExample_Domain_Model_Comment;
$comment->setDate(new DateTime);
$comment->setAuthor('Peter Pan');
$comment->setEmail('peter.pan@example.com');
$comment->setContent('Do you need some pills?.');
$post->addComment($comment);

$blog->addPost($post);

$blogRepository = t3lib_div::makeInstance('Tx_BlogExample_Domain_Repository_BlogRepository')
$blogRepository->add($blog);

In a first step, we create a new blog with an administrator, and a post with a comment. We instanciate the BlogRepository in line 21 and add the blog to it. That’s all we have to do. The blog is now persisted and can be fetched later from the repository again. We don’t have to say save($blog) at any time. The repository “takes care” of the blog.

Keep in mind that you have to instanciate the repository with t3lib_div::makeInstance() as it is a Singleton[3]. If you instanciate it with new the Repository will be always an empty one if Extbase searches for objects to be persisted.

The BlogRepository class is as simple as this:

1
2
3
class Tx_BlogExample_Domain_Repository_BlogRepository
    extends Tx_Extbase_Persistence_Repository {
}

Most of the methods needed are already implemented in the Tx_Extbase_Persistence_Repository. These are:

$repository->add($object)
$repository->remove($object)
$repository->replace($existingObject, $newObject)
$repository->update($modifiedObject)
$repository->findAll()
$repository->countAll() // Since Extbase 1.1
$repository->removeAll()
$repository->createQuery()
$repository->countByProperty($value) // Since Extbase 1.1
$repository->findByProperty($value)
$repository->findOneByProperty($value)

The methods add() and remove() are self-explanatory. replace() takes an untracked object and puts it at the “place” of an existing one where update() just takes an existing object and updates it with the property values of the modified object.

The findByProperty() and findOneByProperty() methods are magic methods where you can replace “Property” with the name of a property of the Blog object. For example we can write $blogRepository->findByTitle('My Blog') to fetch all Blogs with the given title. This also works for objects like in $blogRepository->findByAdministrator($administrator) but not yet for properties holding aggregates. Thus, $blogRepository->findByPost($post) won’t work by now. findOneByProperty() is pretty similar to findByProperty() but returns the only first Object found.

The countAll() and countByProperty() methods are similar to the according find methods. But the don’t build objects. They only count resulting objects.

All these methods keep the underlying storage solution (mostly a relational database) transparent to the user[4]. But what if we have special constraints that are not covered with the methods above? Let’s say we want to find the five most recent posts of a given blog. Then we can use a Query object. Let’s ask the PostRepository for those posts:

1
2
3
4
5
6
7
8
9
10
11
12
class Tx_BlogExample_Domain_Repository_PostRepository
    extends Tx_Extbase_Persistence_Repository {

    public function findRecentByBlog(Tx_BlogExample_Domain_Model_Blog $blog, $limit = 5) {
        $query = $this->createQuery();
        $query->matching($query->equals('blog', $blog));
        $query->setOrderings(array('date' => Tx_Extbase_Persistence_QueryInterface::ORDER_DESCENDING));
        $query->setLimit((integer)$limit);
        $posts = $query->execute();
        return $posts;
    }
}

The method createQuery() returns an appropriate Query object suitable for Post objects. In Line 6 we define the constraint the posts should match: $query->matching([constraint]). The constraint is returned by $query->equals('blog', $blog). There are some more methods returning a constraint:

$query->logicalAnd($constraint1, $constraint2);
$query->logicalOr($constraint1, $constraint2);
$query->logicalNot($constraint);
$query->withUid($uid);
$query->equals($propertyName, $operand, $caseSensitive = TRUE);
$query->in($propertyName, $operand); // Since Extbase 1.1
$query->contains($propertyName, $operand); // Since Extbase 1.1
$query->like($propertyName, $operand);
$query->lessThan($propertyName, $operand);
$query->lessThanOrEqual($propertyName, $operand);
$query->greaterThan($propertyName, $operand);
$query->greaterThanOrEqual($propertyName, $operand);

We can define the orderings with $query->setOrderings([orderings]) where [orderings] is expected to be an array of property-order pairs. There are two constants defining the order:

  • Order ascending: Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING
  • Order descending: Tx_Extbase_Persistence_QueryInterface::ORDER_DESCENDING

We limit the result set to a maximum of five posts with $query->setLimit((integer)$limit). And finally invoke $query->execute() which returns us the already build posts with all their comments inside (if we are not “lazy” ;-) ). There is also a method $query->count() which does not fetch the resulting posts but counts them. Note that the Query object enables chaining. Thus, the following code does the same as the given above:

1
2
3
4
5
6
7
public function findRecentByBlog(Tx_BlogExample_Domain_Model_Blog $blog, $limit = 5) {
    $query = $this->createQuery();
    return $query->matching($query->equals('blog', $blog))
        ->setOrderings(array('date' => Tx_Extbase_Persistence_QueryInterface::ORDER_DESCENDING))
        ->setLimit((integer)$limit)
        ->execute();
}

That’s it for today. I hope you got a deeper insight and I am looking forward to your comments. Maybe you want to join the discussion on the related mailing list. I have taken most of the code from the extension BlogExample which is available in the Extension Repository (TER).

Footnotes

  1. Extbase is a framework to develop Extensions for TYPO3
  2. Fo a brief introduction in DDD visit www.typo3-media.com 
  3. A Singleton is a design pattern to ensure the uniqueness of an object throughout a given scope. 
  4. “User” means in our case the Controller object in which we fetch the blogs and hand them to the View. More on the Model-View-Controller Pattern in a later post. 
  1. Thanks for this great post, Jochen.
    One question: Is it planned to execute the “findByProperty” work with aggregates ?

    PS: I like the idea to execute the query object as fluent interface :)

  2. Hey Jochen, thanks a lot for the post. I had to figure these things out myself – I’m now writing my first big extbase application.
    I’m definitly waiting for more extbase related posts. Keep it up!

    Dmitri.

  3. Thanks a lot for this article. Its short and good. Perhaps it can find the Way in the Forge Wiki.

    Thomas

  4. Hi Jochen,

    thanks for this article, it helps a lot to have the ideas together on a single post.

    Just a small hint: There’s a little typo in the already implemented methods of the repository. I think it should be $repository->remove($object) and not $repository->removes($object)

    Greetings

    Michael

  5. Axel Brand says:

    Hey,

    thanks for the helpfull article.
    Can you tell me how connect to external data-storage (e.g. per soap or external database) and map the results to the model?!
    Tx_Extbase_Persistence_Mapper_DataMapper->map(modelClass, $results) does not work if $results is an simple array cause Tx_Extbase_Persistence_RowIteratorInterface is expected.

    Greetings, Axel

  6. Oliver says:

    Hey Jochen,

    thanks a lot for the post. I had to figure these things out myself.
    I’m definitly waiting for more extbase related posts.

  7. Henjo says:

    Hi Jochen,

    nice! This article doesn’t only help in the sense of providing information about this particular part, but helps to make to read other parts of the source as well.

    Looking forward to more articles! ;-)

    Henjo

  8. Kim says:

    Hi
    I am looking for a reverse lookup in the repository.

    I often need to get the used categories in a selectbox. so i have to get the categories that was linked in the “Aggregate Root”.

    in SQL
    SELECT c.name FROM caterories AS c
    LEFT JOIN jobs AS j
    ON j.category = c.uid
    GROUP BY c.uid

    I have absolutely no clue how to do this without custom SQL

  9. Daniel P says:

    Hey Jochen – thx for the informative article.

    Realy cool is the automagic behind MM Table relations:

    if (count($tags)>0) {
    $constrains[] = $query->in(‘txPageresourceTags.uid’,$tags);
    }

    $query->matching(
    $query->logicalAnd(
    $constrains
    )
    );

  10. Patrick F. says:

    Issn’t makeInstance() obsolete?

    cheers

  11. Still, PageRank is vital for those of us with new locations because the update should show the advancement of our weblog.

  12. Katrin H. says:

    Thank you for this great information.
    I just have tried this, but I have met a problem.
    How can I add data to an 1:m relation?

    Example:
    gallery {name, image}
    image {img, title}
    One gallery contains several images.
    How would I add a new image to a gallery?

    • Jochen Rau says:

      It should be as easy as $gallery->addImage($image). Have a look at the BlogExample. The relation Blog->Post is equivalent to your problem.

  13. Felix Nagel says:

    “replace() takes an untracked object and puts it at the “place” of an existing one where update() just takes an existing object and updates it with the property values of the modified object.”

    Am I wrong or is this explanation wrong? Afaics the update function just gets the uid and uses replace, which just deletes and adds the new object. So its possible (if the modified object does not contain all properties) to lose properties when using update.

    tl;dr
    Update is no real update but a shortcut for replace.

    • Jochen Rau says:

      Yes and no. The update function uses in fact the replace function as you described it. But you don’t “loose” the properties (better: their values), it just persists the state of the object you provided. I should better write: “where update() just persists the current state of an existing object.”

      • Felix Nagel says:

        Thanks for the fast response.

        Perhaps I should try to make this more clear:
        We have object1 with attr1, attr2, attr3 in our DB and another object2 with attr1 and attr2. When using replace(object1, object2) the attr3 value will be lost. In my definition thats not updating its replacing.

        • Jochen Rau says:

          You are right, because you replace object1 with object2. That’s the intended behavior. Following your example, object1 and object2 are different objects. But I assume you meant something like update(object). In this case, your distinction between object1 and object2 means something different. Namely, two different states of the same object. Only then, update() makes sense (and Extbase checks this by comparing the uids). So, if you reconstitute an object (with attr1, attr2, attr3), then set attr3 to NULL and invoke update(object), attr3 will be NULL the next time ist is reconstituted again. This is imo the intended behavior. You cannot update an object1 with the attribute values of an object2.

          • Felix Nagel says:

            So I will need to set each poperty manually in order to have a real update :-/
            Thanks for the clarification.

          • Jochen Rau says:

            No need to :-/, Felix. :-)! If you need to use an object as an “interface” to a SQL UPDATE, why don’t you just use $repository->findByUid($uid) to fetch the object, manipulate it and let Extbase take care of the persistence?

          • Felix Nagel says:

            Thats what I meant. Just get the old object and use setXyz for each changed property.

  14. acti says:

    Where is the >effective< way to use the repository and query objects? It's just a way you can use them. The topic suggests an effective way that is not explained… What is effective in this context? :(

  15. Hey Jochen,

    how to get the pid in Controller View?

    Best Regards
    Waldemar

  16. Ganybhat says:

    Hi Jochen,
    How to write the UPDATE query ?
    I wanted to update the some other table immediately after create query is executed.
    $this->ordersRepository->add($newOrders);

    • Jochen Rau says:

      Adding an object to a repository doesn’t persist it to the database immediately. You need to call persistAll() on the PersistenceManager.

  17. Tom says:

    Extbase halte ich für ein möglicherweise durchdachtes Konzept, aber wie alles in TYPO3 ist das Ganze dermassen schlecht dokumentiert, dass man Stunden für einfachste Dinge benötigt. Versuch mal SQL-Abfragen “zusammenzubauen”, die etwas schwieriger sind, wie z.B. FIND_IN_SET, BETWEEN oder auch Kombinationen von OR- und AND-Befehlen. Da bekommst Du echt Tobsuchtsanfälle.

  18. Even the Article is already older now, I’ve still have an issue where I never found any information:

    Using your technic works fine but not all queries are as easy as findAll().
    As I just want to restrict the access to one user (the current fe_user) I think there must be a smarter way than overwriting the whole repository.
    In general I just want add
    $query->matching($query->equals(‘user’, $fe_user));
    to all queries and it’s a bit difficult and needs some time to rewrite all the querying functions.

    • Jochen Rau says:

      To define a subclass of the Repository class in order to add custom functionality is the way to go in the OOP world. Matching against the fe_user is only one way to authorize the access to a resource. I would rather keep the Repository slim and allow developers adding custom solutions on top of it, than covering all the possible cases in one class.

  19. Jennifer Koenig says:

    This techniques in this article work for me (we are using Typo3 4.7 and Extbase), but for one problem: in the table where a record for the object is inserted, all the fields except uid and pid are empty. I’ve checked the data being set and it is there. Is there some kind of mapping from object properties to fields that one has to do – in the TCA maybe? Or the Configuration/setup.txt ?

  1. [...] TYPO3Development » Blog Archive » How to Effectively use the Repository and Query Object of Extbas… Extbase[] supports very well the idea of Domain-Driven Design[]. [...]

Leave a Reply