making a model from table mySQL with PDO - php

I am developing an application for fun, and I am purposefully NOT using any frameworks aside from mustache for PHP and JS, etc on either the client or server side of the modest application.
Each view has a table which holds all data used for each one. I wish to execute a query that selects each column in the table and makes it available to my mustache template.
I do NOT want to change methods in order to access any particular column at runtime. fetchAll gives me a ton of null fields in the data returned that I do not want while fetch() gives me a nice JSON structure containing all of the proper data aside from a column containing more than one row of data only shows for the first row/instance.
Here is my PHP method:
public function get_view($view_name , $out_type = "raw"){
// Col names may vary from view table to view table
$statement = $this->prep_sql("SELECT * FROM `$view_name`");
$statement->execute(array());
$view_data = $statement->fetch(); // I know fetchAll will work for arrays but how then will my mustache template bind to data returned from columns with more than one row?
return (strtoupper($out_type) === "JSON" ? json_encode($view_data) : $view_data);
}
What I want is to ignore any fields with null or empty as if they do not exist and form an array with columns in the database with multiple rows of data. I also realize that using fetch for single instances like a page title and fetchAll would work for multiple rows however I want to eliminate this.
In order for the templates to bind correctly the data output must look similar to:
{
module_name: 'main',
module_title: 'Main',
module_images: ['http://...', 'http://...', 'http://...'],
module_scripts: ['http://...','http://...','http://...',]
}
Instead with fetchAll I get
[{
module_name: 'main',
module_title: 'Main',
module_images: 'http://...', // 1
module_scripts: 'http://...' // 1
}, {
module_name: null,
module_title: null,
module_images: 'http://..', // 2
module_scripts: 'http://..' // 2
}];
I am aware SELECT * is not a traditional way to select all of your view data however the number of cols in each view table will be less than 100 max and no tables besides view tables will be accessed using a wildcard select. That said along with the dynamic col names from view to view mean ALOT less code to write. I hope that this doesnt offend anybody :)
Thank you all kindly for the help.

Is this what you are after??
CREATE TABLE view_data_main (
module_name VARCHAR(30),
module_title VARCHAR(30),
module_images VARCHAR(30),
module_scripts VARCHAR(30)
)
INSERT INTO `view_data_main` (module_name,module_title,module_images,module_scripts) VALUES
('welcome','main','http://www.test.com','http://www.test2.com'),
(NULL,NULL,'http://www.test.com','http://www.test2.com'),
(NULL,NULL,'http://www.test.com','http://www.test2.com')
SELECT module_name,module_title,GROUP_CONCAT(module_images),GROUP_CONCAT(module_scripts)
FROM `view_data_main`
WHERE module_images IS NOT NULL AND module_scripts IS NOT NULL
GROUP BY CONCAT(module_images,',',module_scripts)
Having re-read the question I don't think the subquery is necessary unless I'm missing something?

Related

Postgresql returned timestamp format differes from test and prod db?

