I'm trying to use a select object to filter the results of a many to many rowset. This call works great:
$articles = $this->model->findArticlesViaArticlesUsers();
This however does not:
$articles = new Default_Model_Articles();
$articleSelect = $articles->select();
$articleSelect->where("status = 'published'")
->order("date_published DESC")
->limit(1);
$articles = $this->model->findArticlesViaArticlesUsers($articleSelect);
That throws the following error:
exception 'Zend_Db_Select_Exception'
with message 'You cannot define a
correlation name 'i' more than once'
I can't figure out how to successfully get "articles that have the status of 'published'" using the magic many-to-many relationship (nor findManyToManyRowset). I'm at the end of my rope and thinking of just writing the sql manually. Any ideas?
When defining the select statement, you must use the same object that you call findManyToManyRowset (or whatever magic function you use) on.
Ex:
$articles = new Default_Model_Articles();
$user = $articles->find($userId)->current();
$select = $user->select();
$select->where('status = ?', 'published');
$articles = $user->findArticlesViaArticlesUsers($select);
Notice the select statement and findArticlesViaArticlesUsers are both extending $user. Thats the key.
I think you've misunderstood how the relationships work.
See this manual page - you should call the magic method, findArticlesViaArticlesUsers, on a Row object. In this case, I think you want to find a User, and then call findArticles... on that.
Related
So... I've inherited this rather large project based on Zend Framework 1.12 and a feature that I'm trying to add involves a more complex database operation than what I'm used to doing with the project. I'm extremely new to Zend Framework, or MVC for that matter. I found an answer for Zend Framework 3 which would have been perfect but I have to make do with this version.
The function basically builds a Zend_Db_Select based on various parameters and the feature I'm trying to add will involve joining two different tables and checking if a specific combination exists in one or the other.
Here's what I have so far:
//SQL that I'm trying to do. Assume table1 and table2 are already joined.
//Ignore the imperfect syntax. I'm trying to get the concept across.
//SELECT * FROM (table1 joined to table2 by a common key)
//WHERE ( (table1.column1 = myParam1) AND (table1.column2 = myParam2) )
//OR WHERE ( (table2.column1 = myParam1) AND (table2.column2 = myParam2) )
public function buildSelect($params){
//Zend code starts here
//This one starts the Zend_Db_Select
$select = $this->select();
$table1Name = get_table_name_from_object($table1);
//lots of preexisting code here
//my code starts here.
$table2Name = get_table_name_from_object($table2);
$select->join($table2Name, "$table1Name.key = $table2Name.key", array('column1', 'column2', 'key');
//After I wrote this, I instantly realized why it won't work the way I intended it but putting it here to show what I tried at which point I got stuck.
$select->where("($table1Name.column1 = ?) OR ($table2Name.column1 = ?)",$params[1]);
$select->where( "($table1Name.column2 = ?) OR ($table2Name.column2 = ?)", $params[2]);
//more preexisting code below.
return $select
}
Obviously, if I tried this as is, the program will happily return results that include a combination of, say, an entry where param1 is in table1.column1 and param2 is in table2.column2.
I received some feedback from a friend and posting here for posterity.
They noticed my code already contains parentheses and recommended that I simply take advantage of orWhere() then write it like this:
$select->where("($tableName1.column1 = ?", $params[param1])
->where("$tableName1.column2 = ?)", $params[param2]);
$select->orWhere("($tableName1.column1 = ?",$params[param1])
->where("$tableName2.column2 = ?)",$params[param2]);
I don't know if 'arguments' is the right term. I want to get a model without everything and add the selects and wheres later.
$query = Pic::;
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
$query->get();
Pic:: or Pic don't work. Only DB::table('pics') does, but does not seem to return a collection.
Use Model::query();
$query = Pic::query();
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
$query->get();
i want to load models from helpers... im using eval to insert the modelname etc.
public function getMod($mName) {
// this works but need a dynamic one
$model = $this->users = new Application_Model_Users();
// so i did this:
$model = $this->_listsMod = eval ("new Application_Model_$mName();");
return $model;
and you can call it by: $this->_helper->getmod->getMod('Users')->myuserFunc();
but it doesnt work, its says Fatal error: Call to a member function myuserFunc() on a non-object
Updated answer:
Have you tried:
$modelName = 'Application_Model_'.$mName;
$model = $this->_lists = new $modelName ;
?
You should have a look at the Factory Pattern (which is effectively what you're doing here) eg here and here.
As you probably know, its best to avoid using eval if possible. Also, it seems a bit odd needing to load Models like this - can you tell us why you need to load them like this?
Note: I've updated this answer following comments below, to make it quicker for future readers of the question / answer.
For example:
I need to retrieve a set of male User's IDs, First Names, and Last Names, but nothing else.
So I have a function in UserMapper called fetchAllMaleUsers() that returns a set of User entities.
i.e:
public function fetchAllMaleUsers() {
$select = $this->getDbTable()
->select()
->from($this->getDbTable(),
array('ID', 'FirstName', 'LastName'))
->where('Gender = ?', 'M');
$resultSet = $this->getDbTable()->fetchAll($select);
$users = array();
foreach ($resultSet as $row) {
$user = new Application_Model_User();
$user->setId($row->ID)
->setFirstName($row->FirstName)
->setLastName($row->LastName);
$users[] = $user;
}
return $users;
}
Does this function belong in the mapper layer?
Is it ok to only set the Id, Firstname, and LastName of each User entity?
1) Perfectly fine for the mapper/Gateway or however you call it.
2) You can do but i highly disencourage this. Why? Later on in your application you can't tell from where you did get the model. So you'd have to check if a value is set in your model each time you're not sure if it is set or you'd need some autoloading stuff for missing values (which is as worse as missing stuff). Another reason is that you can't reuse the function for other porposes where you might need other properties of the user model. Last reason afaik is, that a model represents the complete entity at any time, not parts of it. And there's no real reason why not to load all fields (beside references to other entities why should be autoloaded anyways).
Along with Fge's reply I believe that $resultSet should already be returning values of type Application_Model_User. If not, you may need to set $_rowClass in Application_Model_DbTable_User.
I have a very simple mapping function called "BuildEntity" that does the usual boring "left/right" coding required to dump my reader data into my domain object. (shown below) My question is this - If I don't bring back every column in this mapping as is, I get the "System.IndexOutOfRangeException" exception and wanted to know if ado.net had anything to correct this so I don't need to bring back every column with each call into SQL ...
What I'm really looking for is something like "IsValidColumn" so I can keep this 1 mapping function throughout my DataAccess class with all the left/right mappings defined - and have it work even when a sproc doesn't return every column listed ...
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim product As Product
While reader.Read()
product = New Product()
product.ID = Convert.ToInt32(reader("ProductID"))
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
Also check out this extension method I wrote for use on data commands:
public static void Fill<T>(this IDbCommand cmd,
IList<T> list, Func<IDataReader, T> rowConverter)
{
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
list.Add(rowConverter(rdr));
}
}
}
You can use it like this:
cmd.Fill(products, r => r.GetProduct());
Where "products" is the IList<Product> you want to populate, and "GetProduct" contains the logic to create a Product instance from a data reader. It won't help with this specific problem of not having all the fields present, but if you're doing a lot of old-fashioned ADO.NET like this it can be quite handy.
Although connection.GetSchema("Tables") does return meta data about the tables in your database, it won't return everything in your sproc if you define any custom columns.
For example, if you throw in some random ad-hoc column like *SELECT ProductName,'Testing' As ProductTestName FROM dbo.Products" you won't see 'ProductTestName' as a column because it's not in the Schema of the Products table. To solve this, and ask for every column available in the returned data, leverage a method on the SqlDataReader object "GetSchemaTable()"
If I add this to the existing code sample you listed in your original question, you will notice just after the reader is declared I add a data table to capture the meta data from the reader itself. Next I loop through this meta data and add each column to another table that I use in the left-right code to check if each column exists.
Updated Source Code
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim table As DataTable = reader.GetSchemaTable()
Dim colNames As New DataTable()
For Each row As DataRow In table.Rows
colNames.Columns.Add(row.ItemArray(0))
Next
Dim product As Product While reader.Read()
product = New Product()
If Not colNames.Columns("ProductID") Is Nothing Then
product.ID = Convert.ToInt32(reader("ProductID"))
End If
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
This is a hack to be honest, as you should return every column to hydrate your object correctly. But I thought to include this reader method as it would actually grab all the columns, even if they are not defined in your table schema.
This approach to mapping your relational data into your domain model might cause some issues when you get into a lazy loading scenario.
Why not just have each sproc return complete column set, using null, -1, or acceptable values where you don't have the data. Avoids having to catch IndexOutOfRangeException or re-writing everything in LinqToSql.
Use the GetSchemaTable() method to retrieve the metadata of the DataReader. The DataTable that is returned can be used to check if a specific column is present or not.
Why don't you use LinqToSql - everything you need is done automatically. For the sake of being general you can use any other ORM tool for .NET
If you don't want to use an ORM you can also use reflection for things like this (though in this case because ProductID is not named the same on both sides, you couldn't do it in the simplistic fashion demonstrated here):
List Provider in C#
I would call reader.GetOrdinal for each field name before starting the while loop. Unfortunately GetOrdinal throws an IndexOutOfRangeException if the field doesn't exist, so it won't be very performant.
You could probably store the results in a Dictionary<string, int> and use its ContainsKey method to determine if the field was supplied.
I ended up writing my own, but this mapper is pretty good (and simple): https://code.google.com/p/dapper-dot-net/