How to build a transport vehicle for a Parent > Child category relationship?

I can not seem to get this working. Any direction would be greatly appreciated. Below is a Snippet that I am using to test out the concept.

Here’s the structure:

  1. Root level category, “Top Category”
    1.a Chunk related to Top Category
  2. Child category, “Second Category”
    2.a Chunk related to Second Category
<?php

$modx->loadClass('transport.modPackageBuilder','',false, true);
$builder = new modPackageBuilder($modx);
$builder->createPackage('test','0.1','alpha');
$builder->registerNamespace('test',false,true,'{core_path}components/test/');

$cat1Attr = [
    xPDOTransport::UNIQUE_KEY => 'category',
    xPDOTransport::PRESERVE_KEYS => false,
    xPDOTransport::UPDATE_OBJECT => true,
    xPDOTransport::RELATED_OBJECTS => true,
    xPDOTransport::RELATED_OBJECT_ATTRIBUTES => [
        'Children' => [
            xPDOTransport::UNIQUE_KEY => 'category',
            xPDOTransport::PRESERVE_KEYS => false,
            xPDOTransport::UPDATE_OBJECT => true,
            xPDOTransport::RELATED_OBJECTS => true,
            xPDOTransport::RELATED_OBJECT_ATTRIBUTES => [
                'Chunks' => [
                    xPDOTransport::UNIQUE_KEY => 'name',
                    xPDOTransport::PRESERVE_KEYS => false,
                    xPDOTransport::UPDATE_OBJECT => true
                ]
            ]
        ],
        'Chunks' => [
            xPDOTransport::UNIQUE_KEY => 'name',
            xPDOTransport::PRESERVE_KEYS => false,
            xPDOTransport::UPDATE_OBJECT => true
        ]
    ]
];

// Setup the first category and chunk
$cat1 = $modx->newObject('modCategory', [
    'category' => 'Top Category'    
]);
$chunk1 = $modx->newObject('modChunk', [
    'name' => 'FirstChunk'    
]);

// Add the chunk to the category
$cat1->addMany($chunk1);


// Setup the second category and chunk
$cat2 = $modx->newObject('modCategory', [
    'category' => 'Child Category'    
]);
$chunk2 = $modx->newObject('modChunk', [
    'name' => 'SecondChunk'    
]);

// Add the chunk to the category
$cat2->addMany($chunk2);

// Add the child category to the parent
$cat1->addMany($cat2);

// Create the vehicle
$vehicle = $builder->createVehicle($cat1, $cat1Attr);
$builder->putVehicle($vehicle);

// Set the attributes
$builder->setPackageAttributes(array(
    'license' => "You can use it",
    'readme' => "Nothing to see here"
));

When I check the resulting vehicle in the packages directory, it has the following inside the modCategory folder:

<?php return array (
  'unique_key' => 'category',
  'preserve_keys' => false,
  'update_object' => true,
  'related_objects' => 
  array (
    'Chunks' => 
    array (
      '675d3eba53ee00d9cbc9eb7ed826eb8b' => 
      array (
        'unique_key' => 'name',
        'preserve_keys' => false,
        'update_object' => true,
        'class' => 'modChunk',
        'object' => '{"id":null,"source":0,"property_preprocess":0,"name":"FirstChunk","description":"Chunk","editor_type":0,"category":0,"cache_type":0,"snippet":null,"locked":0,"properties":null,"static":0,"static_file":"","content":null}',
        'guid' => 'ad0aaa5c8aaf60e197cae079e3230e1a',
        'native_key' => NULL,
        'signature' => 'f11127b591a9b7d0896edcc0390f3918',
      ),
    ),
  ),
  'related_object_attributes' => 
  array (
    'Children' => 
    array (
      'unique_key' => 'category',
      'preserve_keys' => false,
      'update_object' => true,
      'related_objects' => true,
      'related_object_attributes' => 
      array (
        'Chunks' => 
        array (
          'unique_key' => 'name',
          'preserve_keys' => false,
          'update_object' => true,
        ),
      ),
    ),
    'Chunks' => 
    array (
      'unique_key' => 'name',
      'preserve_keys' => false,
      'update_object' => true,
    ),
  ),
  'namespace' => 'test',
  'resolve' => NULL,
  'validate' => NULL,
  'vehicle_package' => 'transport',
  'vehicle_class' => 'xPDOObjectVehicle',
  'guid' => 'bf901ab5dd6885b236f37c2894e69b78',
  'package' => 'modx',
  'class' => 'modCategory',
  'signature' => '71443f211b25e490a91fb3b5626e6e0e',
  'native_key' => NULL,
  'object' => '{"id":null,"parent":0,"category":"Top Category","rank":0}',
);

