Working with Express objects programmatically

Permalink 4 users found helpful
Hi,

Liking the new Express objects. However, documentation is lacking severely when it comes to working with Express in code. The couple of videos posted don't really cut the mustard if you want to do some serious work with these.

Actually, documentation has been a major problem with Concrete ever since 5.7 - I mean, just contrast the Developer docs for 5.4 and 5.7 and you'll see what I mean. Pretty useless.

What can be done? Shouldn't the C5 team either buckle down and create proper docs, or maybe get the community involved some more here? Huddled masses unite!

Anyway I digres, I have been working with Express objects a bit, and I will be documenting my efforts here for others to peruse. Maybe you'll find some of this useful.

Selecting an Express object by handle (user_bookmarks - think of this as "Marina" in the C5 guide), finding a specific Entry by custom attribute (here, user_id) and then list the one to many associations to another Express object called "Bookmark" (think of this as Boats in the C5 guide)

use Concrete\Core\Support\Facade\Facade;
use Concrete\Core\Support\Facade\Express;
use Concrete\Core\Entity\Express\Entity;
use Concrete\Core\Express\Entry\Search\Result\Result;
use Concrete\Core\Express\EntryList;
class Bookmarks
{
  public $entityManager;
  public $app;
   public function __construct() {
     $this->app = Facade::getFacadeApplication();
     $this->entityManager = $this->app->make('database/orm')->entityManager();
   }
    /* Get user bookmarks from Express object with corresponding User ID attr set */
    public function getUserBookmarks($userid) {


Here is another way of doing things. We just filtered our list of objects by an attribute (user_id) - but what if the object is already associated to something as an atribute? Here we have it associated to a User attribute, and in this way we can pull the list like so:

$ui = UserInfo::getById($userid);
      $ui_bookmarks = $ui->getAttribute("user_bookmarks");
      if (is_object($ui_bookmarks)) {
        //Good, the user has a bookmark attr value associated, grab the entity so we can read from it
        $entries = $ui_bookmarks->getSelectedEntries()->getValues()[0]->getBookmark();
        if (count($entries)) {
          foreach ($entries as $bookmark) {
//This nastyness gets a specific Select Attribute Option Value
            $type = $bookmark->getBookmarkType()->getSelectedOptions()->getValues()[0]->getSelectAttributeOptionValue();
            $data[] = Array(
             "type" => $type,
             "entity_id" => $bookmark->getBookmarkEntityId(),
             "note" => $bookmark->getBookmarkNote()
           );
         }


I will be posting more examples as I figure them out. Namely: Creating new Entries, associating an Object to a user attribute (will probably work the same way for pages) and maybe some more odds and ends.

 
andrew replied on at Permalink Reply
andrew
These are helpful. You should consider posting them to the documentation site as either specific developer tutorials or additions to the Express documentation itself.
Hammertime replied on at Permalink Reply
Hey Andrew

Alrighty, I might do that. I'll start with maintaining this post for the next few days and document my efforts and make everything pretty. I might then hit you up for instructions on submission to your docs - if my approach cuts the mustard, that is. I mean, I found a way to do these things - but it's not guaranteed that it's the best, nor the easiest, way of going about it.

Keep up the good work! :O)
Hammertime replied on at Permalink Reply
Andrew

It would be great if you could give me a hint here as I am stuck on the association step.

- This code runs without errors, but I am not getting any association made between the entries.

- Do I need to read out existing entries and pass those along with the new one to Applier::associateOneToMany ?

$bookmark = new Entry();
      $bookmark->setEntity($bookmark_entity[0]);
      //Some DB voodoo here
      $this->entityManager->persist($bookmark);
      $this->entityManager->flush();
      //Yay! Let's set some data
      $bookmark->setAttribute("bookmark_type",$data["bookmark_type"]);
      $bookmark->setAttribute("bookmark_entity_id",$data["bookmark_id"]);
      $bookmark->setAttribute("bookmark_note",$data["note"]);
      //Now we associate it with the correct User Bookmarks entry
      $association = new OneToManyAssociation();
      $association->setSourceEntity($user_bookmarks_entity[0]);
      $association->setTargetEntity($bookmark_entity[0]);
      $this->entityManager->persist($association);
      $this->entityManager->flush();
andrew replied on at Permalink Reply
andrew
We will be releasing concrete5 8.1.0 early next week. One of its improvements is an improved Express object builder API. I would recommend checking out these docs and upgrade to 8.1 when it is available:

https://documentation.concrete5.org/developers/express/programmatica...
JohntheFish replied on at Permalink Reply
JohntheFish
Do singular handles and plural handles need to be distinct?

Supposing I was creating a sheep object. I can have one sheep or many sheep. The singular and the plural are the same.
tsorelle replied on at Permalink Reply
It would be extremely helpful if someone (Andrew?) could work up a set of simple code examples for CRUD operations against express.
- C: Insert an entry
- R: Retrieve an entry and get attribute values
- U: Change attribute values and save
- D: Delete an entry.
It is slow going slogging through the core code and limited documentation for clues.
andrew replied on at Permalink Reply
andrew
I have added this documentation. NOTE: it will require concrete5 version 8.1.0, which will be arriving today.

https://documentation.concrete5.org/developers/express/creating-read...
tsorelle replied on at Permalink Reply
Excellent! Thank you Andrew.
Hammertime replied on at Permalink Reply
Excellent work Andrew!

I would like to add a few notes, which you might want to add to the docs:

- You need to (this might depend on context) include a use Concrete\Core\Support\Facade\Facade; clause in addition to use Express;

- When retrieving an Express entry from a User attribute (this is probably the case for pages as well) you get an object of type ExpressValue. This means you have to do something like this to get the Entry object (in order to do associations):

$ui_bookmarks = $ui->getAttribute("user_bookmarks");
$entry = $ui_bookmarks->getSelectedEntries()->getValues()[0];


- When calling ->associateEntries()->set* on an Entry, you have to include all the entries you want associated. That is, this is a destructive update so you need to read out existing associations and pass those through. I dug around but could not find an addEntry() or equivalent ...

Please let me know if there is an easier way to do some of this... Here is my full code I ended up with for reading out and adding entries from a User attribute, with corresponding associations (much cleaner than my first attempts ... Thanks again Andrew!)

use Concrete\Core\Support\Facade\Facade;
use Express;
use UserInfo;
class Bookmarks
{
  public $entityManager;
  public $app;
   public function __construct() {
     $this->app = Facade::getFacadeApplication();
     $this->entityManager = $this->app->make('database/orm')->entityManager();
     ini_set('xdebug.var_display_max_depth', 7);
     ini_set('xdebug.var_display_max_children', 256);
     ini_set('xdebug.var_display_max_data', 1024);
   }
    /* Get user bookmarks from Express object with corresponding User ID attr set */
kfidel replied on at Permalink Reply
kfidel
Thank you very much, Andrew! It works like a charm.
One question: How can I make many to many associations? I always get
Argument 1 passed to Concrete\Core\Express\Association\Applier::associate() must be an instance of Concrete\Core\Entity\Express\Association, null given

when I do
$entry->associateEntries()->setRules([$rule1, $rule2]);
Hammertime replied on at Permalink Reply
Hey

I have been battling this as well. It turns out that you have to make sure that you are using the correct plural form of the object you are trying to associate with. So check if your object handles are "rule" and "rules" respectively

Also, I _think_ it's required that you set up a form for one (or both?) objects where you add the association.

Anyway, that seemed to do the trick for me when I ran into that one.

@Andrew: Is there a non-magic method for the line kfidel has problems with here? It would make it easier to debug methinks if you could do something like

$entry->associateEntries()->setAssociationByHandle("rule",[$rule1, $rule2]);


Or some equivalent.
kfidel replied on at Permalink Reply
kfidel
Hmm, I tried many things, singular and plural. Nothing worked for me. I also tried to pass an object instead of an array, a.s.o.
kfidel replied on at Permalink Reply
kfidel
Any hints about that problem?
aghouseh replied on at Permalink Reply
aghouseh
I'm seeing the same thing here. Creating the objects programmatically works as the docs suggest, but when attempting to associate, it's a no go. I don't get any errors thrown, but when I attempt to get the association after setting it, its null.
ukdevcatena replied on at Permalink Reply
ukdevcatena
this is because the documentation is wrong and was rushed...

the correct will be, using the example of documentation:

$student1 = Express::buildEntry('student')
    ->setStudentFirstName('Andrew')
    ->setStudentLastName('Embler')
    ->save();
$student2 = Express::buildEntry('student')
    ->setStudentFirstName('Jane')
    ->setStudentLastName('Doe')
    ->save();
$teacher = Express::buildEntry('teacher')
    ->setTeacherFirstName('Albert')
    ->setTeacherLastName('Einstein');
$teacher = $teacher->save();
$teacher->associateEntries()->setStudents([$student1, $student2]);
maschenborn replied on at Permalink Reply
maschenborn
I think I reached the same problem.
Is there a non-magic-function to reach the association?
$tukey_object = Express::buildEntry('tukeys')->setKey($tukey)->save();
            $links = [];
            foreach ($institute as $institut) {
                $tmp++;
                    $link = Express::buildEntry('link')
                        ->setInstitut($institut)
                        ->setLink('https://www.default.de')
                        ->save();
                    array_push($links, $link);
            }
            $tukey_object->associateEntries()->setLinks($links);

returns
Argument 1 passed to Concrete\Core\Express\Association\Applier::associate() must be an instance of Concrete\Core\Entity\Express\Association, null given, called in /homepages/20/d310593786/htdocs/relaunch2019/concrete/src/Express/EntryBuilder/AssociationUpdater.php on line 55
JeffPaetkau replied on at Permalink Reply
JeffPaetkau
I know this is an old thread but for reference the way to do this is:

$entry->associateEntries()->associate($handle, $association);


I had to actually go read the code to find this information though. It's not documented anywhere :(