We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 36834
    • 25 Posts
    I have a recurring issue that I would really appreciate some help with. When developing in MODx, I frequently find myself using the getChunk method to process a chunk in a loop. I like the theory - I can separate my view files and store them as chunks etc. etc.

    The problem that I am having is that the getChunk method appears to be really slow. Perhaps I'm using it incorrectly? Perhaps I'm missing something?

    Here's the latest example. I have written an extra that displays properties on a Google Map. Each property has an info window that contains a summary of the property details. At the moment there are 38 properties in the database. When the map page loads I retrieve the properties via Ajax using a front end processor that calls getChunk (38 times) on a template chunk for the info window.

    If I use the getChunk method to create the info windows the map takes 5 seconds or more to render. If I use HTML and variables in my code (not ideal) then the page takes 918 ms to render. These results are in a production environment. On my local dev server the results are different but getChunk still takes twice as long to process.

    I would really like to know how to keep my templates separate from my code and still use them efficiently. If you would like to see the site in question, then please PM me.

    Thanks in advance.

    Chunk used:
    <div class="infobox-property-container">
    	<div class="infobox-property infobox-property[[+transaction_id:eq=`1`:then=`sales`:else=`lettings`]] clear-fix">
    	  <div class="infobox-image"><img src="[[+image_url:phpthumbof=`f=jpg&w=116&h=87&zc=1`]]" width="116" height="87">
    		<p class="infobox-more-details"><a href="[[+property_url]]">More Details</a></p>
    	  </div>
    	  <div class="infobox-text">
    		<p class="infobox-address">[[+display_address]]</p>
    		<p class="infobox-price">[[+price_qualifier]] [[+price:notempty=`£[[+price]]`]] [[+transaction_id:eq=`2`:then=`PCM`]]</p>
      		[[+status:notempty=`<p class="status">[[+status]]</p>`]]
    		<p class="infobox-bedrooms">[[+bedrooms:gt=`0`:then=`[[+bedrooms]] bedroom`]] [[+sub_type]] [[+transaction_id:eq=`1`:then=`for sale`:else=`to let`]]</p>
    	  </div>
    	</div>
    </div>


    Hard coded alternative:
    $content = '<div class="infobox-property-container">
    	<div class="infobox-property clear-fix">
    	  <div class="infobox-image"><img src="' . $placeholders['image_url'] . '" width="116" height="87">
    		<p class="infobox-more-details"><a href="' . $placeholders['property_url'] . '">More Details</a></p>
    	  </div>
    	  <div class="infobox-text">
    		<p class="infobox-address">' . $placeholders['display_address'] . '</p>
    		<p class="infobox-price">' . $price_text . '</p>' .
      		$placeholders['status'] . 
    		'<p class="infobox-bedrooms">' .$bedrooms_text . '</p>
    	  </div>
    	</div>
    </div>';


    Production Server:
    GenuineIntel, Intel(R) Xeon(R)CPU E5310 @ 1.60GHz
    Linux 2.6.18-028stab094.3
    Apache 20051115
    PHP 5.2.17
    MySql 5.0.77
    MODx 2.1.5-pl traditional

    Dev Server:
    Mac OSX 10.7.2
    Apache 2.2.21 (Unix)
    PHP 5.3.9-ZS5.6.0
    MySql 5.1.54
    MODx 2.1.5-pl traditional
      • 4172
      • 5,888 Posts
      First: try to cache the parsed results, perhaps with the getCache-snippet, if calling your snippet in cached mode isn't possible for some reasons.

      Then outputfilters can slow the parsing. Try to use them only where really needed. Where possible do the logic in your snippet and set placeholders.

      When using something like that:
      [[+transaction_id:eq=`2`:then=`PCM`]]


      try, if this is faster:

      [[+transaction_id:eq=`2`:then=`PCM`:else=``]]


      or if the IF-snippet is faster, than output-filters.
        -------------------------------

        you can buy me a beer, if you like MIGX

        http://webcmsolutions.de/migx.html

        Thanks!
      • @simonp98,

        how did you use the getChunk API?
          Rico
          Genius is one percent inspiration and ninety-nine percent perspiration. Thomas A. Edison
          MODx is great, but knowing how to use it well makes it perfect!

          www.virtudraft.com

          Security, security, security! | Indonesian MODx Forum | MODx Revo's cheatsheets | MODx Evo's cheatsheets

          Author of Easy 2 Gallery 1.4.x, PHPTidy, spieFeed, FileDownload R, Upload To Users CMP, Inherit Template TV, LexRating, ExerPlan, Lingua, virtuNewsletter, Grid Class Key, SmartTag, prevNext

          Maintainter/contributor of Babel

          Because it's hard to follow all topics on the forum, PING ME ON TWITTER @_goldsky if you need my help.
          • 3749
          • 24,544 Posts
          As Bruno17 says, the conditional modifiers are very slow, while getChunk itself is very fast.

          So instead of this:

          <div class="infobox-property infobox-property[[+transaction_id:eq=`1`:then=`sales`:else=`lettings`]] clear-fix">



          Do this in a custom snippet:


          <div class="infobox-property infobox-property[[+transaction_id]] clear-fix">



          <?php
          $fields['transaction_id'] = $transactionId == 1 ? 'sales' : 'lettings';
          return $modx->getChunk('ChunkName', $fields);


            Did I help you? Buy me a beer
            Get my Book: MODX:The Official Guide
            MODX info for everyone: http://bobsguides.com/modx.html
            My MODX Extras
            Bob's Guides is now hosted at A2 MODX Hosting
            • 36834
            • 25 Posts
            Thank you for your replies. Following Bruno17's advice I have stripped out all the conditional modifiers from the chunk and replaced them with logic and lexicon strings. This has resulted in speed improvement. But there is still huge difference between using getChunk and straight html in logic.

            getChunk takes 2.5 to 3 seconds whereas the html and variables takes 890 milliseconds.

            @Bruno17 - I'm not sure what I should be caching. The snippet I am using is called via ajax and returns JSON to a javascript function to plot the markers on a map. Should I be caching the info window HTML? Any clues would be appreciated.

            @goldsky - I have a custom class using the following getChunk method. I have also included the processor code below this (it includes both ways of creating the html for the info window, one is commented out).

            @BobRay - thanks. I have moved the logic to the code below.

            public function getChunk($name, $properties = array()) {
            		$chunk = null;
            		if (!isset($this->chunks[$name])) {
            			$chunk = $this->_getTplChunk($name);
            			if (empty($chunk)) {
            				$chunk = $this->modx->getObject('modChunk',array('name' => $name));
            				if ($chunk == false) return false;
            			}
            			$this->chunks[$name] = $chunk->getContent();
            		} else {
            			$o = $this->chunks[$name];
            			$chunk = $this->modx->newObject('modChunk');
            			$chunk->setContent($o);
            		}
            		$chunk->setCacheable(false);
            		return $chunk->process($properties);
            	}
             
            	private function _getTplChunk($name, $postfix = '.chunk.tpl') {
            		$chunk = false;
            		$f = $this->config['chunks_path'].strtolower($name).$postfix;
            		if (file_exists($f)) {
            			$o = file_get_contents($f);
            			$chunk = $this->modx->newObject('modChunk');
            			$chunk->set('name',$name);
            			$chunk->setContent($o);
            		}
            		return $chunk;
            	}


            <?php
            require_once $modx->getOption('propertymanager.core_path',null,$modx->getOption('core_path').'components/propertymanager/').'model/propertymanager/propertymanager.class.php';
            require_once $modx->getOption('googlemap.core_path',null,$modx->getOption('core_path').'components/googlemap/').'model/googlemap/googlemap.class.php';
            
            $transaction_id = NULL;
            $parent_id = NULL;
            if (isset($_GET['transaction_id'])) {
            	$transaction_id = filter_input(INPUT_GET, 'transaction_id', FILTER_SANITIZE_STRING);
            	$parent_id = filter_input(INPUT_GET, 'parent_id', FILTER_SANITIZE_STRING);
            }
            
            $property_manager = new PropertyManager($modx, $transaction_id);
            $criteria = $property_manager->getCriteria();
            $properties = $modx->getIterator('pmProperty', $criteria);
            
            $map = new GoogleMap($modx);
            $map->map_options->setScrollwheel(FALSE);
            $map->map_options->setCenter(53.9609795,-2.0119441);
            if ($area = $property_manager->getArea()) {
            	$map->map_options->setCenter($area['latitude'], $area['longitude']);
            	$map->map_options->setZoom(10);
            }
            
            $map->setLexicon($modx->lexicon->fetch('pm.',false));
            
            foreach ($properties as $property) {
            	$content = NULL;
            	$placeholders = $property->toArray();
            	$placeholders['sub_type'] = strtolower($property->getOne('SubType')->get('name'));	
            	// set price text
            	$placeholders['price_qualifier'] = ($placeholders['price_qualifier_id'] > 0) ? $property->getOne('PriceQualifier')->get('name') : NULL;
            	$placeholders['price'] =  number_format($placeholders['price'], 0, '.', ',');
            	$placeholders['rent_frequency'] = ($transaction_id == 2) ? $modx->lexicon('pm.pcm') : NULL;
            	if ($placeholders['price_qualifier_id'] == 1) {
            		$placeholders['price_text'] = $placeholders['price_qualifier'];
            	} else {
            		$placeholders['price_text'] = sprintf($modx->lexicon('pm.price_text'), $placeholders['price_qualifier'], $placeholders['price'], $placeholders['rent_frequency']);
            	}
            	// set status text
            	if ($placeholders['status_id'] > 0) {
            		$placeholders['status'] = sprintf($modx->lexicon('pm.status_text'), $property->getOne('Status')->get('name'));
            	}
            	// set bedroom text
            	if ($placeholders['bedrooms'] > 0) {
            		$lexicon_string = ($transaction_id == 1) ? $modx->lexicon('pm.description_text_sales'): $modx->lexicon('pm.description_text_lettings');
            		$placeholders['description_text'] =  sprintf($lexicon_string, $placeholders['bedrooms'], $placeholders['sub_type']);
            	} else {
            		$lexicon_string = ($transaction_id == 1) ? $modx->lexicon('pm.description_text_sales_no_beds'): $modx->lexicon('pm.description_text_lettings_no_beds');
            		$placeholders['description_text'] =  sprintf($lexicon_string, ucfirst($placeholders['sub_type']));
            	}
            	
            	$placeholders['property_url'] = $modx->makeUrl($parent_id) . 'property-' .  $placeholders['id'] . '.html';
            	
            	if ($image_array = $property_manager->getMainImage($placeholders['id'])) {
            		$placeholders['image_url'] = $image_array['url'];
            		$placeholders['images_title'] = $placeholders['display_address'];
            	} else {
            		$placeholders['image_url'] = $property_manager->config['assets_url'] . 'images/awaiting-image.jpg';
            		$placeholders['images_title'] = NULL;
            	}
            	$content = $property_manager->getChunk('infobox', $placeholders);
            	
            /*	
            	if ($image_array = $property_manager->getMainImage($placeholders['id'])) {
            		$placeholders['image_url'] = $property_manager->config['phpthumb'] . '?src=' . $property_manager->config['base_url'] . $image_array['url'] . '&f=jpg&w=116&h=87&zc=1';
            		$placeholders['images_title'] = $placeholders['display_address'];
            	} else {
            		$placeholders['image_url'] = $property_manager->config['phpthumb'] . '?src=' . $property_manager->config['assets_url'] . 'images/awaiting-image.jpg' . '&f=jpg&w=116&h=87&zc=1';
            		$placeholders['images_title'] = NULL;
            	}
            	
            	
            	$content = '<div class="infobox-property-container">
            	<div class="infobox-property clear-fix">
            	  <div class="infobox-image"><img src="' . $placeholders['image_url'] . '" width="116" height="87">
            		<p class="infobox-more-details"><a href="' . $placeholders['property_url'] . '">More Details</a></p>
            	  </div>
            	  <div class="infobox-text">
            		<p class="infobox-address">' . $placeholders['display_address'] . '</p>
            		<p class="infobox-price">' . $placeholders['price_text'] . '</p>' .
              		$placeholders['status'] . 
            		'<p class="infobox-bedrooms">' . $placeholders['description_text'] . '</p>
            	  </div>
            	</div>
            </div>';
            */
            	$marker = new GoogleMapMarkerOptions();
            	$marker->setPosition($placeholders['latitude'],$placeholders['longitude']);
            	$marker->setTitle($placeholders['display_address']);
            	$marker->setContent($content);
            	$marker->setIcon('icon');
            	$marker->setShadow('shadow');
            	$map->setMarker($marker);
            }
            
            $image = new GoogleMapMarkerImage();
            $image->setUrl($property_manager->config['assets_url'] . 'icons/marker.png');
            $image->setSize(21, 24);
            $image->setOrigin(0, 0);
            $image->setAnchor(10, 24);
            
            $image_2 = new GoogleMapMarkerImage();
            $image_2->setUrl($property_manager->config['assets_url'] . 'icons/marker.png');
            $image_2->setSize(29, 24);
            $image_2->setOrigin(23, 0);
            $image_2->setAnchor(10, 24);
            
            $map->setMarkerImage('icon', $image);
            $map->setMarkerImage('shadow', $image_2);
            
            $clusterer_options = new GoogleMapMarkerClustererOptions($modx);
            $clusterer_options->setGridSize(10);
            $clusterer_options->setMaxZoom(14);
            $clusterer_options->setAverageCenter(TRUE);
            $clusterer_options->setZoomOnClick(FALSE);
            $clusterer_options->setMinimumClusterSize(2);
            $clusterer_options->setTitle('Properties');
            
            $style = new GoogleMapMarkerClustererIconStyle($modx);
            $style->setUrl($property_manager->config['assets_url'] . 'icons/clustermarker.png');
            $style->setWidth(21);
            $style->setHeight(24);
            $style->setAnchor(0, 0);
            $style->setAnchorIcon(24, 10);
            $style->setBackgroundPosition('0 0');
            $style->setTextSize(1);
            
            $map->setClustererOptions($clusterer_options);
            $map->setClustererIconStyle($style);
            
            $info_box = new GoogleMapInfoBox();
            $info_box->setAlignBottom(TRUE);
            $info_box->setBoxClass('infobox');
            $info_box->setCloseBoxMargin('5px 5px 5px 0');
            //$info_box->setInfoBoxClearance(3,3);
            //$info_box->setPixelOffset(10,10);
            
            $map->setInfoBox($info_box);
            
            return json_encode($map);
            
              • 4172
              • 5,888 Posts
              If the resulting json-string is allways the same you can just try to call your ajax-snippet cached.
              [[yoursnippet]]


              If thats not possible, because of different results, depending of REQUEST than you can wrap your snippet by getCache:

              https://github.com/opengeek/getCache/wiki/Properties
                -------------------------------

                you can buy me a beer, if you like MIGX

                http://webcmsolutions.de/migx.html

                Thanks!
                • 3749
                • 24,544 Posts
                You can often speed things up by putting placeholders in the chunk and replacing them with str_replace rather than having MODX parse the whole chunk.

                This is a very handy function for doing the replacement using an associative array:

                <?php
                public function strReplaceAssoc(array $replace, $subject) {
                   return str_replace(array_keys($replace), array_values($replace), $subject);
                } 
                
                $placeholders['[[+placeholder1]]']] = 'Something';
                $placeholders['[[+placeholder2]]']] = 'Something else';
                
                $chunk = $modx->getChunk('MyChunk');
                
                return strReplaceAssoc($placeholders, $chunk);
                


                ---------------------------------------------------------------------------------------------------------------
                PLEASE, PLEASE specify the version of MODX you are using . . . PLEASE!
                MODx info for everyone: http://bobsguides.com/modx.html
                  Did I help you? Buy me a beer
                  Get my Book: MODX:The Official Guide
                  MODX info for everyone: http://bobsguides.com/modx.html
                  My MODX Extras
                  Bob's Guides is now hosted at A2 MODX Hosting
                  • 36834
                  • 25 Posts
                  Thanks BobRay your method has resulted in a speed increase from 3 down to approximately 1 second for the ajax request to load the page.

                  @Bruno17 - The json-string is the result of a property search that is plotted on google maps and is very unlikely to be the same. The search results are dependent upon many factors like area, bedrooms, price etc. so I'm not sure that caching is the way to go. When not in map view I use a traditional paged recordset listing of 10 properties, so the performance hit is not so noticable. Would it be practical to cache in this situation?

                  I am sure that where caching techniques can be used, that the performance of getChunk (and input/output filters is not an issue, but after much testing I do think that getChunk, when called repetitively uncached is quite an expensive process.

                  So far I have achieved the best results by moving all logic into my code and not using output filters and using BobRays method above (get the chunk once and then str_replace).

                  Is this the best way to avoid the overhead of getChunk when caching isn't an option? Or do I need to understand and implement caching better?

                  One other technique that I've been experimenting with is using snippets with external php based templates. I'd be interested to hear if anyone thinks this a a good or bad idea. I use a function to load an external template file and pass the 'placeholder' variables to it. Here's a an illustration of how it works.

                  I'd probably best mention that I am not using this is the context of MODx's resources - I am using it to deliver custom content types from custom tables.

                  template:
                  <h1><?php echo $heading ?></h1>
                  <p><?php echo $text ?></p>
                  	


                  snippet:
                  $vars['heading'] = 'heading here';
                  $vars['text'] = 'text here';
                  $string = get_include_contents('test.chunk.tpl', $var);
                  
                  return $string;


                  function:
                  function get_include_contents($filename, $vars) {
                      if (is_file($filename)) {
                          extract($vars);
                          ob_start();
                          include $filename;
                          return ob_get_clean();
                      }
                      return false;
                  }
                  


                  [ed. note: simonp98 last edited this post 12 years, 2 months ago.]
                    • 36834
                    • 25 Posts
                    I ended up using the modx cache manager to cache search result pages and individual elements from the results list along with moving filter logic to php. Thanks for all your help. No speed issues now.
                      • 34004
                      • 125 Posts
                      Further to this, I have a similar issue and I'm also finding getChunk very slow. I have a function from http://codingrecipes.com/finding-and-fixing-bottlenecks-slow-parts-in-your-php-code to time how long each loop is taking for getchunk (I have a table that is 220 rows long but am limiting to 100 for testing)

                      I have to run position, secs to hms and sorted functions 15 times, but getchunk 100 times - I'm seeing page load speeds of 45 seconds so I know I need to do some major optimisations!

                      position 0.00403785705566
                      secs to hms 0.00405311584473
                      sorted 0.000234842300415
                      position 0.00423884391785
                      secs to hms 0.00422692298889
                      sorted 0.000258922576904
                      position 0.0039918422699
                      secs to hms 0.00408315658569
                      getchunk: 0.0941989421844
                      getchunk: 0.0966041088104
                      getchunk: 0.0950701236725
                      getchunk: 0.096314907074
                      getchunk: 0.0952978134155
                      getchunk: 0.136080980301
                      getchunk: 0.156613111496
                      getchunk: 0.165791988373
                      getchunk: 0.114756822586

                      so the getchunk loop is quite slow. I'm going to try your suggestion Bobray, but simonp could you please elaborate on your cache manager solution? Maybe there's some clues there...