I want to pass a row object to a function that uses the configuration information stored in the row to build a Zend Search Lucene Document and add it to my index.
The information stored looks like this:
protected $_index = array(
'url' => array('UnIndexed', 'getUrl()'),
'fragment' => array('UnIndexed', 'getFragment()'),
'edited' => array('UnIndexed', 'edited'),
'title' => array('Text', 'getTitle()'),
'summary' => array('Text', 'getSummary()'),
'text' => array('UnStored', 'getText()'),
);
Here's the function I call:
public static function addDoc(My_Db_Table_Row_Abstract $row)
{
$config = $row->getIndexConfig();
if (empty($config)) {
return;
}
// setup the document
$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::keyword('table_name', $row->getTable()->info('name')))
->addField(Zend_Search_Lucene_Field::keyword('table_row_key', $row->getIndexRowKey()));
// add the configured fields
foreach ($config as $name => $opts) {
$type = $opts[0];
$value = eval('return $row->' . $opts[1]);
switch ($type) {
case 'Keyword' :
$field = Zend_Search_Lucene_Field::keyword($name, $value);
break;
case 'UnIndexed' :
$field = Zend_Search_Lucene_Field::unIndexed($name, $value);
break;
case 'Binary' :
$field = Zend_Search_Lucene_Field::binary($name, $value);
break;
case 'Text' :
$field = Zend_Search_Lucene_Field::text($name, $value);
break;
case 'UnStored ' :
$field = Zend_Search_Lucene_Field::unStored($name, $value);
break;
}
$document->addField($field);
}
// add to the index
self::getIndex()->addDocument($document);
}
As you can see, I need to get information via function calls into the document fields. I am concerned about using eval(), but I don't know another way. Is there a better way to accomplish the same functionality?
SOLUTION
I changed the configuration data to use functions for all the fields (removing the parenthesis), and lower-cased the first letter of the type so I could use that for call_user_func as well:
protected $_index = array(
'url' => array('unIndexed', 'getUrl'),
'fragment' => array('unIndexed', 'getFragment'),
'edited' => array('unIndexed', 'getEdited'),
'active_self' => array('keyword', 'isActive'),
'active_block' => array('keyword', 'isActiveBlock'),
'active_page' => array('keyword', 'isActiveNav'),
'members_only' => array('keyword', 'isMembersOnly'),
'title' => array('text', 'getTitle'),
'summary' => array('text', 'getSummary'),
'text' => array('unStored', 'getText'),
);
And the function call, which ended up being much simpler:
public static function addDoc(My_Db_Table_Row_Abstract $row)
{
$config = $row->getIndexConfig();
if (empty($config)) {
return;
}
// setup the document
$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::keyword('table_name', $row->getTable()->info('name')))
->addField(Zend_Search_Lucene_Field::keyword('table_row_key', $row->getIndexRowKey()));
// add the configured fields
foreach ($config as $name => $opts) {
$value = call_user_func(array($row, $opts[1]));
$field = call_user_func(array('Zend_Search_Lucene_Field', $opts[0]), $name, $value);
$document->addField($field);
}
// add to the index
self::getIndex()->addDocument($document);
}
You can also use call_user_func. Here is the PHP doc.
Example:
<?php
function barber($type)
{
echo "You wanted a $type haircut, no problem\n";
}
call_user_func('barber', "mushroom");
call_user_func('barber', "shave");
?>
Output:
You wanted a mushroom haircut, no problem
You wanted a shave haircut, no problem
For you example, you need to change :
$value = eval('return $row->' . $opts[1]);
by
$value = call_user_func($row->' . $opts[1]);
I guess you can do:
// ...
$type = $opts[0];
$method = $opts[1];
$value = $row->$method;
Or:
// ...
$type = $opts[0];
call_user_func(array($row, $opts[1]));
Remove the parens from your method names:
protected $_index = array(
'url' => array('UnIndexed', 'getUrl'),
'fragment' => array('UnIndexed', 'getFragment'),
...
Replace this attribute:
'edited' => array('UnIndexed', 'edited'),
With an equivalent getter method:
'edited' => array('UnIndexed', 'getEdited'),
Then just do this:
$method = $opts[1];
$value = $row->$method();
Related
I have this method:
private function formatCliendCardData($data) {
$formatedData = array();
$formatedData['first_name'] = trim($data['name']);
$formatedData['last_name'] = trim($data['surname']);
$formatedData['date_of_birth'] = $data['birth_year'] . '-' . sprintf("%02d", $data['birth_month_id']) . '-' . sprintf("%02d", $data['birth_day']); //[yyyy-mm-dd]
$formatedData['sex'] = ($data['sex_id'] == 's1'? 'm' : 'f');
$formatedData['county'] = 'Latvija'; //#TODO get real data
$formatedData['post_index'] = (int) preg_replace( '/[^0-9]/', '', $data['post_index']);
$formatedData['city'] = trim($data['city']);
$formatedData['street'] = trim($data['street']);
$formatedData['house_nr'] = trim($data['house_nr']);
$formatedData['phone'] = trim($data['phone']);
$formatedData['email'] = trim($data['email']);
return $formatedData;
}
I run in this problem from time to time. I have big method that just reformats data and returns it. It looks ugly. There is few other aproaches Im aware -- just make foreach loop, but there are exeptions, so i need make 'ifs' anyway. What is better way to reformat such data in your opinon?
I aggree with helloei,
create a class to handle all the formating and validation in the setter.
class Person
{
public function setName($name)
{
$this->name = trim($name);
}
...
}
and then create the object in your function:
private function formatCliendCardData($data) {
$person = new Person();
$person->setName($data['name`])
...
if you have highly custom condition to reformat some datas i think you don't have any other solution that apply this condition manually with some if/else or other custom loop.
if else, you have some unified condition to reformat you can aggregate this and apply in batch at your datas. In other word for my opinion the only other solution are: generalize conditions of your reformatting operations and apply this at your data.
this is Object Oriented approach
IMHO In those cases you have to reduce and clean your code. Often I use an helper function that allows me to split data and formatting rules.
// helper function (OUTSITE YOUR CLASS)
function waterfall($input=NULL,$fns=array()){
$input = array_reduce($fns, function($result, $fn) {
return $fn($result);
}, $input);
return $input;
}
// your formatting rules
$rules = array(
'first_name' => array('trim'),
'last_name' => array('trim'),
'date_of_birth' => array(
'f0' => function($x){
return sprintf("%s-%02d-%02d",
$x['birth_year'],
$x['birth_month_id'],
$x['birth_day']
);
},
'trim'
),
'sex' => array(
'f0' => function($x){
if($x['sex_id'] == 's1'){
return 'm';
}else{
return 'f';
}
}
),
'post_index' => array(
'f0' => function($x){
return (int) preg_replace('/[^0-9]/', NULL, $x);
},
'trim'
),
'city' => array('trim'),
'email' => array('strtolower','trim')
);
// your data
$data = array(
'first_name' => ' Andrea',
'last_name' => 'Ganduglia ',
'date_of_birth' => array(
'birth_year' => '1899',
'birth_month_id' => 5,
'birth_day' => 7
),
'post_index' => 'IT12100',
'city' => 'Rome',
'email' => 'USER#DOMAIN.tld '
);
// put all together
$formatedData = array();
foreach($data as $k => $v){
$formatedData[$k] = waterfall($v,$rules[$k]);
}
// results
print_r($formatedData);
/*
Array
(
[first_name] => Andrea
[last_name] => Ganduglia
[date_of_birth] => 1899-05-07
[post_index] => 12100
[city] => Rome
[email] => user#domain.tld
)
*/
So, I have a switch statement inside a foreach loop that checks the value of a field in the array and uses the value to build a named array and email address variable for each unique value. Question is below example code.
As an example, if the values are foo, bar, and foobar, then the following occurs:
switch($value['value']) {
case 'foo':
if(!isset($foo)){
$foo = array();
}
if(!isset($email_foo)){
$email_foo = convert_to_email($value['value']);
}
array_push($foo, $value);
break;
case 'bar':
if(!isset($bar)){
$bar = array();
}
if(!isset($email_bar)){
$email_bar = convert_to_email($value['value']);
}
array_push($bar, $value);
break;
case 'foobar':
if(!isset($foobar)){
$foobar = array();
}
if(!isset($email_foobar)){
$email_foobar = convert_to_email($value['value']);
}
array_push($foobar, $value);
break;
default:
if(!isset($default)){
$default = array();
}
if(!isset($email_default)){
$email_default = 'this#isemail.com';
}
array_push($default, $value);
}
Resulting in 4 different email addresses:
$email_foo = 'foo#isemail.com';
$email_bar = 'bar#isemail.com';
$email_foobar = 'foobar#isemail.com';
$email_default = 'default#isemail.com';
and 4 different arrays of data:
$foo = array(
0 => array(
'value' => 'F Oo',
'name' => 'Janet',
'age' => 23
),
1 => array(
'value' => 'F Oo',
'name' => 'Doug',
'age' => 42
)
)
$bar = array(
0 => array(
'value' => 'B Ar',
'name' => 'James',
'age' => 23
),
1 => array(
'value' => 'B Ar',
'name' => 'Donald',
'age' => 42
)
)
etc...
So, here is the question:
Is it possible to write a class that can be used to create all of the named arrays and email variables? Sort of something like this:
class Account_Manager_Build
{
public function __construct()
{
if(!isset($this)){
$this = array();
}
if(!isset("email_$this")){
"email_$this" = convert_to_email($this->value['value']);
}
array_push($this, $this->value);
}
}
For testing purposes, here is the function convert_to_email that is used throughout the examples:
function convert_to_email($input){
$returned = strtolower(substr($input, 0, 1)).strtolower(end(str_word_count($input, 2))).'#ismail.com';
return $returned;
}
Actually, you can create dynamically variables using the $$ notation:
$variable = "email_$name";
$$variable = "something";
However I'm not sure of what you're trying to do
What's the point of computing $email_something if it can be easily computed from something?
Why do you create those variables instead of just storing this in an array?
In a repository, I need a custom getALL query which checks for items based on an array of data. e.g. I need to be able to send $params = [ 'type' => [ 'type1', 'type2' ], 'username' => $username ] but I can currently only send one parameter such as: $params = ['type' => 'type1', 'username' => $username].
What would be the best way of adding acceptance of an array (like the one above) to this getAll query?:
public function getAllQuery($params = [])
{
$query = $this->createQueryBuilder('c');
if(count($params))
{
foreach($params as $param => $value)
{
//todo need to make it accept an array of parameters
if(in_array($param, $this->getClassMetadata()->getFieldNames()) && $param != 'deleted')
{
$query
->andWhere(sprintf('c.%1$s LIKE :%1$s', $param))
->setParameter($param, sprintf('%%%s%%', $value));
}
/*code already exists here for checking other parameters
outside of 'type' (e.g. 'username') */
}
}
return $query->getQuery();
}
I am not able to comment so I will answer without to be sur that I've understand the question. Are you waiting something like this ? In this case it's more a PHP problem than Doctrine.
The results of the past code : here.
$params = array( 'type' => array( 'type1', 'type2' ), 'username' => 'flavien');
$requiredFields = array('type');
if(count($params))
{
foreach($params as $param => $value)
{
if(in_array($param, $requiredFields) && $param != 'deleted')
{
if(is_array($value))
{
echo "My param: {$param}" . PHP_EOL;
for($i=0; $i < count($value); $i++) {
echo $value[$i] . PHP_EOL;
}
}
}
}
}
I have a class which stores values with a multi-level associative array:
I need to add a way to access and modify nested values. Here is a working solution for my problem, but it is rather slow. Is there a better way of doing this?
Note: The use of get / set functions is not mandatory, but there needs to be an efficient way to define a default value.
class Demo {
protected $_values = array();
function __construct(array $values) {
$this->_values = $values;
}
public function get($name, $default = null) {
$token = strtok($name, '.#');
$node = $this->_values;
while ($token !== false) {
if (!isset($node[$token]))
return $default;
$node = $node[$token];
$token = strtok('.#');
}
return $node;
}
public function set($name, $value) {
$next_token = strtok($name, '.#');
$node = &$this->_values;
while ($next_token !== false) {
$token = $next_token;
$next_token = strtok('.#');
if ($next_token === false) {
$node[ $token ] = $value;
break;
}
else if (!isset($node[ $token ]))
$node[ $token ] = array();
$node = &$node[ $token ];
}
unset($node);
}
}
Which would be used as follows:
$test = new Demo(array(
'simple' => 27,
'general' => array(
0 => array(
'something' => 'Hello World!',
'message' => 'Another message',
'special' => array(
'number' => 27
)
),
1 => array(
'something' => 'Hello World! #2',
'message' => 'Another message #2'
),
)
));
$simple = $test->get('simple'); // === 27
$general_0_something = $test->get('general#0.something'); // === 'Hello World!'
$general_0_special_number = $test->get('general#0.special.number'); === 27
Note: 'general.0.something' is the same as 'general#0.something', the alternative punctuation is for the purpose of clarity.
Well, the question was interesting enough that I couldn't resist tinkering a bit more. :-)
So, here are my conclusions. Your implementation is probably the most straightforward and clear. And it's working, so I wouldn't really bother about searching for another solution. In fact, how much calls are you gonna get in the end? Is the difference in performance worth the trouble (I mean between "super ultra blazingly fast" and "almost half as fast")?
Put aside though, if performance is really an issue (getting thousands of calls), then there's a way to reduce the execution time if you repetitively lookup the array.
In your version the greatest burden falls on string operations in your get function. Everything that touches string manipulation is doomed to fail in this context. And that was indeed the case with all my initial attempts at solving this problem.
It's hard not to touch strings if we want such a syntax, but we can at least limit how much string operations we do.
If you create a hash map (hash table) so that you can flatten your multidimensional array to a one level deep structure, then most of the computations done are a one time expense. It pays off, because this way you can almost directly lookup your values by the string provided in your get call.
I've come up with something roughly like this:
<?php
class Demo {
protected $_values = array();
protected $_valuesByHash = array();
function createHashMap(&$array, $path = null) {
foreach ($array as $key => &$value) {
if (is_array($value)) {
$this->createHashMap($value, $path.$key.'.');
} else {
$this->_valuesByHash[$path.$key] =& $value;
}
}
}
function __construct(array $values) {
$this->_values = $values;
$this->createHashMap($this->_values);
// Check that references indeed work
// $this->_values['general'][0]['special']['number'] = 28;
// print_r($this->_values);
// print_r($this->_valuesByHash);
// $this->_valuesByHash['general.0.special.number'] = 29;
// print_r($this->_values);
// print_r($this->_valuesByHash);
}
public function get($hash, $default = null) {
return isset($this->_valuesByHash[$hash]) ? $this->_valuesByHash[$hash] : $default;
}
}
$test = new Demo(array(
'simple' => 27,
'general' => array(
'0' => array(
'something' => 'Hello World!',
'message' => 'Another message',
'special' => array(
'number' => 27
)
),
'1' => array(
'something' => 'Hello World! #2',
'message' => 'Another message #2'
),
)
));
$start = microtime(true);
for ($i = 0; $i < 10000; ++$i) {
$simple = $test->get('simple', 'default');
$general_0_something = $test->get('general.0.something', 'default');
$general_0_special_number = $test->get('general.0.special.number', 'default');
}
$stop = microtime(true);
echo $stop-$start;
?>
The setter is not yet implemented, and you would have to modify it for alternative syntax (# separator), but I think it conveys the idea.
At least on my testbed it takes half the time to execute this compared to the original implementation. Still raw array access is faster, but the difference in my case is around 30-40%. At the moment that was the best I could achieve. I hope that your actual case is not big enough that I've hit some memory constraints on the way. :-)
Ok, my first approached missed the goal I was aiming for. Here is the solution to using native PHP array syntax (at least for access) and still being able to set a default value.
Update: Added missing functionality for get/set and on the fly converting.
By the way, this is not an approach to take if you are optimizing for performance. This is perhaps 20 times slower than regular array access.
class Demo extends ArrayObject {
protected $_default;
public function __construct($array,$default = null) {
parent::__construct($array);
$this->_default = $default;
}
public function offsetGet($index) {
if (!parent::offsetExists($index)) return $this->_default;
$ret = parent::offsetGet($index);
if ($ret && is_array($ret)) {
parent::offsetSet($index, $this->newObject($ret));
return parent::offsetGet($index);
}
return $ret;
}
protected function newObject(array $array=null) {
return new self($array,$this->_default);
}
}
Init
$test = new Demo(array(
'general' => array(
0 => array(
'something' => 'Hello World!'
)
)
),'Default Value');
Result
$something = $test['general'][0]['something']; // 'Hello World!'
$notfound = $test['general'][0]['notfound']; // 'Default Value'
You're looking for something like that? Essentially the get() method uses references to descend into the $values array and breaks out of the method if a requirement could not be met.
class Demo {
protected $_values = array();
public function __construct(array $values) {
$this->_values = $values;
}
public function get($name, $default = null) {
$parts = preg_split('/[#.]/', $name);
if (!is_array($parts) || empty($parts)) {
return null;
}
$value = &$this->_values;
foreach ($parts as $p) {
if (array_key_exists($p, $value)) {
$value = &$value[$p];
} else {
return null;
}
}
return $value;
}
/**
* setter missing
*/
}
$test = new Demo(array(
'simple' => 2,
'general' => array(
0 => array(
'something' => 'Hello World!',
'message' => 'Another message',
'special' => array(
'number' => 4
)
),
1 => array(
'something' => 'Hello World! #2',
'message' => 'Another message #2'
)
)
));
$v = $test->get('simple');
var_dump($v);
$v = $test->get('general');
var_dump($v);
$v = $test->get('general.0');
var_dump($v);
$v = $test->get('general#0');
var_dump($v);
$v = $test->get('general.0.something');
var_dump($v);
$v = $test->get('general#0.something');
var_dump($v);
$v = $test->get('general.0.message');
var_dump($v);
$v = $test->get('general#0.message');
var_dump($v);
$v = $test->get('general.0.special');
var_dump($v);
$v = $test->get('general#0.special');
var_dump($v);
$v = $test->get('general.0.special.number');
var_dump($v);
$v = $test->get('general#0.special.number');
var_dump($v);
$v = $test->get('general.1');
var_dump($v);
$v = $test->get('general#1');
var_dump($v);
$v = $test->get('general.1.something');
var_dump($v);
$v = $test->get('general#1.something');
var_dump($v);
$v = $test->get('general.1.message');
var_dump($v);
$v = $test->get('general#1.message');
var_dump($v);
This is how multidimensional array work in general in PHP:
$data = array(
'general' => array(
0 => array(
'something' => 'Hello World!'
)
)
);
To receive Hello World:
echo $data['general'][0]['something'];
I'm sure there is a better to this. Any help will be greatly appreciated.
I want to pass an array to a php function that contains the argument and all the arguments are optional. I'm using code ignitor and am by no means an expert. Below is what i have been using so far:
function addLinkPost($postDetailArray) {
if (isset($postDetailArray['title'])) {
$title = $postDetailArray['title']; }
else {
$title = "Error: No Title";
}
if (isset($postDetailArray['url'])) {
$url = $postDetailArray['url'];
} else {
$url = "no url";
}
if (isset($postDetailArray['caption'])) {
$caption = $postDetailArray['caption'];
} else {
$caption = "";
}
if (isset($postDetailArray['publish'])) {
$publish = $postDetailArray['publish'];
} else {
$publish = TRUE;
}
if (isset($postDetailArray['postdate'])) {
$postdate = $postDetailArray['postdate'];
} else {
$postdate = "NOW()";
}
if (isset($postDetailArray['tagString'])) {
$tagString = $postDetailArray['tagString'];
} else {
$tagString = "";
}
You can use an array of defaults and then merge the argument array with the defaults. The defaults will be overridden if they appear in the argument array. A simple example:
$defaults = array(
'foo' => 'aaa',
'bar' => 'bbb',
'baz' => 'ccc',
);
$options = array(
'foo' => 'ddd',
);
$merged = array_merge($defaults, $options);
print_r($merged);
/*
Array
(
[foo] => ddd
[bar] => bbb
[baz] => ccc
)
*/
In your case, that would be:
function addLinkPost($postDetailArray) {
static $defaults = array(
'title' => 'Error: No Title',
'url' => 'no url',
'caption' => '',
'publish' => true,
'postdate' => 'NOW()',
'tagString' => '',
);
$merged = array_merge($defaults, $postDetailArray);
$title = $merged['title'];
$url = $merged['url'];
$caption = $merged['caption'];
$publish = $merged['publish'];
$postdate = $merged['postdate'];
$tagString = $merged['$tagString'];
}
You could do it like this:
function addLinkPost(array $postDetailArray)
{
$fields = array(
'key' => 'default value',
'title' => 'Error: No Title',
);
foreach ($fields as $key => $default) {
$$key = isset($postDetailArray[$key]) ? $postDetailArray[$key] : $default;
}
}
Simply edit the $fields array with your key and its default value.
Using the array as an argument is a good idea in this case. However, you could simplify the code in the function a bit by using the ternary operator (http://dk.php.net/ternary):
$title = isset($postDetailArray['title']) ? $postDetailArray['title'] : 'Error: No Title';
You could simplify it even more by doing the following:
function addLinkPost($data) {
$arguments = array('title', 'url', 'caption', 'publish', 'postdate', 'tagString');
foreach ($arguments as $value) {
$$value = isset($data[$value]) ? $data[$value] : 'Error: No '.$value;
}
}
Try this:
function addLinkPost($postDetailArray) {
foreach($array as $key=>$value){
$$key = (isset($value) && !empty($value)) ? $value : ('no '.$key);
}
//all keys are available as variables
var_dump($url); var_dump($publish); //etc
}
You could make all elements of the array parameters of the function. Check if the first is an array in the function and if so, extract the array.
function addLinkPost($title = null, $url = null, $caption = null, $publish = null, $postDate = null, $tagString = null)
{
if(is_array($title)) {
extract($title);
}
....
}
Maybe that makes the code a little more clear.
How about:
function getdefault($value, $default = null) {
return isset($value) ? $value : $default;
}
function addLinkPost($postDetailArray) {
$title = getdefault($postDetailArray['title'], 'Error: No Title');
$url = getdefault($postDetailArray['url'], 'no url');
$caption = getdefault($postDetailArray['caption'], '');
$publish = getdefault($postDetailArray['publish'], TRUE);
$postdate = getdefault($postDetailArray['postdate'], 'NOW()');
$tagString = getdefault($postDetailArray['tagString'], '');
}
or alternatively:
$defaults = array(
'title' => 'Error: No Title',
'url' => 'no url',
'caption' => '',
'publish' => TRUE,
'postdate' => 'NOW()',
'tagString' => '',
);
function addLinkPost($postDetailArray) {
global $defaults;
foreach ($defaults as $k => $v) {
$$k = isset($postDetailArray[$k]) ? $postDetailArray[$k] : $v;
}
}
With the one warning that if you have an array key of 'defaults' in $defaults, it will overwrite the global $defaults.