简体   繁体   中英

Recursive traversal of nested objects and extract some properties

I'm struggling with the following situation:

I have an object of type Category with several private properties I want to extract into a separate data structure / array through its available getters: getId() , getTitle() , getLink() ,. It stores any nested subcategories as a property as well, which can be accessed through getter getSubCats() which will return an associative array of Categories with the corresponding ID's as keys.

I need to extract some properties and create a nested array (for use with json_encode) which fits the following structure:

<ID> => array(
  'title'    => <title>,
  'url'      => <url>,
  'parent'   => <parentID>,
  'children' => array(
    <ID> => array(
      'title'    => <title>,
      'url'      => <url>,
      'parent'   => <parentID>,
      'children' => array(
        ...
    ),
    <ID> => array(
      'title'    => <title>,
      'url'      => <url>,
      'parent'   => <parentID>,
      'children' => array(
      ...
    ),
    ...
  )
)

Maybe this can be done by using RecursiveIteratorIterator? Below are my last two approaches:

function buildTree_v1(&$oCat, &$aOutput = [], &$oParent = null) {
  $actId = $oCat->getId();
  $aOutput [$actId]['title'] = $oCat->getTitle();
  $aOutput [$actId]['url'] = $oCat->getLink();
  $aOutput [$actId]['parent'] = $oCat->getParentCategory()->getId() ?? null;
  if ( $oCat->getHasVisibleSubCats() ) {
    foreach ( $oCat->getSubCats() as $sId => &$oSubCat ) {
      $aOutput [$actId][$sId] = [];
      foreach ( $oSubCat as &$oSubChild ) {
        buildTree_v1($oCat, $aOutput, $oSubChild);
      }
    }
  }
}


// Try to split - only build the nested object structure as first step
function buildTree_v2(&$oCat) {
  $return = [];
  if ( $oCat->getHasVisibleSubCats() ) {
    foreach ( $oCat->getSubCats() as &$oSubCat) {
      if ( $oSubCat->getHasVisibleSubCats() ) {
        $return[$oSubCat->getId()] = buildTree_v2($oSubCat);
      } else {
        $return[] = $oSubCat->getId();
      }
    }
  }
  return empty($return) ? null : $return;
}


Thanks in advance for any advice!

//Edit:

Input structure (relevant parts):

class Category {
  protected $_aSubCats = array();
  protected $_blHasVisibleSubCats;
  protected $_aSeoUrls = array();
  protected $_oParent = null;
  protected $_sId;
  protected $_sTitle;

  public function getSubCats(): array<Category>;
  public function getHasVisibleSubCats(): bool;
  public function getLink($iLang = null): string;
  public function getParentCategory(): ?Category;
  public function getId(): string;
  public function getTitle(): string;

}

An concrete example input object might look like:

$oInputCat = {Category}
-> $_aSubCats = array(
     '316' => {Category},
     '23'  => {Category},
     '262' => {Category}
   )
-> $_blHasVisibleSubCats = true
-> $_aSeoUrl = 'https://example.com/Hardware'
-> $_oParent = {Category}
-> $_sId = '5068'
-> $_sTitle = 'Hardware'


/* Entries of $_aSubCats */

// '316' = {Category}
-> $_aSubCats = array()
-> $_blHasVisibleSubCats = false
-> $_aSeoUrl = 'https://example.com/Hardware/3D-Googles'
-> $_oParent = {Category}
-> $_sId = '316'
-> $_sTitle = '3D Googles'

// '23' = {Category}
-> $_aSubCats = array(
     '26'  => {Category}
   )
-> $_blHasVisibleSubCats = true
-> $_aSeoUrl = 'https://example.com/Hardware/CPUs-and-Cooler'
-> $_oParent = {Category}
-> $_sId = '23'
-> $_sTitle = 'CPUs & Cooler'

// '262' = {Category}
-> $_aSubCats = array()
-> $_blHasVisibleSubCats = false
-> $_aSeoUrl = 'https://example.com/Hardware/Sound-Cards'
-> $_oParent = {Category}
-> $_sId = '262'
-> $_sTitle = 'Sound Cards'

// '26' = {Category}  <-- example for a 2nd level subcategory
-> $_aSubCats = array()
-> $_blHasVisibleSubCats = false
-> $_aSeoUrl = 'https://example.com/Hardware/CPUs-and-Cooler/Cooler'
-> $_oParent = {Category}
-> $_sId = '26'
-> $_sTitle = 'Cooler'

The desired result would look like:

array(
  '5068' => array(
    'title'    => 'Hardware',
    'url'      => 'https://example.com/Hardware',
    'parent'   => '12',  // not listed - parent of the input category
    'children' => array(
      '316' => array(
        'title'    => '3D Googles',
        'url'      => 'https://example.com/Hardware/3D-Googles',
        'parent'   => '5068',
        'children' => array(),
      '23' => array(
        'title'    => 'CPUs & Cooler',
        'url'      => 'https://example.com/Hardware/CPUs-and-Cooler',
        'parent'   => '5068',
        'children' => array(
          '26' => array(
          'title'    => 'Cooler',
          'url'      => 'https://example.com/Hardware/CPUs-and-Cooler/Cooler',
          'parent'   => '23',
          'children' => array(),
        ),
      '262' => array(
        'title'    => 'Sound Cards',
        'url'      => 'https://example.com/Hardware/Sound-Cards',
        'parent'   => '5068',
        'children' => array(),
    )
  )
)

The real object tree has an arbitrary number of branches and dimensions, but I think this should be enough to understand, what I want to do.

As you can see, the desired target could be described as "convert the objects to arrays while filtering some specific properties". I hope this will make things easier to understand.

Finally, I was able to solve this by myself.

Only for the archive, the following code will do the task:

/**
 * Extracts all relevant properties of the given base category and all of its subcategories recursively
 * and assigns this data structure to the given template variable.
 *
 * @param Category $oCat    [mandatory] Target base category
 * @param array    $aOutput [optional]  Extraction storage
 * @param ?object  $oParent [internal]  Latest parent
 * @param bool     $return  [internal]  Return indicator
 *
 * @return array|void Derived category tree structure
 */
function buildTree($oCat, &$aOutput = [], $oParent = null, $return = true) {
  // scalar properties
  $aOutput[$actId]['id'] = $actId = $oCat->getId();
  $aOutput[$actId]['title'] = $oCat->getTitle();
  $aOutput[$actId]['url'] = $oCat->getLink();
  // parent property (object)
  if ( $oParent !== null ) {
    $aOutput[$actId]['parent'] = $oParent->getId();
  } elseif ( $parent = $oCat->getParentCategory() ) {
    $aOutput[$actId]['parent'] = $parent->getId();
  } else {
    $aOutput[$actId]['parent'] = ':globalroot'
  }
  // subcats property ([object])
  if ( $oCat->getHasVisibleSubCats() ) {
    foreach ( $oCat->getSubCats() as $sId => $oSubCat ) {
      $aOutput[$actId]['children'][$sId] = [
        'id'       => $sId,
        'title'    => $oSubCat->getTitle(),
        'url'      => $oSubCat->getLink(),
        'parent'   => $actId,
        'children' => null
      ];
      if ( $oSubCat->getHasVisibleSubCats() ) {
        foreach ( $oSubCat->getSubCats() as $oSubChild ) {
          buildTree($oSubChild, $aOutput[$actId]['children'][$sId]['children'], $oSubCat, false);
        }
      }
    }
  } else {
    $aOutput[$actId]['children'] = null;
  }
  if ( $return ) {
    return $aOutput;
  }
}

Usage:

$aTree = buildTree($oBaseCat);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM