I recently faced a design problem with PHP. I noticed that in a function you can pass as parameter an array. I didn't noticed the powerful of this thing first, but now i'm obsessed with arrays.
For example, in my template class i have to pass some variables and some mysqli_results into the template file (like phpbb do). And i was wondering which one of the following possibilities is the best.
# 1
$tpl = new template(array(
'vars' = array('var1' => 'val1', 'var2' => 'val2'),
'loops' = array('loop1' => $result1, 'loop2' => $result2)
));
# 2
$tpl = new template;
$tpl->assignVars(array(
'var1' => 'val1',
'var2' => 'val2'
));
$tpl->assignloops(array(
'loop1' => $result1,
'loop2' => $result2
));
# 3
$tpl = new template;
$tpl->assignVar('var1', 'val1');
$tpl->assignVar('var1', 'val1');
$tpl->assignLoop('loop1', $result1);
$tpl->assignLoop('loop2', $result2);
Or if there is something better. I was even thinking about creating a db class that performs a query as follow:
$result = $db->fastQuery(array(
'select' => 'user-name',
'from' => $table,
'where' => array('user-id' => 123, 'user-image' => 'none'),
'fetch' => true
));
Oh my God, i'm really obsessed.
If it was up to me I would chose #1, I don't like nesting objects and arrays only if it is necessary. by doing so I keep my code simple.
and if you follow your obsession you may end up writing a full ORM.
#4
Allowing both:
function assign($name, $val = null)
{
if (is_array($name)) {
// loop through and assign
} else {
// assign single var
}
}
This is akin to overloading techniques you would see in C++/Java.
You can also allow #1 by just calling assign in the constructor. It is not uncommon in OOP programming to have the constructor allow a shortcut to setting properties that can also be set in other methods.
Related
I just conducted this very interesting experiment and the results came out quite surprising.
The purpose of the test was to determine the best way, performance-wise, to get an element of an array. The reason is that I have a configuration class which holds settings in an associative mufti-dimensional array and I was not quite sure I was getting these values the best way possible.
Data (it is not really needed for the question, but I just decided to include it so you see it is quite a reasonable amount of data to run tests with)
$data = array( 'system' => [
'GUI' =>
array(
'enabled' => true,
'password' => '',
),
'Constants' => array(
'URL_QUERYSTRING' => true,
'ERRORS_TO_EXCEPTIONS' => true,
'DEBUG_MODE' => true,
),
'Benchmark' =>
array(
'enabled' => false,
),
'Translations' =>
array(
'db_connection' => 'Default',
'table_name' =>
array(
'languages' => 'languages',
'translations' => 'translations',
),
),
'Spam' =>
array(
'honeypot_names' =>
array(
0 => 'name1',
1 => 'name2',
2 => 'name3',
3 => 'name4',
4 => 'name5',
5 => 'name6',
),
),
'Response' =>
array(
'supported' =>
array(
0 => 'text/html',
1 => 'application/json',
),
),]
);
Methods
function plain($file, $setting, $key, $sub){
global $data;
return $data[$file][$setting][$key][$sub];
}
function loop(...$args){
global $data;
$value = $data[array_shift($args)];
foreach($args as $arg){
$value = $value[$arg];
}
return $value;
}
function arr(){
global $data;
return $data;
}
Parameters (when calling the functions)
loop('system', 'Translations', 'table_name', 'languages');
plain('system', 'Translations', 'table_name', 'languages');
arr()['system']['Translations']['table_name']['languages'];
Leaving aside any other possible flaws and focusing on performance only, I ran 50 tests with 10000 loops. Each function has been called 500000 times in total. The results are in average seconds per 10000 loops:
loop: 100% - 0.0381 sec. Returns: languages
plain: 38% - 0.0146 sec. Returns: languages
arr: 23% - 0.0088 sec. Returns: languages
I was expecting loop to be quite slow because there is logic inside, but looking at the results of the other two I was pretty surprised. I was expecting plain to be the fastest because I'm returning an element from the array and for the opposite reason arr to be the slowest because it returns the whole array.
Given the outcome of the experiment I have 2 questions.
Why is arr almost 2 times faster than plain?
Are there any other methods I have missed that can outperform arr?
I said this in the comment, but I decided it's pretty close to an answer. Your question basically boils down to why is 2+2; not faster than just plain 2;
Arrays are just objects stored in memory. To return an object from a function, you return a memory address (32 or 64 bit unsigned integer), which implies nothing more than pushing a single integer onto the stack.
In the case of returning an index of an array, that index really just represents an offset from the base address of the array, so everytime you see a square bracket-style array access, internally PHP (rather the internal C implementation of PHP) is converting the 'index' in the array into an integer that it adds to the memory address of the array to get the memory address of the stored value at that index.
So when you see this kind of code:
return $data[$file][$setting][$key][$sub];
That says:
Find me the address of $data. Then calculate the offset that the string stored in $file is (which involves looking up what $file is in memory). Then do the same for $setting, $key, and $sub. Finally, add all of those offsets together to get the address (in the case of objects) or the value (in the case of native data types) to push on to the stack as a return value.
It should be no surprise then that returning a simple array is quicker.
That's the way PHP works. You expect, that a copy of $data is returned here. It is not.
What you acutaly have, is a pointer (Something like an ID to a specific place in the memory), which reference the data.
What you return, is the reference to the data, not the data them self.
In the plain method, you search for the value first. This cost time. Take a look at this great article, which show, how arrays are working internal.
Sometimes Code say's more then words. What you assume is:
function arr(){
global $data;
//create a copy
$newData = $data;
//return reference to $newData
return $newData;
}
Also you should not use global, that is bad practise. You can give your array as a parameter.
//give a copy of the data, slow
function arr($data) {
return $data;
}
//give the reference, fast
function arr(&$data) {
return $data;
}
community.
I've been looking for an answer, but not close enough to this scenario
I have a code like this (actually working fine):
array('desc'=>'Home')
It is working defining it as text (Home) but I would rather to use it as a PHP variable due multilanguage site. Using the language package I have a variable such:
$lang['HOME'] = 'Home';
So depending on the language selected, the value changes
In other words, I need the array to have the variable value as the element
array('desc'=>'variablehere')
Can anyone plese let me know what am I missing? I have tried to set it as variable, as echo and so other ways.
Thanks a lot!
Like this?
$myArray = array('desc' => $variable);
or
$myArray = array(
'desc' => $desc,
'name' => $name
);
In your case:
$lang = array('HOME' => $homeVariable);
Use a multi-dimensional array. e.g., given something like
$lang = 'en';
$key = 'desc';
The exact array structure depends on your needs, but it could either be primarily by language, or by key-to-translate:
language-based:
$translations = array(
'en' => array('desc' => 'foo'),
'fr' => array('desc' => 'bar')
);
$text_to_display = $translations['en']['desc']; // produces 'foo'
or
$translations = array(
'desc' => array(
'en' => array('desc' => 'foo'),
'fr' => array('desc' => 'bar')
)
)
$text_to_display = $translations['desc']['fr']; // produces 'bar'
Use a translate function instead:
// It can be key-based, i.e., t('home_string'), or literal like below
t('Home');
function t($text, $language = NULL)
{
if (!$language) {
// determine visitor's language
}
// look up translation in db/file
// if none found just return $text as is
}
I am working with a (highly optimized/adapted version of) CakePHP 2.3 and my application is running on VERY slow hardware (300MHz ARM) so I am still optimizing wherever I can. One method of the framework is called VERY often and not very fast (~1-5ms), but I can not think of a way to improve it (without changing the output) - in total I spend ~10% of the total time in this method:
public static function normalizeObjectArray($objects) {
$normal = array();
foreach ($objects as $i => $objectName) {
$options = array();
if (!is_int($i)) {
$options = (array)$objectName;
$objectName = $i;
}
list(, $name) = pluginSplit($objectName);
$normal[$name] = array('class' => $objectName, 'settings' => $options);
}
return $normal;
}
Does anyone have an idea how to speed this up?
The profiler has the following output for one of the calls - I already asked how to improve pluginSplit in this question:
(Profiling is about 10-15 times slower then normal execution)
Is it the is_int that is that slow or where is that time "lost"?
Optimize by removing the method.
normalizeObjectArray is the method that converts arrays like this:
public $foo = array(
'One',
'Two',
'Three' => array('option' => 1, 'other' => 2)
);
into:
public $foo = array(
'One' => array('className' => 'One', 'settings' => array()),
'Two' => array('className' => 'Two', 'settings' => array()),
'Three' => array('className' => 'Three', 'settings' => array('option' => 1, 'other' => 2))
);
If instead of trying to optimize this code, you refactor the code to not call it and ensure that wherever it's called the array is already in the format required (e.g. component, helper, behavior arrays), the logic is redundant and can simply be removed.
First you can avoid the list. Instead, you can do:
$normal[pluginSplit($objectName)[1]] = ... ;
Secondly, I think (not sure) that ctype_digit() can improve performance a bit.
By the way, could you give an example of content of $objects? It sounds like a weird array...
Ok I am making a blog system using MongoDB as the back end. I want to do the same thing as wordpress when you edit it saves the past versions and allows you to revert to them if needed.
I would like to do the same.
I have a few ways to doing it. but un sure if this is the best easiest way to do it and would like some suggestions.
the first is a find and the insert $SET
<?php
$cursor = $collection->find(array("_id"=> new MongoId($data)));
if ($cursor->count() > 0)
{
while( $cursor->hasNext() ) {
foreach($cursor->getNext() as $key => $value)
{
define("_".strtoupper($key), $value);
}
}
$cursor = $collection->update(array("_id" => new MongoId($data)),
'$set'=>array("title"=>$data['TITLE'], "content"=>$data['content'], "past_versons"=>array("title" => _TITLE, "content" => _CONTENT)));
}
?>
So my question is this the way I would do it.
here a sample JSON
{
"title":"blog title",
"content":"blog content",
"past_verson":[{"title":"blog title past","content":"past blog content"}]
}
In your example, you're iterating over a MongoCursor even though you only expect to work with one document at most. MongoCollection::findOne() is going to be more appropriate here, as it will return the document array of the first result or null if no documents matched.
Even if you needed to iterate across multiple results, you'd do better to take advantage of the iterable nature of MongoCursor (it implements Iterator, which extends Traversable, which means you can use it in a foreach loop). I also don't think define() is appropriate for storing what is essentially a temporary variable here. Moreover, suppose this code needed to run twice (error due to redefinition) or the title or content was not a scalar (another error).
Consider the following rewrite (I assumed $data['id'] is correct and you made a typo in the above code by using $data alone for the MongoId):
$document = $collection->findOne(array('_id' => new MongoId($data['id'])));
if (null !== $document) {
$collection->update(
array('_id' => new MongoId($data['id'])),
array(
'$set' => array(
'title' => $data['title'],
'content' => $data['content'],
),
'$push' => array(
'past_versions' => array(
'title' => $document['title'],
'content' => $document['content'],
),
),
)
);
}
One deviation here, which you may not need, is that I'm aggregating old versions into a past_versions array. We can use the $push operator to append to an array field in the document. Alternatively, you could simply use $set if you only need/want to store the most recent version.
It's pretty handy in JS to create objects like this:
test = { foo : { bar : "hello world" }, bar2 : "hello world 2" }
and then use them like:
test.foo.bar
test.bar2
Is there anything like this in PHP without declaring classes?
It's called associative arrays.
Example (note: the indentation is for layout purposes):
$test = array(
'foo' => array(
'bar' => 'hello world'
),
'bar2' => 'hello world 2'
);
$test['foo']['bar'];
$test['bar2'];
This is equivalent to the following Javascript code:
var test = {
'foo': {
'bar': 'hello world',
},
'bar2': 'hello world 2'
};
As an alternative, you can use the pre-declared StdClass.
$test = new StdClass;
$test->foo = new StdClass;
$test->foo->bar = 'hello world';
$test->bar2 = 'hello world 2';
which would be written in JavaScript as:
var test = new Object;
test.foo = new Object;
test.foo.bar = 'hello world';
test.bar2 = 'hello world 2';
(note: new Object is the same as {} in Javascript)
stdClass allows you to create (essentially) typeless objects. For example:
$object = (object) array(
'name' => 'Trevor',
'age' => 42
);
As shown here, the fastest way to create a stdClass object is to cast an associative array. For multiple levels, you just do the same thing again inside like this:
$object = (object) array(
'name' => 'Trevor',
'age' => '42',
'car' => (object) array(
'make' => 'Mini Cooper',
'model' => 'S',
'year' => 2010
)
);
Another method is to convert the associative array to an object afterwards with a recursive function. Here's an example.
function toObject(array $array) {
$array = (object) $array;
foreach ($array as &$value)
if (is_array($value))
$value = toObject($value);
return $array;
}
// usage:
$array = // some big hierarchical associative array...
$array = toObject($array);
This is useful when you're not the one making the associative array.
Unfortunately, even though PHP 5.3 supports anonymous methods, you cannot put an anonymous method into a stdClass (though you can put one into an associative array). But this isn't too bad anyway; if you want functionality in it, you really should create a class instead.
You can use a StdClass object or an ArrayObject which are included in php (though the latter requires that you have SPL installed). Though unless you need to access the values specifically with the -> operator its more efficient to just use an associative array instead.
I think what you are looking for is an Associative Array
$test["foo"]["bar"]
http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=keyed+arrays
The closest thing would be arrays.
$test = array(
'foo' => array('bar' => 'hello world'),
'bar2' => 'hello world 2',
);
echo $test['foo']['bar'];
Technically, no. However if you are creating a data object (ie no methods), you could technically write a JSON string and use
$obj = json_decode($obj_string);
I wouldn't recommend it however. I assume there would be significant speed loss.
EDIT
Though it goes without mentioning, associative arrays should be used for this instead of flat data objects.
The only reason to do that is if you wish to pass data back to a JavaScript function with JSON. In that case, use json_encode on the array. Otherwise, just keep it as an array, as there's not reason to encode it and then decode it just so it looks like JavaScript.
Try this way: https://github.com/ptrofimov/jslikeobject
Author implemented JS-like objects, you can even access properties from functions via $this pointer.
But perhaps it is not so good to use such objects instead of usual ones.
$a = array(
'a'=> 123,
'b'=> 334,
'c'=> 7853
);
echo json_encode($a);
This will be the result: {"a":123,"b":334,"c":7853}