Strange issue calling migxLoopCollection with `$modx->runSnippet`

My Snippet calls migxLoopCollection with $modx->runSnippet.

To do this it builds a series of where conditions as an array, these are based on frontend user selections (none, parent, family, date). An entry is added to the array for each user selection eg. $arr_where[] = '{"family":"' . $family . '"}';.

The Snippet then generates a single $where string by imploding the array ie. $where = implode(',', $arr_where);.

The resulting value of $where string becomes the JSON required for the migxLoopCollection call eg. {"family":"3"},{"published":"1"}.

Unltimately the $where variable is used, along with other relevant parameters, as

$modx->runSnippet('migxLoopCollection',array(
    'where' => '[' . $where . ']',

The first strange part is this works as expected, depending on the user selection (2 of 4 combinations work).

Checking the value of the computed $where variable for each combination shows each is correct.

{"published":"1"} - works
{"family":"3"},{"published":"1"} - fails
{"family":"2"},{"parent":"6"},{"published":"1"} - fails
{"date:>=":"2024-10-01"},{"date:<=":"2024-10-31"},{"published":"1"} - works

It becomes more strange if I manually set the $where value in my Snippet to those noted above - in this case the migxLoopCollection call works every time eg. when I set

$where = '{"published":"1"}'; - works
$where = '{"family":"3"},{"published":"1"}'; - works
$where = '{"family":"2"},{"parent":"6"},{"published":"1"}'; - works
$where = '{"date:>=":"2024-10-01"},{"date:<=":"2024-10-31"},{"published":"1"}'; - works

But there is no difference in the $where value set manually or computed by the snippet.

My first thought was this might somehow be caused by character encodiing but running mb_detect_encoding() on the $where value shows UTF-8 every time.

The actual Snippet is 500 lines so I’m not sure it should be posted in full.

Can anyone suggest what the issue might be?

I know it would be better to remove migxLoopCollection and use native xPDO instead, but my attempts to configure the joins for my schema and database tables failed hence migxLoopCollection looked like a reasonable option at the time.

Any help appreciated.

Thanks

It could be that the where property you are generating is just not valid JSON.
Maybe instead try to create the JSON-string like this:

$where_clauses = [];
$where_clauses[] = ["family" => 2];
$where_clauses[] = ["parent" => 6];
$where_clauses[] = ["published" => 1];
$where_json = json_encode($where_clauses);
1 Like

@halftrainedharry your suggestion produces the the same result, although the json string has one minor difference.

My version sets all the key values as strings eg. {"published":"1"} and your json_encode version is is technically more correct because it sets the key valus as integers eg. {"published":1} which is a better approach I will use in my code - thank you.

However migxLoopCollection accepts both versions and produces the same result.

So the strange issue persists - if I write the value of $where_json to screen and copy/paste the output to manually set $where_json as

$where_json = '[{"family":2},{"parent":6},{"published":1}]';

migxLoopCollecton works as expected.

The error log has an entry I should have included yesterday. My snippet is wrapped with pdoPage to provide ajax Load More functionality.

For the WHERE conditions that fail, selecting Load More produces the following error log

[2023-12-07 11:02:32] (ERROR @ /home/webapps/dev/httpdocs/core/xpdo/xpdo.class.php : 644) Could not load class: VenueFamily from mysql.venuefamily.
[2023-12-07 11:02:32] (ERROR @ /home/webapps/dev/httpdocs/core/xpdo/xpdo.class.php : 762) VenueFamily::load() is not a valid static method.

The error doesn’t appear for the successful WHERE conditions and I’m not sure it can help resolve the current issue.

Any further suggestions would be very much appredciated.

Thanks.

MODX doesn’t know your custom class.

Where do you call addPackage? Do you use MODX 2.x or 3?
What’s the code of the snippet “get__object_field”?

Using MODX 2.8.5.

The get_events snippet doesn’t call $modx->addPackage, it relies on migxLoopCollection to load the class and query the database.

The get__object_field snippet is

<?php
/**
 * &class - string, name of the object class name eg. VenueExperience
 * &where_key - string, key is the column to search
 * &where_value - string, value is the row to retrieve
 * &field - string, name of the field to return the value for
 * 
 * &to_placeholder - string, name of placeholder to set with output value, default: none
 * 
 * TODO:
 * - extend key/value parameters to allow array to be set in snippet call eg. by explode on key1==value1||key2==value2, or loop over all script properties except those named above?
 * - validate we have all required values
 * 
 */
 
/* example use
    [[get__object_field?
        &class=`VenueExperience` 
        &where_key=`id` 
        &where_value=`[[+rel_experience]]` 
        &field=`title`

        &to_placeholder=`rel_experience`
    ]]
*/


// get the object eg. $obj = $modx->getObject('VenueExperience', array('id'=>1));
// NOTE: if no $criteria parameter is specified, no class is found, or an object cannot be located by the supplied criteria, null is returned.
$obj = $modx->getObject($class, array($where_key => $where_value));

// check if valid obj has been retreived
if (!is_null($obj)) {
    $output = $obj->get($field);

    // if 
    if (!empty($to_placeholder)) {
        $modx->setPlaceholder($to_placeholder, $output);
    } else {
        return $output;
    }
}

Relevant parts of MIGXdb schema (full file is too big to post), if it helps is

<?xml version="1.0" encoding="UTF-8"?>
<model package="venue" baseClass="xPDOObject" platform="mysql" defaultEngine="INNODB" version="1.1">
    <!--
        Class - VenueFamily - football, concert
    -->
    <object class="VenueFamily" table="venue__family" extends="xPDOSimpleObject">
        <field key="name" dbtype="varchar" precision="100" phptype="string" null="false" default="" />
        
        <!-- slug to act as furl fot CustomRequest -->
        <field key="slug" dbtype="varchar" precision="100" phptype="string" null="false" default="" />

        <!-- image will have generic alt tag hard coded eg. 'image depicting the Title family' -->
        <field key="img_src" dbtype="varchar" precision="155" phptype="string" null="false" default="" />

        <composite alias="Parents" class="VenueParent" local="id" foreign="family" cardinality="many" owner="local" />

        <!-- aliases from original venue.mysql.schena.xml
            <composite alias="Experiences" class="VenueExperience" local="id" foreign="family" cardinality="many" owner="local" />
            <composite alias="Events" class="VenueEvent" local="id" foreign="family" cardinality="many" owner="local" /> 
        -->


        <!-- used by MIGX -->        
        <field key="deleted" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="published" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="pos" dbtype="int" precision="10" phptype="integer" null="false" default="0" />

        <!-- good practice for tracking -->
        <field key="createdon" dbtype="datetime" phptype="datetime" null="true"/>
        <field key="createdby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="editedon" dbtype="datetime" phptype="datetime" null="true"/>
        <field key="editedby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
        
        <!-- track createdby and editedby -->
        <aggregate alias="CreatedBy" class="modUser" local="createdby" foreign="id" cardinality="one" owner="foreign" />
        <aggregate alias="EditedBy" class="modUser" local="editedby" foreign="id" cardinality="one" owner="foreign" />
    </object>

    <!--
        Class - VenueParent - scot_prem_league, viaplay_cup, jayz_world_tour, sheeran_mathematics_tour
    -->
    <object class="VenueParent" table="venue__parent" extends="xPDOSimpleObject">
        <field key="family" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="name" dbtype="varchar" precision="100" phptype="string" null="false" default="" />
        
        <!-- slug to act as furl fot CustomRequest -->
        <field key="slug" dbtype="varchar" precision="100" phptype="string" null="false" default="" />
        
        <!-- image will have generic alt tag hard coded eg. 'image depicting the Parent group' -->
        <field key="img_src" dbtype="varchar" precision="155" phptype="string" null="false" default="" />

        <index alias="family" name="family" primary="false" unique="false" type="BTREE">
            <column key="family" length="" collation="A" null="false" />
        </index>
        <index alias="name" name="name" primary="false" unique="false" type="BTREE">
            <column key="name" length="" collation="A" null="false" />
        </index>

        <aggregate alias="Family" class="VenueFamily" local="family" foreign="id" cardinality="one" owner="foreign"/>
        <!-- <composite alias="Event" class="VenueEvent" local="id" foreign="parent" cardinality="many" owner="local" /> -->


        <!-- used by MIGX -->        
        <field key="deleted" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="published" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="pos" dbtype="int" precision="10" phptype="integer" null="false" default="0" />

        <!-- good practice for tracking -->
        <field key="createdon" dbtype="datetime" phptype="datetime" null="true"/>
        <field key="createdby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="editedon" dbtype="datetime" phptype="datetime" null="true"/>
        <field key="editedby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />

        <!-- track createdby and editedby -->
        <aggregate alias="CreatedBy" class="modUser" local="createdby" foreign="id" cardinality="one" owner="foreign"/>
        <aggregate alias="EditedBy" class="modUser" local="editedby" foreign="id" cardinality="one" owner="foreign"/>
    </object>

    <!--
        Class - VenueEvent
    -->
    <object class="VenueEvent" table="venue__event" extends="xPDOSimpleObject">
        <field key="parent" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="family" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />

        <field key="title"                 dbtype="varchar" precision="150"  phptype="string"   null="false" default="" />

        <!-- slug to act as furl for CustomRequest -->
        <field key="slug"      dbtype="varchar" precision="100" phptype="string" null="false" default="" />
        <field key="introtext" dbtype="varchar" precision="155" phptype="string" null="false" default="" />
        <field key="date"                  dbtype="datetime"                 phptype="datetime" null="true"  default="1000-01-01 00:00:00" />
        <!-- images will have generic alt tag hard coded eg. 'image depicting the Title experience' -->
        <field key="img_src" dbtype="varchar" precision="155" phptype="string" null="false" default="" />
        <field key="img_superhero_src" dbtype="varchar" precision="155" phptype="string" null="false" default="" />
        <field key="host"                  dbtype="varchar" precision="150"  phptype="string"   null="false" default="" />
        <field key="host_img"              dbtype="varchar" precision="255"  phptype="string"   null="false" default="" />
        <field key="guest"                 dbtype="varchar" precision="150"  phptype="string"   null="false" default="" />
        <field key="guest_img"             dbtype="varchar" precision="255"  phptype="string"   null="false" default="" />
        <field key="general_admission_url" dbtype="varchar" precision="255"  phptype="string"   null="false" default="" />

        <!-- used by MIGX -->        
        <field key="deleted" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="published" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="pos" dbtype="int" precision="10" phptype="integer" null="false" default="0" />

        <!-- good practice for tracking -->
        <field key="createdon" dbtype="datetime" phptype="datetime" null="true"/>
        <field key="createdby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
        <field key="editedon" dbtype="datetime" phptype="datetime" null="true"/>
        <field key="editedby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
        
        <index alias="parent" name="parent" primary="false" unique="false" type="BTREE">
            <column key="parent" length="" collation="A" null="false" />
        </index>
        <index alias="family" name="family" primary="false" unique="false" type="BTREE">
            <column key="family" length="" collation="A" null="false" />
        </index>
        <index alias="title" name="title" primary="false" unique="false" type="BTREE">
            <column key="title" length="" collation="A" null="false" />
        </index>
        <index alias="date" name="date" primary="false" unique="false" type="BTREE">
            <column key="date" length="" collation="A" null="false" />
        </index>

        <!-- track createdby and editedby -->
        <aggregate alias="CreatedBy" class="modUser" local="createdby" foreign="id" cardinality="one" owner="foreign"/>
        <aggregate alias="EditedBy" class="modUser" local="editedby" foreign="id" cardinality="one" owner="foreign"/>

        <aggregate alias="Family" class="VenueFamily" local="family" foreign="id" cardinality="one" owner="foreign"/>
        <aggregate alias="Parent" class="VenueParent" local="parent" foreign="id" cardinality="one" owner="foreign"/>
        <!-- legacy from mark hamstra schema
        <composite alias="Experiences" class="VenueExperience" local="id" foreign="event" cardinality="many" owner="local"/>
        -->
    </object>

</model>

But get__object_field runs before “migxLoopCollection” is executed (and the package gets added).
How does get__object_field know your custom class?

Maybe just add the addPackage line at the beginning of your code and test if that helps.

@halftrainedharry - the issue looks to be resolved.

Adding the following to the get_events snippet now allows the pdoPage Load More function to work as expected.

// include the venue package - required if calls are made to get__object_field
$base_path = $modx->getOption('core_path') . 'components/venue/';
$modx->addPackage('venue', $base_path . 'model/');

Thanks again for your help and sharing the knowledge.

Chris

So it looks like the get__object_field snippet should be updated to check if the custom class is loaded.

I checked the docs and BobRay’s Understanding addPackage, loadClass and getService article but don’t see any options that specifically help my case.

So I updated get__object_field snippet with two new parameters - &autoload and &package.

&package names the required package and &autoload will load the package if the required Class does not exist.

Had this been done from the outset my original issue would never have existed.

I didn’t test what would happen if get__object_field simply loaded the required Class - I’m unsure if it would throw an error if the Class had already been loaded elsewhere.

<?php
/**
 * &autoload - int, 1/0 to autoload the named class, default 0
 * &package - string, name of the package required to get the requested object field eg. venue
 * &class - string, name of the object class name eg. VenueExperience
 * &where_key - string, key is the column to search
 * &where_value - string, value is the row to retrieve
 * &field - string, name of the field to return the value for
 * 
 * &to_placeholder - string, name of placeholder to set with output value, default: none
 * 
 * TODO:
 * - extend key/value parameters to allow array to be set in snippet call eg. by explode on key1==value1||key2==value2, or loop over all script properties except those named above?
 * - validate we have all required values
 * 
 */
 
/* example use
    [[get__object_field?
        &autoload=`1`
        &package=`venue`
        &class=`VenueExperience` 
        &where_key=`id` 
        &where_value=`[[+rel_experience]]` 
        &field=`title`

        &to_placeholder=`rel_experience`
    ]]
*/

// set autoload to given value else default to 0
$autoload = !empty($autoload) ? $autoload : 0;

// check if the required class already exists and load it if &autoload=1, else log error
if( (!class_exists($class)) && $autoload == 1 ){
    // load named class
    $base_path = $modx->getOption('core_path') . "components/$package/";
    $modx->addPackage($package, $base_path . 'model/');
} elseif ($autoload != 1) {
    // log error because required class does not exist and autoload is not enabled
    $err_msg    = "[get__object_field] Class $class does not exist or autoload not enabled.";
    $err_plugin = 'get__object_field';
    $modx->log(xPDO::LOG_LEVEL_ERROR, $err_msg, '', $err_plugin);
}

// get the object eg. $obj = $modx->getObject('VenueExperience', array('id'=>1));
// NOTE: if no $criteria parameter is specified, no class is found, or an object cannot be located by the supplied criteria, null is returned.
$obj = $modx->getObject($class, array($where_key => $where_value));

// check if valid obj has been retreived
if (!is_null($obj)) {
    $output = $obj->get($field);

    // if 
    if (!empty($to_placeholder)) {
        $modx->setPlaceholder($to_placeholder, $output);
    } else {
        return $output;
    }
}

You can call “addPackage” multiple times. The function checks if the package is already loaded.

Give this a try:

$where_clauses['family] = 2;
$where_clauses['parent] = 6;
$where_clauses['published] = 1;
$where_json = json_encode($where_clauses);

This version works for all conditions in the array as well - thanks Bob.

Glad I could help. :wink:

This topic was automatically closed 2 days after discussion ended and a solution was marked. New replies are no longer allowed. You can open a new topic by clicking the link icon below the original post or solution and selecting “+ New Topic”.