I have the following select box with CakePHP.
//Users/settings.ctp
$options = array('NYC', 'LA');
echo $this->Form->create('Location');
echo $this->Form->input('Location', array('type' => 'select', 'options' => $options));
echo $this->Form->end(__('Submit'));
In the UsersController I have
public function settings($id = null){
if ($this->request->is('post')) {
$location = $this->request->data;
//$location = $location['Location']['Location'][0];
$this->Session->setFlash( __(print_r($location))); //displays '1' in the alert no matter the selection
}
}
When I use print_r on the raw data it shows the following.
Array ( [Location] => Array ( [Location] => 0 ) )
So I have two problems
The index of the item is being selected, not the item itself
The setFlash window always displays '1'. I need to do some string manipulation after I get the listbox working and it's nice to see the output.
update - I went into /Cake/View/Helper/FormHelper.php and did some digging
I did a print_r on the following line
$attributes = $this->_initInputField($fieldName, array_merge(
(array)$attributes, array('secure' => self::SECURE_SKIP)
));
Which resulted in
Array ( [value] => 0 [class] => [name] => data[Users][Location] [id] => UsersLocation )
The value being passed is 0, I don't see the location anywhere
You're creating a form for the "Location" model, but you need to specify which field the select is for:
echo $this->Form->input('Location.location', array('type' => 'select', 'options' => $options));
Or, if it's not actually for the "Location" model, then either replace the "Location" in the form create() with the correct model, or use null if it's not a model at all:
$this->Form->create(null);
I figured it out and decided someone else might find it useful so.
The array needed to be formatted as
$options = array(
'value' => 'Display');
So, I used
$options = array(
'NYC' => 'NYC',
'LA' => 'LA');
Related
I'm using Codeigniter 3 and the table library in order to display some data in the following format;
+---------------+---------------------+
| id | 102 |
+---------------+---------------------+
| First Name | Ross |
+---------------+---------------------+
| Last Name | Bing |
+---------------+---------------------+
| Title | Doctor |
+---------------+---------------------+
| Timestamp | 2019-01-18 10:17:05 |
+---------------+---------------------+
| Member Number | |
+---------------+---------------------+
A vardump of $tableData is;
Array
(
[0] => Array
(
[id] => 102
[firstname] => Ross
[lastname] => Bing
[title] => Doctor
[timestamp] => 2019-01-18 10:17:05
[member_no] =>
)
)
The PHP code I use to generate the HTML table is;
$tableData = $this->My_model->getData();
$heading = array(
'id' => 'ID',
'firstname' => 'First Name',
'lastname' => 'Last Name',
'title' => 'Title',
'timestamp' => 'Date Submitted',
'member_no' => 'Member Number'
);
$fields = array_keys($tableData[0]);
$rows = array();
foreach($fields as $key => $field) {
$rows[$key][0] = array(
'data' => '<strong>' . $heading[$field] . '</strong>'
);
foreach($tableData as $key2 => $item) {
$rows[$key][$key2 + 1] = $item[$field];
}
}
foreach($rows as $row) {
$this->table->add_row($row);
}
The above code works fine, however if a row is empty (see member_no above) i'd like to do one of two things (whichever is easiest);
hide the table row completely
display not available in the table cell
How can I achieve this?
I would do something like this:
$tableData = array (
0 =>
array (
'id' => 102,
'lastname' => 'Bing',
'title' => 'Doctor',
'timestamp' => '2019-01-1810:17:05',
'member_no' => null,
'firstname' => 'Ross', //intentionally moved to show ordering
'foobar' => 'blah' //added for example, this will be removed by array_intersect_key
),
);
$heading = array(
'id' => '<strong>ID</strong>',
'firstname' => '<strong>First Name</strong>',
'lastname' => '<strong>Last Name</strong>',
'title' => '<strong>Title</strong>',
'timestamp' => '<strong>Date Submitted</strong>',
'member_no' => '<strong>Member Number</strong>'
);
//create a default array
//this takes the keys from $heading, and makes an array with all the values as 'not available'
// ['id' => 'not available','lastname' => 'not available', ... ]
$default = array_fill_keys(array_keys($heading), 'not available');
$rows = [];
foreach($tableData as $key => $row) {
//remove all elements with strlen of 0 (in this case 'member_no')
$row = array_filter($row, function($item){return strlen($item);});
//removes 'foobar' or anything that has a key not in $heading
$row = array_intersect_key($row, $heading);
//combine $default and $data (empty items in $row are filled in from default)
//such as items removed by array_filter above
//the key order will match $default, which matches $headings
$row = array_merge($default, $row);
$rows[] = $row;
}
foreach($heading as $key=>$value) {
print_r(array_merge([['data'=>$value]], array_column($rows, $key)));
}
Output
Array
(
[0] => Array
(
[data] => <strong>ID</strong>
)
[1] => 102
//[2] => 108
//...
)
....
Sandbox
I kept these separate so it would be a bit easier to read, but there is no reason you cannot do it this way.
//...
$default = array_fill_keys(array_keys($heading), 'not available');
foreach($tableData as $key => $row) $rows[] = array_merge($default, array_intersect_key(array_filter($row, function($item){return strlen($item);}), $heading));
foreach($heading as $key=>$value) print_r(array_merge([['data'=>$value]],array_column($rows, $key)));
Sandbox
I had to guess a bit on what the end result was, so I ran your original code and it gave me this:
Array
(
[0] => Array
(
[data] => <strong>ID</strong>
)
[1] => 102
//[2] => 108
//...
)
....
In my code you can replace the print_r with this call $this->table->add_row([..array data..]);. Where array data is the stuff in the print_r call. You could make this a variable, but what's the point if its only used here. That eliminates a few of those loops (see below) and A few other advantages:
key order of $headings is preserved, elements appear where they do in the $headings array, regardless of where they are in $tableData. This allows easy re-ording of the data, too, for example: you could even map this to a dynamic array, I do this in CSV files, which allows users to change the order of the headers and columns. They can even rename the headers, because the way the key => value pairing works my_key => their_key...
Data missing from $tableData is defaulted to not available pulled in from $default, in theory you could map this manually to different things. For example: you could default timestamp to the current time by doing $default['timestamp'] = date('Y-m-d H:i:s'); right after creating it with array_fill_keys.
Extra data in $tableData not defined in $headings is removed. Which is good for forward compatibility.
And it's A bit easier to make sense of (once you know how it works) because there are less "transient" variables and less fiddling with the keys ect...
Basically what I am doing is giving control over to the $headings array, in your original code. You do this somewhat by looping over the keys (fields), but this complicates things later like this $rows[$key][$key2 + 1]. It leaves you open to undefined array index issues, if the data changes at a later time, such as adding a new field to the DB.
The order of the output is dependent on the data in $tableData which is less intuitive (and less useful) then if it depends on $headings.
Here is an example of these issues with the original code:
//for example if your data changes to this and run your original code
$tableData = array (
0 =>
array (
'id' => 102,
'lastname' => 'Bing',
'title' => 'Doctor',
'timestamp' => '2019-01-1810:17:05',
'member_no' => null,
'firstname' => 'Ross', //intentionally moved to show ordering
'foo' => 'bar' //added this undefined in $headings
),
);
You'll get this notice and also find the last 2 elements are:
<br />
<b>Notice</b>: Undefined index: foo in <b>[...][...]</b> on line <b>30</b><br />
//... all other elements ...
//in orignal: displayed in the order of $tableData
//in my code: order is based on $headings, so this would be moved to the correct location
Array(
[0] => Array (
[data] => <strong>First Name</strong>
)
[1] => Ross
)
//in orignal: $headings['foo'] is not defined so we have no label here
//in my code: this element is removed and doesn't get rendered
Array(
[0] => Array(
[data] => <strong></strong>
)
[1] => bar
)
Sandbox (Orignal Code)
While these things may never cause an issue, it highlights my point about basing the output off of $headings and not $tableData. Things tend to change, and this way if you add/remove a field from this data, you wont have to worry about it breaking this page etc...
The combination of array_fill_keys, array_intersect_key and array_merge can be used to map the headers (As I shown above) of one array to another. You can use array_combine($headings, $row) to physically swap them and you would get something like this:
[
[
'<strong>ID</strong>' => 102,
'<strong>First Name</strong>' => 'Ross',
//...
],
[...]
]
It works great for CSV files (which is what I figured it out on) and anything else you need to remap the keys for.
Anyway, hope it helps you!
I have a two collections one of all the people I am following and another of what they have been posting on social networking sites like Twitter and Facebook.
The following collection has a subarray of the _id of the feed collection of each user which each status has the word owner and that has the ObjectId that the owner which is the same as the following key. Here is an example.
'_id' => new MongoId("REMOVED"),
'following' =>
array (
'0' => 'ObjectId("53bf464ee7fda8780c8b4568")',
'1' => 'ObjectId("53b00ab5e7fda8304b8b4567")',
),
'owner' => new MongoId("53b9ea3ae7fda8863c8b4123"),
and in the feed you will see that the following.0 status below
array (
'_id' => new MongoId("REMOVED"),
'owner' => new MongoId("53bf464ee7fda8780c8b4568"),
'status' => ' love this video - Pedigree Shelter dogs http://youtube.com/watch?v=5v5Ui8HUuN8',
'timestamp' => new MongoDate(1405044327, 565000),
)
While I can loop through one by one, I can't for some reason do an $or search. I am not quite understanding how I loop through the following array and add it to the search query before I ran the query.
collection = static::db()->feed;
$where=array( '$or' => array(array('owner' => new MongoId($following.0)))));
$feed = $collection->find($where);
return $feed;
now I understand I will somehow have to loop the $where=array( '$or' => array(array('owner' => new MongoId($following.0))))); But I am just not 100% sure how to do this.
Update
As per the answer below I had to edit the array that was returned - now I have only got this working manually and can't seem to get the PHP script to do it.
Answer Returns
Array ( [owner] => Array ( [$in] => Array ( [0] => new MongoId("53bf464ee7fda8780c8b4568") [1] => new MongoId("53b00ab5e7fda8304b8b4567") ) ) )
Correct:
Array ( "owner" => Array ( '$in' => Array ( "0" => new MongoId("53bf464ee7fda8780c8b4568"), "1" => new MongoId("53b00ab5e7fda8304b8b4567") ) ) )
I am not sure how else to get this to work.
current PHP
$collection = static::db()->following;
$following = $collection->findOne(array ('owner' => new MongoId($_SESSION['user_information'][0]['_id'])));
$follow = $following['following'];
$collection = static::db()->feed;
$where=array("owner" => array( '$in' =>$follow));
print_r($where);
$feed = $collection->find($where);
print_r($feed);
return $feed;
I have fixed a small issue with the collection and now the return array shows
Array ( [owner] => Array ( [$in] => Array ( [0] => MongoId Object ( [$id] => 53bf464ee7fda8780c8b4568 ) [1] => MongoId Object ( [$id] => 53b00ab5e7fda8304b8b4567 ) ) ) )
However, I still can't get it to return the feed like this one:
array (
'_id' => new MongoId("53bf4667e7fda8700e8b4567"),
'owner' => new MongoId("53bf464ee7fda8780c8b4568"),
'status' => ' love this video - Pedigree Shelter dogs http://youtube.com/watch?v=5v5Ui8HUuN8',
'timestamp' => new MongoDate(1405044327, 565000),
)
I am presuming here that this is just a PHPism in the way things are displayed and that your following array is an actual array and not a hash/map, which would generally look like this in a JSON representation:
{
"following": [
ObjectId("53bf464ee7fda8780c8b4568"),
ObjectId("53b00ab5e7fda8304b8b4567"),
],
"owner": ObjectId("53b9ea3ae7fda8863c8b4123"),
}
In which case the "following" is already an actual array, and if you just want to .find() all the "feed" items for the people you are following, then you just pass that to the $in operator for your query selection:
$where = array( "owner" => array( '$in' => $following ) );
$feed = $collection->find($where);
return $feed;
The returned cursor will only contain results from the feed where the "owner" is present in your "following" array from the other collection item.
Watch this code:
$list = array(new MongoId(), new MongoId, new MongoId());
$doc = array( "owner" => array( '$in' => $list ));
echo json_encode( $doc, JSON_PRETTY_PRINT );
Despite how this serializes for JSON by this method the equivalent JSON is:
{
"owner": {
"$in": [
ObjectId("53bf8157c8b5e635068b4567"),
ObjectId("53bf8157c8b5e635068b4568"),
ObjectId("53bf8157c8b5e635068b4569")
]
}
}
That is how the BSON will serialize and is the correct query.
(Answer added on behalf the question author to move it to the answer space).
The issue was fixed when I used the following:
var_dump(iterator_to_array($feed));
After hours of searching on this forum and google no answer worked.
I have tried more types of array like array of arrays, and simple key => value array.
Also i have tried code for: expanded true, and multiple true at http://symfony.com/doc/current/reference/forms/types/choice.html
I am getting error " Expected an array. 500 Internal Server Error - TransformationFailedException " When i turn on "multiple" I have tried it with array of arrays and key pair array.
What i want is to do and what its doing.
I select videos from database
make conversion
$video = $this->entityManager->getRepository("CMMSHomeBundle:Video")->findAll();
if (!$video)
{
return '0';
}
$video_arr = array();
for ($i = 0; $i < count($video); $i++)
{
//$video_arr[$i]["id"] = $video[$i]->getId();
//$video_arr[$i]["videoid"] = $video[$i]->getVideoid();
$video_arr[$video[$i]->getId()] = $video[$i]->getVideoid();
}
return $video_arr;
print_f if array is correct: Array ( [1] => X-77txuiVXs [5] => uelHwf8o7_U ) OR Array ( [0] => Array ( [id] => 1 [videoid] => X-77txuiVXs ) [1] => Array ( [id] => 5 [videoid] => uelHwf8o7_U ) )
Do the magick
$form = $this->createFormBuilder($product);
$form->add('name', 'text');
$form->add('short', 'textarea');
$form->add("description", "textarea");
$form->add("price", "text");
$form->add("images", "text");
$form->add('videos', 'choice', array(
'choices' => $videos,
'expanded' => true,
'multiple' => true,
)
);
$form->add("files", "text");
$form->add("size", "text");
$form->add("weight", "text");
$form->add("category", "text");
$form->add("active", "text");
$form->add('save', 'submit');
$form->getForm();
$form is chained, i unchained it to get correct formating by this soverflow editor.
It wont work :-( As soon i turn on multiple
Thank you
we're often dealing with a code that looks like the following:
return $this->find(
'all', [
'fields' => ['DISTINCT Tag.tag', 'COUNT(Tag.tag) as count'],
'group' => ['Tag.tag'],
'order' => ['count' => 'DESC']
]);
This query leads us to the following output:
[0] => Array
(
[Tag] => Array
(
[tag] => walls.io
)
[0] => Array
(
[count] => 15
)
)
As you can see, the query returns the results in a "somehow wrong" nesting. The "count" field is unfortunately put into a pseudo [0]-array.
IIRC, CakePHP uses internally a syntax like Tag__field for correctly nesting virtual fields.
When changing the code to the Model__-syntax, the problem stays the same:
return $this->find(
'all', [
'fields' => ['DISTINCT Tag.tag', 'COUNT(Tag.tag) as Tag__count'],
'group' => ['Tag.tag'],
'order' => ['COUNT(Tag.tag)' => 'DESC']
]);
Output:
[0] => Array
(
[Tag] => Array
(
[tag] => walls.io
)
[0] => Array
(
[Tag__count] => 15
)
)
Workaround 1: array_map
CakePHP pros: Is there a better/more elegant solution than manually mapping the array after the select statement?
$tags = array_map(function($tag) {
$tag['Tag']['count'] = $tag[0]['count'];
unset($tag[0]);
return $tag;
}, $tags);
Workaround 2: virtual field
As described above, the usage of a virtual field might solve this problem:
$this->virtualFields = ['count' => 'COUNT(Tag.Tag)'];
return $this->find(
'all', [
'group' => ['Tag.tag'],
'order' => [$this->getVirtualField('count') => 'DESC']
]);
Unfortunately, with this solution, it's not possible to specify ANY fields at all. only by completely leaving the "fields"-key, the nesting of the array works as expected. when selecting $fields = ['Tag.tag', $this->getVirtualField('count')] the nesting is wrong again.
CakePHP pros: Do you know a method, where the nesting is done right, even if you specify your own fields?
Looking at the CakePHP code, such a method does not exist.
Have a look at the file lib/Cake/Model/Datasource/Database/Mysql.php.
Find the method called: Mysql::resultSet( $results ); (around line 240).
That method maps the result to an array. To determine if a column is part of a table or not it uses PDOStatement::getColumnMeta(). For your "virtual column" that method will return an empty table and so the CakePHP code will put it separately, see the else branch
$this->map[$index++] = array(0, $column['name'], $type);
In order to avoid that else branch you would have to use Virtual fields, but then you run into the other problems that you have noticed.
So you are left with the array_map solution or you could try to overload that Mysql class and add your custom logic at how to identify where a column fits.
is it possible to get the new/updated _id after the query?
example code:
$key = array( 'something' => 'unique' );
$data = array( '$inc' => array( 'someint' => 1 ) );
$mongodb->db->collection->update( $key, $data, array( 'upsert' => true ) );
$key is not holding the new/old _id object and i assume that $data will not either because its just an instruction.
Yes -- It is possible using a single query.
MongoDB includes a findAndModify command that can atomically modify a document and return it (by default it actually returns the document before it's been modified).
The PHP drivers don't include a convenient method for this on the collection class (yet -- check out this bug), but it can still be used (note that my PHP is terrible, so I may very well have made a syntax error in the following snippet):
$key = array( 'something' => 'unique' );
$data = array( '$inc' => array( 'someint' => 1 ) );
$result = $mongodb->db->command( array(
'findAndModify' => 'collection',
'query' => $key,
'update' => $data,
'new' => true, # To get back the document after the upsert
'upsert' => true,
'fields' => array( '_id' => 1 ) # Only return _id field
) );
$id = $result['value']['_id'];
Just in case someone stumbles across this question like I did, Mongo will actually modify the input array when you call MongoCollection->save(); - appending the id to the end.
So, if you call:
$test = array('test'=>'testing');
mongocollection->save($test);
echo $test['_id'];
You will have the mongo id for that object.
I ran into this issue and worked around it by querying back the _id after the upsert. I thought I'd add some of my findings in case they're useful to anyone who comes here searching for info.
When the upsert results in a new document being created in the collection, the returned object contains the _id (here's a print_r of an example):
Array
(
[updatedExisting] => 0
[upserted] => MongoId Object
(
[$id] => 506dc50614c11c6ebdbc39bc
)
[n] => 1
[connectionId] => 275
[fsyncFiles] => 7
[err] =>
[ok] => 1
)
You can get the _id from this:
$id = (string)$obj['upserted'];
However, if the upsert resulted in an existing document being updated then the returned object does not contain _id.
Give this a shot :
function save($data, $id = null) {
$mongo_id = new MongoId($id);
$criteria = array('_id' => $mongo_id);
// Wrap a '$set' around the passed data array for convenience
$update = array('$set' => $data);
$collection->update($criteria, $update, array('upsert' => true));
}
So lets say the passed $id is null, a fresh MongoId is created, otherwise it just converts the existing $id to a MongoId object.
Hope this helps :D
The update method returns an array with the ID of the document UPSERTED:
Array
(
[ok] => 1
[nModified] => 0
[n] => 1
[err] =>
[errmsg] =>
[upserted] => MongoId Object
(
[$id] => 5511da18c8318aa1701881dd
)
[updatedExisting] =>
)
You can also set fsync to true in an update/upsert, to get the _id returned to the object that has been passed to the update.
$save = array ('test' => 'work');
$m->$collection->update(criteria, $save, array('fsync' => true, 'upsert' => true));
echo $save['_id']; //should have your _id of the obj just updated.