Skip to content

Instantly share code, notes, and snippets.

@DavidOliver
Last active March 29, 2019 09:02
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save DavidOliver/3083387 to your computer and use it in GitHub Desktop.
Save DavidOliver/3083387 to your computer and use it in GitHub Desktop.
Notes on getting data from within Symphony CMS

Written based on hacking away at a custom event which aimed to take its data solely from entry data and $_SESSION values, and not posted values (which I understand may have been open to DOM hacking and required "ugly" frontend hidden fields).

Things I think I've learned

Use Xpath in PHP when possible

I was traversing the XMLElement object generated by SymQL (or Entry object generated by EntryManager) with the object's methods and foreach to get values, which seems to require a lot of effort and code:

<?php
foreach($instances_entries->getChildrenByName('entry') as $instance_entry) {
	if ($instance_entry->getAttribute('id') == $instance_id) {
		$instance_fields = $instance_entry->getChildren();
		foreach($instance_fields as $instance_field) {
			switch ($instance_field->getName()) {
				case "code": $instance_code = $instance_field->getValue(); break;
				case "name": $instance_name = $instance_field->getValue(); break;
			}
		}
	}
}
?>

Generating a PHP DOMDocument and navigating it with Xpath is a lot easier and concise (only two lines required to do the actual getting of data):

<?php
$instances_xml = new DOMDocument();
$instances_xml->loadXML($instances_entries->generate());
$instances_xpath = new DOMXPath($instances_xml);

$instance_code = $instances_xpath->evaluate("string(/symql/entry[@id='$instance_id']/code)");
$instance_name = $instances_xpath->evaluate("string(/symql/entry[@id='$instance_id']/name)");
?>

Traversing the product XMLElement objects was even worse than the instance XMLElement objects above:

<?php
foreach($products->getChildrenByName('entry') as $product_entry) {
	foreach($product_entry->getChildrenByName('instances') as $product_instances) {
		foreach($product_instances->getChildrenByName('item') as $product_instance) {
			if ($product_instance->getAttribute('id') == $instance_id) {
				foreach($product_entry->getChildrenByName('name') as $product_entry_name) {
					$product_name = $product_entry_name->getValue();
				}
				foreach($product_entry->getChildrenByName('brand') as $product_entry_brand) {
					foreach($product_entry_brand->getChildrenByName('item') as $product_entry_brand_item) {
						$product_brand = $product_entry_brand_item->getValue();
					}
				}
			}
		}
	}
}
?>

I haven't switched to DOMDocument entirely for the products yet as I can't use SymQL due to a WHERE statement-related bug, but I have improved it by reducing the foreachs, using in_array_r (see below) and switching to Xpath for getting the brand:

<?php
foreach($products as $product_entry) {
	$product_entry_data = $product_entry->getData();
	if (in_array_r($instance_id, $product_entry_data, false)) {
		$product_name = $product_entry_data['1']['value'];
		$product_brand_id = $product_entry_data['9']['relation_id'];
		$product_brand = $brands_xpath->evaluate("string(/symql/entry[@id='$product_brand_id']/name)");
	}
}
?>

in_array_r is a function I found on StackOverflow which, unlike PHP's own in_array function, searches through multi-dimensional arrays which is useful for these sorts of objects when Xpath can't be used:

<?php
function in_array_r($needle, $haystack, $strict = true) {
	foreach ($haystack as $item) {
		if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && in_array_r($needle, $item, $strict))) {
			return true;
		}
	}
	return false;
}
?>

EntryManager is (relatively) hard to use

I couldn't use SymQL for my products query because it either chokes on multiple WHERE values and results in an SQL error or, very strangely, returns the wrong entries (at least it does based my interpretation of my print_r debugging). Github issue. I commented out the SymQL method in my event so that it can still be read or used if this issue is fixed.

So I used EntryManager instead:

<?php
	$keys = array();
	foreach ($_SESSION['sym-cart'] as $key => $value) {
		$keys[] = $key;
	}
	$product_em = new EntryManager();
	$product_fields = array ('name','brand','instances');
	$products = $product_em->fetch(
		false,
		1,
		false,
		false,
		"AND sym_entries_data_137.relation_id IN (" . implode(', ', $keys) . ") AND sym_entries_data_137.entry_id = e.id",
		"JOIN sym_entries_data_137",
		false,
		true,
		$product_fields,
		false,
		true
	);
?>

The resultant object can be converted to an XMLElement (allowing for a DOMDocument object to be created so that Xpath can be used), but I haven't attempted incorporating Nick's code yet.

Questions for future

Can data sources be used instead?

Is there a cleaner/better way of loading data in events, such as generating data by using already-existing Symphony data sources (feeding them filter values if necessary)? I looked at doing this from within my event by including Symphony's data source includes, but quickly gave up. Both data sources and events use/generate $result, and I don't know if there are other issues in using data sources to return XML from an event due to Symphony's structure and execution order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment