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).
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 foreach
s, 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;
}
?>
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.
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.