I have a column called start_date in table X.
In my php project, when I use a simple select * from table x where id = 1 to retrieve a row from table X in my test DB, the following is returned:
{"id":1,"start_date":"2017-12-05 18:56:07"}
Now when I do the same in my production database the following is returned:
{"id":1,"start_date":"05\/12\/2017 18:56:07"
Both my databases have the same datestyle which I have set to ISO,DMY using
ALTER DATABASE xx SET datestyle TO ISO, DMY;
Why if both of these settings are the same and my production db is a restore of my test db, are these two queries returning different formats?
Edit: I'm using codeigniters query builder in my php project to retrieve the values from the db. I then view the value of the direct result using var_dump. Here is my php codeigniter method:
function get_last($id) {
$this->db->where ( "id", $id);
return $this->db->get ('table_x')->row_array ();
}
If I replace the function with the following it works, but I would still like to know why the top is returning two different results.
function get_last($id) {
$this->db->select("id, to_char(dataman, 'DD/MM/YYYY') as start_date");
$this->db->where ( "id", $id);
return $this->db->get ('table_x')->row_array ();
}
This also makes me believe even more that postgregsql is the reason the format is coming back different since by directly defining the format works.

Yii active record relation limit to one record

I am using PHP Yii framework's Active Records to model a relation between two tables. The join involves a column and a literal, and could match 2+ rows but must be limited to only ever return 1 row.
I'm using Yii version 1.1.13, and MySQL 5.1.something.
My problem isn't the SQL, but how to configure the Yii model classes to work in all cases. I can get the classes to work sometimes (simple eager loading) but not always (never for lazy loading).
First I will describe the database. Then the goal. Then I will include examples of code I've tried and why it failed.
Sorry for the length, this is complex and examples are necessary.
The database:
TABLE sites
columns:
id INT
name VARCHAR
type VARCHAR
rows:
id name type
-- ------- -----
1 Site A foo
2 Site B bar
3 Site C bar
TABLE field_options
columns:
id INT
field VARCHAR
option_value VARCHAR
option_label VARCHAR
rows:
id field option_value option_label
-- ----------- ------------- -------------
1 sites.type foo Foo Style Site
2 sites.type bar Bar-Like Site
3 sites.type bar Bar Site
So sites has an informal a reference to field_options where:
field_options.field = 'sites.type' and
field_options.option_value = sites.type
The goal:
The goal is for sites to look up the relevant field_options.option_label to go with its type value. If there happens to be more than one matching row, pick only one (any one, doesn't matter which).
Using SQL this is easy, I can do it 2 ways:
I can join using a subquery:
SELECT
sites.id,
f1.option_label AS type_label
FROM sites
LEFT JOIN field_options AS f1 ON f1.id = (
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
)
Or I can use a subquery as a column reference in the select clause:
SELECT
sites.id,
(
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
) AS type_label
FROM sites
Either way works great. So how do I model this in Yii??
What I've tried so far:
1. Use "on" array key in relation
I can get a simple eager lookup to work with this code:
class Sites extends CActiveRecord
{
...
public function relations()
{
return array(
'type_option' => array(
self::BELONGS_TO,
'FieldOptions', // that's the class for field_options
'', // no normal foreign key
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = t.type LIMIT 1)",
),
);
}
}
This works when I load a set of Sites objects and force it to eager load type_label, e.g. Sites::model()->with('type_label')->findByPk(1).
It does not work if type_label is lazy-loaded.
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // ERROR: column t.type doesn't exist
2. Force eager loading always
Building on #1 above, I tried forcing Yii to always to eager loading, never lazy loading:
class Sites extends CActiveRecord
{
public function relations()
{
....
}
public function defaultScope()
{
return array(
'with' => array( 'type_option' ),
);
}
}
Now everything always works when I load Sites, but it's no good because there are other models (not pictured here) that have relations that point to Sites, and those result in errors:
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // works now
$other = OtherModel::model()->with('site_relation')->findByPk(1); // ERROR: column t.type doesn't exist, because 't' refers to OtherModel now
3. Make the reference to the base table somehow relative
If there was a way that I could refer to the base table, other than "t", that was guaranteed to point to the correct alias, that would work, e.g.
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = %%BASE_TABLE%%.type LIMIT 1)",
where %%BASE_TABLE%% always refers to the correct alias for table sites. But I know of no such token.
4. Add a true virtual database column
This way would be the best, if I could convince Yii that the table has an extra column, which should be loaded just like every other column, except the SQL is a subquery -- that would be awesome. But again, I don't see any way to mess with the column list, it's all done automatically.
So, after all that... does anyone have any ideas?
EDIT Mar 21/15: I just spent a long time investigating the possibility of subclassing parts of Yii to get the job done. No luck.
I tried creating a new type of relation based on BELONGS_TO (class CBelongsToRelation), to see if I could somehow add in context sensitivity so it could react differently depending on whether it was being lazy-loaded or not. But Yii isn't built that way. There is no place where I can hook in code during query buiding from inside a relation object. And there is also no way I can tell even what the base class is, relation objects have no link back to the parent model.
All of the code that assembles these queries for active records and their relations is locked up in a separate set of classes (CActiveFinder, CJoinQuery, etc.) that cannot be extended or replaced without replacing the entire AR system pretty much. So that's out.
I then tried to see if I can create "fake" database column entries that would actually be a subquery. Answer: no. I figured out how I could add additional columns to Yii's automatically generated schema data. But,
a) there's no way to define a column in such a way that it can be a derived value, Yii assumes it's a column name in way too many places for that; and
b) there also doesn't appear to be any way to avoid having it try to insert/update to those columns on save.
So it really is looking like Yii (1.x) just does not have any way to make this happen.
Limited solution provided by #eggyal in comments: #eggyal has a suggestion that will meet my needs. He suggests creating a MySQL view table to add extra columns for each label, using a subquery to look up the value. To allow editing, the view would have to be tied to a separate Yii class, so the downside is everywhere in my code I need to be aware of whether I'm loading a record for reading only (must use the view's class) or read/write (must use the base table's class, does not have the extra columns). That said, it is a workable solution for my particular case, maybe even the only solution -- although not an answer to this question as written, so I'm not going to put it in as an answer.
OK, after a lot of attempts, I have found a solution. Thanks to #eggyal for making me think about database views.
As a quick recap, my goal was:
link one Yii model (CActiveRecord) to another using a relation()
the table join is complex and could match more than one row
the relation must never join more than one row (i.e. LIMIT 1)
I got it to work by:
creating a view from the field_options base table, using SQL GROUP BY to eliminate duplicate rows
creating a separate Yii model (CActiveRecord class) for the view
using the new model/view for the relation(), not the original table
Even then there were some wrinkles (maybe a Yii bug?) I had to work around.
Here are all the details:
The SQL view:
CREATE VIEW field_options_distinct AS
SELECT
field,
option_value,
option_label
FROM
field_options
GROUP BY
field,
option_value
;
This view contains only the columns I care about, and only ever one row per field/option_value pair.
The Yii model class:
class FieldOptionsDistinct extends CActiveRecord
{
public function tableName()
{
return 'field_options_distinct'; // the view
}
/*
I found I needed the following to override Yii's default table data.
The view doesn't have a primary key, and that confused Yii's AR finding system
and resulted in a PHP "invalid foreach()" error.
So the code below works around it by diving into the Yii table metadata object
and manually setting the primary key column list.
*/
private $bMetaDataSet = FALSE;
public function getMetaData()
{
$oMetaData = parent::getMetaData();
if (!$this->bMetaDataSet) {
$oMetaData->tableSchema->primaryKey = array( 'field', 'option_value' );
$this->bMetaDataSet = TRUE;
}
return $oMetaData;
}
}
The Yii relation():
class Sites extends CActiveRecord
{
// ...
public function relations()
{
return (
'type_option' => array(
self::BELONGS_TO,
'FieldOptionsDistinct',
array(
'type' => 'option_value',
),
'on' => "type_option.field = 'sites.type'",
),
);
}
}
And all that does the trick. Easy, right?!?

