How can I return data from an external source as a DocumentSet?
I set up a custom data source to interface with Amazon's Product Advertising API. To do this, I subclassed lithium\data\source\Http and redefined the read method to suit my needs as described in the documentation (http://li3.me/docs/manual/working-with-data/creating-data-sources.wiki).
However, my lithium version (0.11, last release) does not seem to have a cast method like in the example and if I create one it won't get called when I do return $this->item($model, $data, $options).
So, I made a custom item function to create the Documents by calling parent::item just like the documentation example does for cast.
Then, after the recursive calls, I end up with an array of Document objects and the final call to parent::item then gives me an empty DocumentSet object.
How should I pass the data on to create a proper DocumentSet?
Here's a minimal example of my code:
// Within class Amazon extends \lithium\data\source\Http
protected function _init() {
// Define entity classes.
$this->_classes += array(
'entity' => 'lithium\data\entity\Document',
'set' => 'lithium\data\collection\DocumentSet'
);
parent::_init();
}
public function read($query, array $options = array()) {
// Extract from query object.
$parameters = $query->export($this, array('keys' => array('conditions')));
$conditions = $parameters['conditions'];
// Code stripped to validate conditions and prepare Amazon request (that part works).
// results in a $queryString variable.
// Get response from Server.
$xml = simplexml_load_string($this->connection->get($this->_config['basePath'], $queryString));
// Stripped response validation and reformatting -> $items contains an array of SimpleXMLElement objects.
return $this->item($query->model(), $items, array('class' => 'set'));
}
public function item($model, array $data = array(), array $options = array()) {
// Recursively create Documents for arrays.
foreach($data as $key => $value) {
if(is_array($value)) {
$data[$key] = $this->item($model, $value, array('class' => 'entity'));
}
else if(is_object($value) && get_class($value) == "SimpleXMLElement") {
// Stripped code to extract data from XML object and put it in array $docData.
$data[$key] = $this->item($model, $docData, array('class' => 'entity'));
}
}
// Works perfectly for every (recursive) call with $options['class'] == 'entity' but fails for the final call with $options['class'] == 'set' (for this final call $data contains an array of Document objects).
return parent::item($model, $data, $options);
}
I would track the master branch instead of the release versions.
In your case, since you're boxing your objects manually, I would do something like:
return $this->_instance('set', compact('data'));
Related
I'm struggling to find a way to convert my object to the correct format.
I want to replace a function that we currently use on generating detailed array, as you can see below everything is static.
private function departmentArray($content=[])
{
return [ static::$A_DEPT_ID => $content
, static::$O_DEPT_ID => $content
];
}
A sample result when that runs is this
{"3":{"complete":0,"incomplete":0},"5":{"complete":0,"incomplete":0}}
I converted the method
private function departmentArray($content=[])
{
$depts = d::getAllMainDepartment();
$dept_array = [];
foreach ($depts as $dept) {
$dept_array[] = array($dept->id => $content);
}
return $dept_array;
}
The resulting format looks like this
[{"3":{"complete":0,"incomplete":0}},{"5":{"complete":0,"incomplete":0}}]
How can I maintain the same format on the first version of code?
You don't push into an associative array, you use the new key as an index.
$dept_array[$dept->id] = $content;
In Zf2 application written the model file to retrieve the data set from the table,
it works as expected for returning one result set, but for returning multiple rows not able to achieve by the below code.
Working and Returning single row
/**
* #param $id
* #return bool|Entity\Feeds
*/
public function getAppFeed($id)
{
$row = $this->select(array('app_id' => (int)$id))->current();
if (!$row)
return false;
$feedVal = new Entity\Feeds(array(
'id' => $row->id,
'title' => $row->title,
'link' => $row->link,
'Description' => $row->description,
'created' => $row->created,
));
return $feedVal;
}
Removed current and tried tablegateway object also but throwing the error.
Feeds table will have multiple record for each of the application, I need a function to achieve the same.
The Select always returns a ResultSet. You can access the objects(1) of ResultSet by iterating over it, because it implements the Iterator Interface.
Just an example piece of code:
public function getAppFeed($id)
{
$resultSet = $this->select(array('app_id' => (int)$id));
if ($resultSet instanceof \Zend\Db\ResultSet) {
foreach($resultSet as $item) {
// do your feed stuff here
// e.g. $item->id
}
} else {
return false;
}
}
(1) Object: meaning whatever object you asigned as Prototype in your TableGateway.
For further details, please checkout the documentation of ResultSet.
I have the following class definition:
class DatasegmentationController
{
public function indexAction()
{
$options['permissions'] = array(
'canDelete' => false,
'canEdit' => false
);
if ($this->getRequest()->isXmlHttpRequest()) {
$table = $this->getRequest()->getParam('table');
if ($table !== '' && $table !== null) {
$utilStr = new UtilString();
// This is a patch because class and tables names does not match
// so far it only happens with company and this is only for
// instantiate the proper class dynamically
$param_table = $table;
$table = $table === 'companies' ? 'company' : $table;
$classObj = strpos($table, '_') !== false ? $utilStr->stringToCamelCase($table, '_') : $utilStr->stringToCamelCase($table);
$className = new $classObj();
$module_map = $field_map[$param_table];
/** #var $module_map array */
$fields = [];
foreach ($module_map as $key => $value) {
$fields[] = [
'id' => $key,
'text' => $key
];
}
$conditions = json_decode($this->_request->getParam('conditions'), true);
$dynDataGridName = "DataSegmentation{$this->classObj}Grid";
$dynMethodName = "get{$this->classObj}GridModel";
$gridObj = new $dynDataGridName(
$this->className->$dynMethodName($conditions),
$this->view->users_id,
"{$table}_list",
"{$table}.{$table}_id",
'/datasegmentation/index',
'editor',
$options
);
return $this->_helper->json([
'fields' => $fields,
'grid' => $gridObj->getGridJs()
]);
}
if (isset($classObj, $className, $gridObj)) {
$page = $this->_request->getParam('page', 1);
$limit = $this->_request->getParam('rows', 20);
$col = $this->_request->getParam('sidx', 1);
$order = $this->_request->getParam('sord', 0);
$search = $this->_request->getParam('val', null);
echo $gridObj->getData($page, $limit, $col, $order, $search);
}
}
}
}
What the code above does is the following:
The URL http://localhost/datasegmentation is called
The view render a select element (modules) with options
When the select#modules is changed I sent it's value as part of the URL so the next AJAX call becomes: http://localhost/datasegmentation?table=companies (for example)
The indexAction() function then perform what is on the conditional for when $table is not empty or is not null
Among all those stuff it tries to generate everything dynamically as you may notice in code.
One of those stuff is a dinamic grid ($gridObj) which has a AJAX call to the same indexAction() but without parameters for populate the data after gets rendered
After the grid gets rendered at the view it makes the AJAX call and again the indexAction() is called and it skip the conditional for the table because the parameter is not set and tries the second conditional but surprise it fails because the objects that code needs to work are gone.
Having that scenario my questions are:
How do I keep the object alives between AJAX calls? Storing in a session var? Any other workaround?
If the answer is store them in a session var, is it recommendable? What about the answers on this, this and this among others?
How would you handle this?
The problem
The second AJAX call is the one adding data to the grid and it relies on the dynamic parameters. This is what I need to solve for make this to work.
I don't know if this is useful at all but this is being tested and develop on top of Zend Framework 1 project using PHP 5.5.x.
How do I keep the object alives between AJAX calls? Storing in a
session var? Any other workaround?
Storing the data in a session var
Storing the data in a file, with a client ID (could be a login, random, IP, etc)
Storing the data in a database.
If the answer is store them in a session var, is it recommendable?
What about the answers on this, this and this among others?
If you are storing critical data, use end to end encryption, SSL, HTTPS.
How would you handle this?
Using session variables.
I'm using FilterIterator to filter out the values and implemented the accept() method successfully. However I was wondering how would it be possible to get the values that returned false from my accept method in single iteration. Let's take the code below as an example (taken from php.net);
class UserFilter extends FilterIterator
{
private $userFilter;
public function __construct(Iterator $iterator , $filter )
{
parent::__construct($iterator);
$this->userFilter = $filter;
}
public function accept()
{
$user = $this->getInnerIterator()->current();
if( strcasecmp($user['name'],$this->userFilter) == 0) {
return false;
}
return true;
}
}
On the code above, it directly filters out the values and returns the values that pass from the filteriterator. Implemented as;
$array = array(
array('name' => 'Jonathan','id' => '5'),
array('name' => 'Abdul' ,'id' => '22')
);
$object = new ArrayObject($array);
$iterator = new UserFilter($object->getIterator(),'abdul');
It will contain only the array with name Jonathan. However I was wondering would it be possible to store the object with name Abdul in another variable using the same filter with a slight addition instead of reimplementing the entire filter to do the opposite?. One way I was thinking would exactly copy paste the FilterIterator and basically change values of true and false. However are there any neat ways of doing it, since it will require another traversal on the list.
I think you must rewrite the accept() mechanic. Instead of returning true or false, you may want to break down the array to
$result = array(
'passed' => array(...),
'not_passed' => array(...)
);
Your code may look like this
if (strcasecmp($user['name'], $this->userFilter) == 0) {
$result['not_passed'][] = $user;
} else {
$result['passed'][] = $user;
}
return $result;
A comment on the PHP manual states:
If you are using this method, remember
that the array of arguments need to be
passed in with the ordering being the
same order that the SOAP endpoint
expects.
e.g
//server expects: Foo(string name, int age)
//won't work
$args = array(32, 'john');
$out = $client->__soapCall('Foo', $args);
//will work
$args = array('john', 32);
$out = $client->__soapCall('Foo', $args);
I'm building a SOAP client that dynamically assigns the argument values, which means that it happens that the arguments aren't always in the correct order. This then breaks the actual SOAP call.
Is there an easy solution to this, short of checking the order of the parameters for each call?
I had the same problem where I dynamically added the SOAP parameters and I had to get them in the correct order for my SOAP call to work.
So I had to write something that will get all the SOAP methods from the WSDL and then determine in which order to arrange the method arguments.
Luckily PHP makes it easy to get the SOAP functions using the '$client->__getFunctions()' method, so all you need to do is search for the service method you want to call which will contain the method arguments in the correct order and then do some array matching to get your request parameter array in the same order.
Here is the code...
<?php
// Instantiate the soap client
$client = new SoapClient("http://localhost/magento/api/v2_soap?wsdl", array('trace'=>1));
$wsdlFunctions = $client->__getFunctions();
$wsdlFunction = '';
$requestParams = NULL;
$serviceMethod = 'catalogProductInfo';
$params = array('product'=>'ch124-555U', 'sessionId'=>'eeb7e00da7c413ceae069485e319daf5', 'somethingElse'=>'xxx');
// Search for the service method in the wsdl functions
foreach ($wsdlFunctions as $func) {
if (stripos($func, "{$serviceMethod}(") !== FALSE) {
$wsdlFunction = $func;
break;
}
}
// Now we need to get the order in which the params should be called
foreach ($params as $k=>$v) {
$match = strpos($wsdlFunction, "\${$k}");
if ($match !== FALSE) {
$requestParams[$k] = $match;
}
}
// Sort the array so that our requestParams are in the correct order
if (is_array($requestParams)) {
asort($requestParams);
} else {
// Throw an error, the service method or param names was not found.
die('The requested service method or parameter names was not found on the web-service. Please check the method name and parameters.');
}
// The $requestParams array now contains the parameter names in the correct order, we just need to add the values now.
foreach ($requestParams as $k=>$paramName) {
$requestParams[$k] = $params[$k];
}
try {
$test = $client->__soapCall($serviceMethod, $requestParams);
print_r($test);
} catch (SoapFault $e) {
print_r('Error: ' . $e->getMessage());
}
An easy solution exists for named parameters:
function checkParams($call, $parameters) {
$param_template = array(
'Foo' => array('name', 'age'),
'Bar' => array('email', 'opt_out'),
);
//If there's no template, just return the parameters as is
if (!array_key_exists($call, $param_template)) {
return $parameters;
}
//Get the Template
$template = $param_template[$call];
//Use the parameter names as keys
$template = array_combine($template, range(1, count($template)));
//Use array_intersect_key to filter the elements
return array_intersect_key($parameters, $template);
}
$parameters = checkParams('Foo', array(
'age' => 32,
'name' => 'john',
'something' => 'else'
));
//$parameters is now array('name' => 'john', 'age' => 32)
$out = $client->__soapCall('Foo', $parameters);
Not only does it correctly order the parameters, it also filters the parameters in the array.
Another solution is to verify the xsd files from your wsdl.
PHP SOAP construct the request based on parameters order in xsd files.