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.
Related
I realize there are a number of questions about multidimensional arrays and foreach loops, and I have spent hours reading through them and trying to get my own loop to work -- without success. If the solution is a duplicate, I'll remove my question (or link to another if that is preferred).
Now, the challenge:
Using an array of returned MYSQL results. The results are from multiple joined tables in an associative array. Now I need to convert it to the multidimensional array I need.
I've got most of it working, but my issue is looping through and adding new items to the right place in the array.
Here's some code:
//example of how array is setup in the way I want, this part works.
foreach($results as $i => $a):
//some other code is here, see below.
$items[$i] = [
"id" => $a['id'],
"itemid" => $a['itemid'],
"name" => $a['name'],
"def" => $a['def'],
"class" => $a['class'],
"timeline" => $a['timeline'],
"files" => [
[0] => [
"id" => $a['fileid'],
"name" => $a['filename'],
"path" => $a['filepath'],
"type" => $a['filetype']
]
],
"tags" => [
[0] => [
"id" => $a['tagid'],
"name" => $a['tagname']
]
]
];
endforeach;
Then I've tried a number of ways to loop through in order to only add to the 'tags' or 'files' if the item 'id' is the same as the last. Here is the current code in my editor, not working:
//inside foreach loop, before above code
if($items[$i-1]['id'] == $a['id']):
//it is the same item, works to here.
if(in_array($a['filename'], $items[$i-1], FALSE)):
//add to files array for last item
$items[$i-1]['files'][] = [
"id" => $a['fileid'],
"name" => $a['filename'],
"path" => $a['filepath'],
"type" => $a['filetype']
];
elseif(in_array($a['tagname'], $items[$i-1], FALSE)):
//add to tags array for last item
$items[$i-1]['tags'][] = [
"id" => $a['tagid'],
"name" => $a['tagname']
];
endif;
else:// else it does the code above
As you can see, my most recent attempt was to use in_array, which I now realize doesn't work on multidimensional arrays. My issue is that I can't figure out how to determine if its a new file or new tag for the same item.
Ultimately, I want an array of 'items' which have multiple 'files' and 'tags.' I'm going to json_encode and use it with JS afterwards.
Any advice on how to get this working or optimize it, would be greatly appreciated.
P.S. As I mentioned above, I know this question has been asked before -- though I wasn't able to get their solutions working for me. I'll remove this question if the solution is a duplicate (as in, it's not really helpful to others). Thank you for any help, it is greatly appreciated!
Don't use "autoincrementing" array indices as they easily get messed up. Use your database id since it's already there:
//example of how array is setup in the way I want, this part works.
foreach($results as $i => $a):
$items[$a['id']] = [ // THIS CHANGED.
"id" => $a['id'],
"itemid" => $a['itemid'],
...
Now, with any further result, you can easily check, if the id is already in your array:
if (isset($items[$a['id']])) {
// We had this id before, just add files/tags to it.
// Check first, if files subarray exists, if not: create it.
if (!isset($items[$a['id']]['files'])) {
$items[$a['id']]['files'] = array();
}
$items[$a['id']]['files'][] = array(...); // add the new file.
// Repeat for tags.
}
If your result could return the same file more than once for an id, you can check if the filename is already in there by using a search function:
$filename = $a['filename'];
if (!searchFilename($filename, $items[$a['id']]['files']) {
// Filename is not in there, yet.
// Add file info.
}
function searchFilename($id, $array) {
foreach ($array as $key => $val) {
if ($val['filename'] === $id) {
return true;
}
}
return false;
}
Same applies to tags in a similar way.
In the end, if you do not want the ids for index of $items, just call:
$items = array_values($items);
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;
}
I have this multidimensional array which I'll name "original":
$original=
array
0 =>
array
'animal' => 'cats'
'quantity' => 1
1 =>
array
'animal' => 'dogs'
'quantity' => '1'
2 =>
array
'animal' => 'cats'
'quantity' => '3'
However, I want to merge internal arrays with the same animal to produce this new array (with quantities combined):
$new=
array
0 =>
array
'animal' => 'cats'
'quantity' => 4
1 =>
array
'animal' => 'dogs'
'quantity' => '1'
I understand that there are similar questions on stackoverflow, but not similar enough for me to be able to figure out how to use the feedback those questions have gotted to apply to this specific example. Yes, I know I probably look stupid to a lot of you, but please remember that there was a time when you too didn't know crap about working with arrays :)
I've tried the following code, but get Fatal error: Unsupported operand types (Referring to line 11). And if I got that error to go away, I'm not sure if this code would even produce what I'm trying to achieve.
$new = array();
foreach($original as $entity){
if(!isset($new[$entity["animal"]])){
$new[$entity["animal"]] = array(
"animal" => $entity["animal"],
"quantity" => 0,
);
}
$new[$entity["animal"]] += $entity["quantity"];
}
So, I don't know what I'm doing and I could really use some help from the experts.
To try to give a super clear question, here goes... What changes do I need to make to the code so that it will take $original and turn it into $new? If the code I provided is totally wrong, could you provide an alternative example that would do the trick? Also, the only language I am familiar with is PHP, so please provide an example using only PHP.
Thank you
You're very close.
$new[$entity["animal"]] += $entity["quantity"];
needs to be
$new[$entity["animal"]]['quantity'] += $entity["quantity"];
In your if ( !isset [...] ) line, you're setting $new[$entity['animal']] to an array, so you need to access the 'quantity' field of that array before trying to add the new quantity value to it.
One of the reasons why your code is not working is that you're using the animal name as the array index, not the integer index which is used in your desired output.
Try this:
$new = array(); // Desired output
$map = array(); // Map animal names to index in $new
$idx = 0; // What is the next index we can use
foreach ($original as $entity) {
$animal = $entity['animal'];
// If we haven't saved the animal yet, put it in the $map and $new array
if(!isset($map[$animal])) {
$map[$animal] = $idx++;
$new[$map[$animal]] = $entity;
}
else {
$new[$map[$animal]]['quantity'] += $entity['quantity'];
}
}
This works:
$new = array();
$seen = array();
foreach($original as $entity) {
// If this is the first time we're encountering the animal
if (!in_array($entity['animal'], $seen)) {
$new[] = $entity;
$seen[] = $entity['animal'];
// Otherwise, if this animal is already in the new array...
} else {
// Find the index of the animal in the new array...
foreach($new as $index => $new_entity) {
if ($new_entity['animal'] == $entity['animal']) {
// Add to the quantity
$new[$index]['quantity'] += $entity['quantity'];
}
}
}
}
Your example was using the animal name as the index, yet the actual index is just an integer.
However, I think the resulting array would be easier to use and easier to read if it was formatting like this instead:
array('cats' => 4, 'dogs' => 1)
That would require different but simpler code than above... but, it wouldn't be a direct response to your question.
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.
First i am a beginner in programming in general, i am trying to create a program for using gps locations from Lightroom on a map in googlemaps.
When i use the print the strings below ti the screen i see 5 different value's, this is also what i want, but...
I want to create also 5 different markers on the map this is done by the addMarkerByCoords Function but how can i use the 5 value per strings in the function ?
I have tried array, foreach but i cannot getting to work. The not working part can and probably will be my fault. LOL
print_r ("$Loncoord");
print_r ("$Latcoord");
print_r ("$gui");
//$map->formatOutput = true;
$map->addMarkerByCoords("$Loncoord","$Latcoord","$gui",'<b>Old Chicago</b>');
Can somebody give me a hint ?
To: Jonathan Sampson:
outputs print_r :-5.68166666667, +24.6513888889,IMG_3308,index.html,Landschap
To: Anti Veeranna I removed the " marks (and the program still works), but can you explain why this is better ?
And to the others Thank you very very much for the effort,work and really quick responses.
Assuming that this is PHP, you could us an array of arrays, and then loop.
Something like this:
$items = array(
array(
'long' => 12.34567,
'lat' => 34.56789,
'gui' => '????',
'location' => 'old chicago'
),
...
array(
'long' => 12.34567,
'lat' => 34.56789,
'gui' => '????',
'location' => 'old chicago 5'
)
);
foreach ($items as &$item) {
$map->addMarkerByCoords(
$item['long'],
$item['lat'],
$item['gui'],
$item['location']
);
}
unset($item);
$map->addMarkerByCoords(Array($Loncoord, $Latcoord, $gui, '<b>Old Chicago</b>));
??