Kohana add function return province name

I'm back again for another question, i'm trying and trying. But i can't get it fixed.
This is my issue;
I have a database table, with a ProvinceID this can alter from 1 to 12. But it's an ID of the province. The provinces are stored with the value pName in the table Provinces
I have the following code to alter the table, and join it with the preferences table
$veiling = ORM::factory('veilingen')
->select('veilingvoorkeur.*')
->join('veilingvoorkeur', 'LEFT')
->on('veilingen.id', '=', 'veilingvoorkeur.vId')
->find_all();
$this->template->content = View::factory('veiling/veilingen')
->bind('veiling', $veiling);
It displays correctly, in the view i have;
echo '<div class="row">';
foreach($veiling as $ve)
{
echo $ve->provincie;
}
?>
</div>
it displays the provincie id; but i want to add a function to it; So it will be transformed to a province name. Normally i would create a functions.php file with a function getProvince($provinceId)
Do a mysql query to grab the pName value from Provinces and that is the job. But i'm new to kohana. Is there an option to turn the province id to province['pName'] during the ORM selection part, or do i have to search for another solution. Which i can't find :(
So. Please help me on the road again.
Thanx in advance.
Kind regards,
Kevin
Edit: 1:08
I've tried something, and it worked. I used ORM in the view file, for adding a function;
function provincie($id){
$provincie = ORM::factory('provincies', $id);
return $provincie->pName;
}
But i'm not glad with this way of solution, is there any other way? Or am i have to use it this way?
Check out Kohana's ORM relationships. If you use this, you could access the province name by simply calling:
$ve->provincie->name;
The relationship definition could look something like:
protected $_belongs_to = array(
'provincie' => array(
'model' => 'Provincie',
'foreign_key' => 'provincie_id'
),
);
Note that if you have a table column called provincie the relationship definition I give above will not work. You'd either have to change the table column to provincie_id or rename the relationship to e.g. provincie_model.
The output you describe when you do echo $ve->provincie; suggests that you store the ID in a column called provincie, thus the above applies to you.
I'd personally go for the first option as I prefer accessing IDs directly with an _id suffix and models without any suffix. But that's up to you.
Edit: If you use Kohanas ORM relationships you could even load the province with the initial query using with() e.g:
ORM::factory('veiling')->with('provincie')->find_all();
This could save you hundreds of extra queries.

How to create an object of "unknown" class?

I have a MySQL database and a table tobjects where each record has its id, parameter, value (something like XML) and one can say that this parameter column determines the "type" of an object.
The objects are used in some other tables, depending on their types, so each of them should be handled in specific way.
Because "handling" is somewhat common (I use the same function) I created a TObject class (not abstract but could be) from which I inherit other classes; this inheritance method is very useful and that's the very reason I use object oriented programming. For example TObject has retrieve() method that gets from db all the necessary data, not those in tobjects table but others too, which are type dependent, so I override it in some classes.
The problem I encountered is that when I create an object I do not know what class should it be. Of course, I can SELECT Parameter FROM tobjects WHERE id=$id, and then (with switch) create object of the proper class, and use its retrieve() method (each class retrieves different data, only those from tobjects are common) to get data from the db, that causes me to run query two times and some part of work outside the class, which works, but is not gentle.
The best solution would be if I can create a TObject and then, upon retrieving, change the class of the object to the one I need and it would be TObject's descendant, but I'm almost sure it's not possible.
Is my solution, that I run the first query just to select one field from tobjects only to determine object's class right? Or is there a trick to change object's class in runtime?
If understand what you are doing correctly, here is the way I would approach this:
Passing PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE to the first argument of PDOStatement::fetch() will return an object of class PDOStatement::fetchColumn(0) - in other words, it determines the class name to instantiate from the value of the first column of the result set.
To leverage this, you would JOIN tobjects ON targetTable.objectType = tobjects.id and select tobjects.Parameter as the first column in the result set. If the Parameter column already holds a 1:1 mapping of database object types to class names, this is all you need to do, however I'm not sure whether this is the case, and it probably shouldn't be, because it makes it more difficult to substitute another class at a later date.
To overcome this limitation, I suggest you create a temporary table when you first connect the database, which maps Parameter values to class names, which you can JOIN onto the query to obtain the target class name.
So the flow would go something like this:
// Set up the connection
$db = new PDO('mysql:yourDSNhere');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// Create a temp table to store the mapping
$db->query("
CREATE TEMPORARY TABLE `objectMappings` (
`Parameter` INT NOT NULL PRIMARY KEY,
`ClassName` VARCHAR(255)
) ENGINE=MEMORY
");
// A mapping of Parameter IDs to class names
$classMap = array(
1 => 'Class1',
2 => 'Class2',
3 => 'Class3',
// ...
);
// Build a query string and insert
$rows = array();
foreach ($classMap as $paramId => $className) {
// this data is hard-coded so it shouldn't need further sanitization
$rows[] = "($paramId, '$className')";
}
$db->query("
INSERT INTO `objectMappings`
(`Parameter`, `ClassName`)
VALUES
".implode(',
', $rows)."
");
// ...
// When you want to retrieve some data
$result = $db->query("
SELECT m.ClassName, t.*
FROM targetTable t
JOIN tobjects o ON t.objectType = o.id
JOIN objectMappings m ON o.Parameter = m.Parameter
WHERE t.someCol = 'some value'
");
while ($obj = $result->fetch(PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE)) {
// $obj now has the correct type, do stuff with it here
}

Symfony Criteria Join unexpected behaviour

i'm trying to do a complex query with Criteria in a Symfony project using Propel ORM.
the query i want to make is, in human words:
Select from the 'interface' table the registers that:
- 1 are associated with a process (with a link table)
- 2 have a name similat to $name
- 3 its destiny application's name is $apd (application accecible by foreign key)
- 4 its originapplication's name is $apo (application accecible by foreign key)
here the code i made, and not working:
$c = new Criteria();
$c->addJoin($linkPeer::CODIGO_INTERFASE,$intPeer::CODIGO_INTERFASE); //1
$c->add($linkPeer::CODIGO_PROCESONEGOCIO,$this->getCodigoProcesonegocio());//1
if($name){
$name = '%'.$name.'%'; //2
$c->add($intPeer::NOMBRE_INTERFASE,$name,Criteria::LIKE); //2
}
if($apd){
$apd = '%'.$apd.'%'; //3
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_DESTINO);//3
$c->add($appPeer::NOMBRE_APLICACION,$apd,Criteria::LIKE); //3
}
if($apo){
$apo = '%'.$apo.'%';//4
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_ORIGEN);//4
$c->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
}
After that i did a $c->toString() to see the SQL generated and i saw that when i send only an $apd value, the SQL is correct, when i send an $apo value too. But when i send both, only the $apo AND apears on the SQL.
I guess its because the $c->add(...) call is the same with a distinct parameter, but not sure at all. Is this the error? What is the best way to generate my query correctly?
Thank you very much for your time! :D
Yes, it's overriding the previous call as the Criteria object only stores one condition per field. The solution is to create 2 or more separate Criterion objects and mix them into the Criteria object:
//something like this
$cron1 = $criteria->getNewCriterion();
$cron1->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
$criteria->add($cron);
//and the same with the other criterion
However, it would be much easier to upgrade to Propel15+, where you work on the Query class level, and multiple restrictions on the same field don't override each other.
Hope this helps,
Daniel

Categories