Only the chunk directly related to the top level category is packaged in. I saw reference to Category Closures. Do I need to somehow package those in as well?

If I try and create a vehicle for each category, the files are created and chunks are referenced, but it doesn’t install correctly since the relationship is missing between categories.

Do most/all Extras just have a single category?

Actually I just found a few discussions from the old forums. I am guessing this is still not possible even though this was 10 years ago?

Anyway… in case people find it on the new forums, responses were:

splittingred: At this time, to my knowledge, any levels more than 2 deep for RELATED_OBJECTS wont work. OpenGeek can give you more information on that.

bobray: …TBH, when I have trouble, I usually end up doing it in a Script resolver where I know it will work. There, you can create the category and get its ID before adding the children to it (or just set their ’parent’ fields as you create them).

I’m building with GPM now and use my migxGpmHelper
https://github.com/Bruno17/migxGpmHelper to collect categories and elements into the config.json
this is the processor, which does that stuff
https://github.com/Bruno17/migxGpmHelper/blob/3abde5971471ac51a2d1a0de9faac3f719ab57e3/core/components/migxgpmhelper/processors/mgr/default/writetofiles.php

I have this PR included in my GPM, so I can work with elements in the db and GPM doesn’t touch my elements when activated this settings.

Thanks Bruno! I’ll take a look later today when I have a chance.

It’s very easy to do it in a resolver if you know the name of the category and the name of the parent category you want:

$parentCategoryName = 'someCategory';
$childCategoryName = 'someSubCategory';

$parentObject = $modx->getObject('modCategory', array('category' => $parentCategoryName);
$childObject = $modx->getObject('modCategory', array('category' => $childCategoryName);

$childObject->set('parent', $parentObject->get('id'))
$childObject->save();

BTW, I suspect that the original problem is caused by the fact that the modCategory object has no name field and uses a field called category to hold the name of the category.

I should also mention that when MyComponent creates a project for you, it automatically creates a generic resolver you can use for stuff like this.

but then you will need to know all subCategories and you will need to resolve the category - ids of all the elements somehow

Thanks for the contribution here. I think things like this will be helpful so that the new community site has more current and relevant discussion with examples.

Definitely benefits and challenges with both approaches. I think this is a good discussion for Extra developers to find though.

To summarize my understanding:

  1. The current MODX Transport system does NOT handle child categories when creating a transport package.
  2. One way to deal with it is a custom and dynamic build process. In your case based on GPM.
    a. Pro: Dynamic, handles categories and sub categories.
    b. Con: You have to learn GPM, and use your PR.
  3. Another way is a scripted resolver per Bobray’s method
    a. Pro: Simpler for someone to grasp on a small scale
    b. Con: Not as scalable

I think if you went the resolver script route, if you have a small number of categories, you could probably create a little hash map to show the parent child relationships. Then just loop through and set the parent on each. You’d have to store the category name since they would likely have different IDs when installed.

Anyway. Always multiple ways to do things. So, thanks for both suggestions and feedback. Very helpful.

1 Like

another good option is to use teleport with the elements template.
I didn’t try it, yet, but I think, this could do what you need, too.

This is from the example config file for MyComponent:

'categories' => array(
        'Example' => array(
            'category' => 'Example',
            'parent' => '',  /* top level category */
        ),
        'category2' => array(
            'category' => 'Category2',
            'parent' => 'Example', /* nested under Example */
        )
    ),

You can add as many as you want and they can be nested as far as you want without adding another level to the array as long as the parent always appears in the list before the child. The resolver will be created automatically.

The downside to MyComponent is the effort it takes to get used to using it. It’s too bad the video from the first ModExpo I did on MyComponent was lost. I created a new transport package from scratch complete with snippets, chunks, plugins, resources, and lexicon strings to internationalize the output. It took about 20 minutes. Basically, you just take the “Example” config file, delete the stuff you don’t need, and modify the stuff you do need.

In your code, to ensure proper packaging of nested categories and chunks, consider using the addOne method for adding chunks to categories. Additionally, explore Category Closures for establishing relationships between categories during packaging. This approach might address your issue effectively, potentially benefiting moving-services.