I am working with a database which has all uppercase snakecase column names and when I fetch them with eloquent I do something like:
foreach($data as $key => $item){
$data[$key] = array_change_key_case($item);
}
This makes the keys ie the column names to lower case, but it soon becomes inefficient since I need to nested arrays too like so:
foreach($tasks as $key => $task){
foreach($task['users'] as $innerKey => $user){
$task['users'][$innerKey] = array_change_key_case($user);
}
$tasks[$key] = array_change_key_case($task);
}
And I can't change the database. Is there a way I can make eloquent give me back the column names in lower case?
You can transform column names at the driver level using PDO attributes. To do so, set your Laravel connection options (in app/config/database.php) like so:
return array(
'connections' => array(
'mysql' => array(
'options' => array(
PDO::ATTR_CASE => PDO::CASE_LOWER,
),
),
)
)
The default is PDO::CASE_NATURAL, which is why your code sees them as the database has them stored.
Update: If you are using MySQL, you might consider setting lower_case_table_names = 2, which tells the server:
If set to 2, table names are stored as given but compared in lowercase. This option also applies to database names and table aliases.
If override the getAttribute() method on your model you can transform the key before the call:
public function getAttribute($key)
{
$databaseColumn = implode('_', array_map('ucfirst', explode('_', $key)));
return parent::getAttribute($databaseColumn);
}
This will allow you to do $model->get_model_param and it will access $model->Get_Model_Param on the model.
Related
My current problem is that all data are being updated all at once when I use update_batch(), is there a way to update only the last data?
I am updating grades every semester that is why I need to update the latest grades in this semester. However whenever I update grades, the latest update will also update the previous grades in my semester that is why it is being overwritten. Hence, I only want to update the latest grades in this semester. I also used update_batch to update multiple records, am I wrong doing so?
Here is my controller:
$schoolYear = $csvData[0]['schoolYear'];
$semester = $csvData[0]['semester'];
$studentIDFromDB = $this->importSISAcademicInformationGetStudentIDFromDB($schoolYear, $semester);
foreach ($csvData as $key => $value) {
if(isset($studentIDFromDB[$value['studentID']])){
$updateFields[$key] = array(
'studentID' => $value['studentID'],
'programType' => $value['programType'],
'level' => $value['level'],
'sectionDescription' => $value['sectionDescription'],
'schoolYear' => $value['schoolYear'],
'semester' => $value['semester']
);
}
}
if(isset($updateFields))
$this->DBLogic->updateBatchRecordMultipleCondition('tbl_tt_academicinfo', $updateFields, 'studentID', array('schoolYear' => $schoolYear, 'semester' => $semester));
Here is my Model:
public function updateBatchRecordMultipleCondition($table, $fields, $criteria1, $criteriaN){
$this->db->where($criteriaN);
$this->db->update_batch($table, $fields, $criteria1);
}
In Codeigniter 3.x, the function batch_update(), as by documentation has its where clause set in the 3rd parameter of the function and not by a preceding where() clause.
without knowing anything else about your variables and data, most likely this will do it:
$this->db->update_batch($table, $fields, $criteriaN);
Anyway, make sure you follow these indications from doc:
update_batch($table[, $set = NULL[, $value = NULL[, $batch_size = 100]]])
Parameters:
$table (string) – Table name
$set (array) – Field name, or an associative array of field/value pairs
$value (string) – Field value, if $set is a single field
$batch_size (int) – Count of conditions to group in a single query
Returns:
Number of rows updated or FALSE on failure
hint: In case you cannot set the where clause in the update_batch() function, use the update() function - which allows you to set a preceding where() - within you foreach loop.
Alright so I have an insert query that I would like to run but the issue I am having is with getting object properties/values that I need to insert.
Say I have a query that looks like the one below.
$this->db->insert('tblitems_in', array(
'platform' => $item['Platform'],
'ram' => $item['RAM'],
'qty' => $item['qty'],
'rate' => number_format($item['rate'], 2, '.', ''),
'rel_id' => $insert_id,
'rel_type' => 'estimate',
'item_order' => $item['order'],
'unit' => $item['unit']
));
This works fine when the person chooses RAM on the webpage which sets the $item Objects property 'RAM' to the value that was picked. Now if they choose HardDrive, that properties name is now sent as 'HardDrive' with the value they chose. Is there a way that i Could replace the 'ram' and 'RAM' from the below example with a variable so I could change what the property name is that I would like to insert and insert into the corresponding db column?
EDIT:
I should have added that the options on the webpage are also dynamically created from a database so I do not know at the time of coding what the property names are. They could be RAM, HardDrive, Processor, maybe even Elephant. I was hoping I could use variables so that I could look at the DB used to create the webpage so that I know the property names and then dynamically add those names into the query.
EDIT:
Right now I am using the following code in order to get all the possible options that can be received from the webpage from a DB the webpages uses to create itself.
$plat_options = $this->db->get('tblplatform_options')->row()->name;
In the database right now it is only populated with names RAM and HardDrive to make things known for testing purposes. So this returns $plat_options = {RAM, HardDrive}. I now have to figure out how to test is $item has these(RAM and HardDrive) as properties and if $item does have them then add them into the query previously shown.
You can set an array of key => variable names, then loop over those values to see if they exist in the $item variable and, if so, add that value to the data to be inserted into the db:
//default array of data to insert
$data = [
'platform' => $item['Platform'],
'qty' => $item['qty'],
'rate' => number_format($item['rate'], 2, '.', ''),
'rel_id' => $insert_id,
'rel_type' => 'estimate',
'item_order' => $item['order'],
'unit' => $item['unit']
];
//Get column names from db
$plat_options = $this->db->get('tblplatform_options')->row()->name;
// $plat_options = [RAM, HardDrive]
//Check if $item[$name] exists. If it does, add that to the
// array of data to be inserted
foreach($plat_options as $key) {
if(array_key_exists($key, $item)) {
$data[$key] = $item[$key];
}
}
$this->db->insert('tblitems_in', $data);
edit
I'm not sure this will work (I don't understand the use case).
It is possible, using array_diff_key to get a list of array keys that exist in $item but not in $data. With this array of keys, you can add the missing keys.
I have altered my previous code to demonstrate this.
You could create the array one element at a time based on whatever field data you received. I used a switch statement, but it could be a simple if/then/else as well.
$data_array = array();
$data_array['platform'] = $item['Platform']
switch($item['Object'] {
case 'HardDrive':
$data_array['harddrive'] = $item['HardDrive'];
break;
case 'RAM':
$data_array['ram'] = $item['RAM'];
break;
}
$data_array['qty'] = $item['qty'];
$data_array['rate' = number_format($item['rate'], 2, '.', '');
$data_array['rel_id'] = $insert_id;
$data_array['rel_type' = 'estimate';
$data_array['item_order'] = $item['order'];
$data_array['unit'] = $item['unit'];
$this->db->insert('tblitems_in', $data_array);
I want to save log entries to my MySQL database from Zend Framework 2. I am using Zend\Log\Logger with a Zend\Log\Writer\Db writer. By supplying the writer with an array, one can choose which columns to save what data to (e.g. timestamp into a "log_date" column) and which data to save. Here is what I am doing:
$logger = new Zend\Log\Logger();
$mapping = array(
'timestamp' => 'timestamp_column',
'priority' => 'priority_column',
'message' => 'message_column',
'extra' => 'extra_column'
);
$logger->addWriter(new Zend\Log\Writer\Db($dbAdapter, 'table_name', $mapping));
$logger->err('some message', array('some extra information'));
The problem I am facing is that the array of column names and their values contain an incorrect column name for the "extra" column. Based on the array above, it should be inserting the value "some extra information" into the "extra_column" column. The problem is that the Zend\Log\Writer\Db class is using the letter "e" as the name of the extra column. This comes from the first letter of "extra_column" in my array above. For some reason, it is taking the first letter of "extra_column" and using it as the column name instead of the entire value.
I took a look at the source code. The mapEventIntoColumn method is being used to get the column names and values as an array. I copied in the relevant part of the method below.
// Example:
// $event = array('extra' => array(0 => 'some extra information'));
// $columnMap = array('extra' => 'extra_column');
// Return: array('e' => 'some extra information')
// Expected (without looking at the code below): array('extra_column' => 'some extra information')
protected function mapEventIntoColumn(array $event, array $columnMap = null) {
$data = array();
foreach ($event as $name => $value) {
if (is_array($value)) {
foreach ($value as $key => $subvalue) {
if (isset($columnMap[$name][$key])) {
$data[$columnMap[$name][$key]] = $subvalue;
}
}
}
}
return $data;
}
The $event parameter is an array containing the same keys as my $mapping array in my first code snippet and the values for the log message. The $columnMap parameter is the $mapping array from my first code snippet (array values are column names).
What actually seems to happen is that because I am passing in extra information as an array (this is required), the inner foreach loop is executed. Here, $key is 0 (the index) so it is actually doing like this: $columnMap['extra'][0]. This gives the letter "e" (the first letter in "extra_column"), which is used as the column name, where it should be the entire column name instead.
I tried to supply my own key in the extra array when calling the log method, but the same happens. The official documentation shows no examples of usage of the extra parameter. I want to insert information that can help me debug errors into my table, so I would like to use it.
Is this a bug or am I missing something? It seems really strange to me! I hope I explained it well enough - it is quite tricky!
Since Daniel M has not yet posted his comment as an answer, I will refer you to his comment which solved the problem.
I have 2 dates (create, update) that i want to merge in a new column, selecting the newest date... how can I do it?
Here is the array creation:
$this->Message= array(
'fields' => array('Message.id','Message.type','Message.createdate','Message.updatedate'),
'conditions' => $cond);
$messages = $this->Message->find('all', $conditionsMessage);
Now I need another field (lets call it NewDate) Message.NewDate that gets the newest date from Message.createdate and Message.updatedate, so i can call it after in a view using $messages[NewDate]
Help plz...
Thx!
UPDATE:
It's not hard to loop over the array with something like
foreach($messages as $k => $m){
if(strtotime($m['Message']['updatedate']) > strtotime($m['Message']['createdate'])){
$messages[$k]['Message']['NewDate'] = $m['Message']['updatedate'];
}else{
$messages[$k]['Message']['NewDate'] = $m['Message']['createdate'];
}
}
ORIGINAL ANSWER:
I would think that your Message.updatedate would always be the newest date, so you could just select that. But assuming that's not the case for some reason, you can create a virtual field in your model:
public $virtualFields = array(
'NewDate' => "IF(Message.updatedate > Message.createdate, Message.updatedate, Message.createdate)"
);
This uses the MySQL IF() function. If you're not using MySQL you'd have to figure out how to do something similar with your database.
http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html#function_if
I'd like to exclude results from a call to a Lithium model's find() method. I need to do this for models with both MongoDB and MySQL data sources, but in SQL I mean something like WHERE myfield NOT IN (1,2,3).
I'd like to just be able to pass a not clause in the conditions array like below, but that doesn't appear to be possible.
Item::all(array('conditions' => array('not' => array('myfield' => array(1,2,3))));
So my question is, is this possible in Lithium in a way that I've overlooked? And if not, what would be the most Lithium-ish way to implement it for my models?
Just to clarify, Lithium's MongoDB adapter supports most SQL comparison operators as a convenience, so for either Mongo or MySQL, you could simply write the query as follows:
Item::all(array('conditions' => array(
'myfield' => array('!=' => array(1,2,3))
)));
And it should give you the results you expect. For MySQL, the query should look something like:
SELECT * FROM items WHERE myfield NOT IN (1, 2, 3);
And in Mongo:
db.items.find({ myfield: { $nin: [1, 2, 3] }})
Merely filtering for MongoDB can easily be achieved like this:
Item::all(array('conditions' =>
array('myfield' => array(
'$nin' => array(1,2,3)
))
));
If this is something you do a lot you could even create a custom finder for it :
class MyModel extends \lithium\data\Model {
public static function __init()
{
parent::__init();
static::finder('notin', function($self, $params, $chain) {
// Take all array keys that are not option keys
$array = array_diff_key($params['options'],
array_fill_keys(array('conditions', 'fields','order','limit','page'),0));
// Clean up options leaving only what li3 expects
$params['options'] = array_diff_key($params['options'], $array);
$params['options']['conditions'] = array(
'myfield' => array(
'$nin' => $array
)
);
return $chain->next($self, $params, $chain);
});
}
}
And call it like this :
MyModel::notin(array(1,2,3));
In the same manner you could create a custom finder for MySQL sources.
As you probably can see this creates some issues if you pass something like array('fields'=>$array) as it would overwrite the option.
What happens is that ::notin() (finders in general) has a distinct behavior for the (array,null) signature. If that happens it thinks the first array is options and the finder took no arguments.
Using notin($array,array()) breaks the previous finder because the first argument ends up in $params['notin'] when the real second argument (options) is passed.
If you mix data sources on the fly here I would create a custom model that does not inherit \lithium\data\Model and have it delegate
to the different models and create the conditions based on the end models data source.
class MyFacadeModel {
public static function byNotIn($conditions, $source) {
return ($source == "mongodb")
? $source::find( $rewrittenConditions)
: $source::find( $rewrittenConditionsForMysql );
}
}
(Code might be slightly incorrect as its mostly taken from the top of my head)