I am trying to figure out how to return asset field(s) in a PHP entry query. Also if I could learn how to return "custom fields" when returning an Object that would be great too! Right now I am having to specify asArray() to even get access to most of my "custom fields"
As as example: I have a Vehicle Entry which has a custom field with the handle of price (number field) and another custom field (asset field) with the handle of images. When I execute the query without specifying the asArray() param I cannot find the custom fields included in the results. But if I specify asArray() then they are all there with the exception of my images field which I think is because it is an asset field or possible because it can be a collection of images? How can I make sure all the fields tied to an entry are returned in my query?
Here are some examples of queries and the corresponding results:
PHP Query without asArray():
$entry_query = Entry::find()
->section('inventory')
->all();
Returns:
PHP Query results with asArray():
$entry_query = Entry::find()
->section('inventory')
->asArray();
Returns:
However even when specifing to make the result set an array I still cannot figure out how to include the 'images' field.
I am having a difficult time finding an answer via the documentation or an example of someone doing the same. All the examples i find are for the template side in twig.
Thanks!
This is what I ended up with:
$vehicles = array(); // empty container to hold our modified entries
// Lets Query our Inventory - all of it
/** #var array $entry_query the name of our query to get our inventory - return a list of inventory after we execute query */
$entries = Entry::find()
->section('inventory')
->all();
foreach($entries as $entry) {
/*
* Let's get all our custom fields we want
* to include with our entries
*/
// Get our image field and add to result set - because it's an asset field this returns a query object
$ourimages = $entry->images->all(); // get all our images
$price = $entry->price;
$featured = $entry->featureThisVehicle;
$make = $entry->make;
$model = $entry->model;
$year = $entry->year;
$description = $entry->description;
$inventoryStatus = $entry->inventoryStatus;
$bodyStyle = $entry->bodyStyle;
$color = $entry->color;
$miles = $entry->miles;
$vin = $entry->vin;
$stkid = $entry->stkid;
// cast out entry object as an array - so we can add props to it
$entry = (array)$entry;
// add our custom fields to our newly casted entry array
$entry['images'] = $ourimages;
$entry['price'] = $price;
$entry['featured'] = $featured;
$entry['make'] = $make;
$entry['model'] = $model;
$entry['year'] = $year;
$entry['description'] = $description;
$entry['inventoryStatus'] = $inventoryStatus;
$entry['bodyStyle'] = $bodyStyle;
$entry['color'] = $color;
$entry['miles'] = $miles;
$entry['vin'] = $vin;
$entry['stkid'] = $stkid;
// Recast back to object just cause (not really necessary since we are json_encode'ing this)
$entry = (object)$entry;
array_push($vehicles, $entry);
}
return json_encode($vehicles);
Yii has an array helper function that simplifies this. See this stack exchange entry – it’s essentially the same approach as the one you describe, but simpler (less queries, less lines of code).
Related
View in database I mean :
create view `vMaketType` as select * from MaketType
I have a view in database, but because of doctrine cant support it now, i using query, and fetch it one by one :
$em = $this->getDoctrine()->getManager();
$con = $this->getDoctrine()->getEntityManager()->getConnection();
$stmt = $con->executeQuery('SELECT * FROM vMaketType');
$domain = [];
//I must fetch it and set it one by one
foreach ($stmt->fetchAll() as $row){
$obj = new vMaketType();
$obj->setId($row["Id"]);
$obj->setName($row["Name"]);
$obj->setAmount($row["Amount"]);
array_push($domain, $obj);
}
for me this is really takes too much time to code one by one.
vMaketType is a custom entity I created to send data from controller to [Twig]view.
is there any easier way to fetch to array of object vMaketType?
because I have a view with 24 fields, I wish there is easier way for it.
Perhaps you can try with the serializer:
$obj = $this->get('serializer')->deserialize($row, 'Namespace\MaketType', 'array');
Code not tested, tweaks may be done, see the related doc.
I've seen on many shopping websites they have search filters on the side, and you can add any number of filters and it researches the data showing only the data that matches all of the filter queries.
As an example, if you go to ebay and look for a computer, you can filter by various specs of the computer to narrow the results.
The problem I'm having is working out how to do this for a table that has so many fields that a user may search by.
I have a table of Properties that I want to search by any number of parameters e.g. rent, location, etc.
I could create search queries for each possible option e.g. searchByAddress($array), searchByAddressAndRent($array), etc. but that's clearly not feasible.
Another way I could create separate queries for each field, then trigger separate search queries for each parameter i.e. searchByRent($array), searchByAddress($array) and allow the PHP application to compute which fields are common in all resulting arrays using array_intersect.
But I was wondering, there must be a proper technique in achieving this. This question is a bit long-winded and I couldn't find any tutorials on it from googling.
So my question is, what's the "right" method/technique of searching a database table with various search-filters?
If you create a class representing the property table, you can define a static array in the class detailing each field name and corresponding data type.
On the front end, each search field should correspond to the column name in the database, so the $_REQUEST array keys will match the column names.
After this, iterate through your search array, checking each variable exists in your class field definition array, and add it onto the search query.
Below is a very simplified example class which will hopefully give you the idea.
class Property () {
// search parameters come from $values_array
// each search field should correspond to the $field_definitions key
public function search($values_array = null) {
// populate the values array. Using this method meads you can pass an array directly
// into the search function, or you can rely on the $_REQUEST
$values_array = self::getValuesArray($values_array);
// setup the initial query
$query = "SELECT * FROM properties";
// set our initial join string
$join = "WHERE";
// loop each of our search values
foreach ($values_array as $field=>$value) {
// check the search key exists in our table definition and it's not empty
if (array_key_exists($field_definitions, self::$fields) && !empty($value)) {
// switch the datatype for the field so we can set the appropriate query string
switch (self::$field_definitions[$field]) {
case 'int':
$query .= "$join $field = {$value} ";
$join = "AND";
break;
default:
$query .= "$join $field = '%{$value}%' ";
$join = "AND";
break;
}
}
}
// now execute the query...
$results = mysql_query($query);
// do something to process the results and then return
return $results;
}
// basic function to grab our values from $_REQUEST if passed an empty array
private function getValuesArray($values_array = null) {
$values_array = (!empty($values_array)) ? $values_array : $_REQUEST;
return $values_array;
}
// variable containing all available fields in table and corresponding datatype
public static $field_definitions =
array( 'number'=>'int',
'street'=>'string',
'locality'=>'string',
'townland'=>'string',
'town'=>'string',
'postcode'=>'string'
);
}
I'm using codeigniter to code my site and I'm running into a roadblock. I know how to do this in regular, non "MVC", not OOP PHP, but am struggling on it in Codeigniter.
I have blog_model.php, which has a function to retrieve the datetime from my database, explode it into an array so that I can work with it outside the model and feed it into CSS where I have individual calendar icons for it. Each calendar icon is loaded by the month number in the view (<div class='calendar-icon-$stats['date']). This function also pulls the amount of comments from that individual post and outputs it into an array so that I can show it in the view.
public function get_stats($id) {
$this->db->select('id,datetime')->from('blog_posts')->where('id',$id);
$dquery = $this->db->get();
$dquery = $dquery->row_array();
$date = date('Y-m-d', strtotime($dquery['datetime']));
$stats = explode("-", $date); // This makes $stats[0] the year, $stats[1] the month and $stats[2] the day.
$stats['time'] = date('H:i', strtotime($dquery['datetime']));
$stats['comcount'] = $this->db->get_where('blog_comments', array('blogid' => $id));
$stats['comcount'] = $stats['comcount']->num_rows();
return $stats;
}
There is also a function to retrieve the three most recent entries:
public function get_blog_last() {
$query = $this->db->order_by('id desc')->get('blog_posts',3);
return $query->result_array();
}
This code is then loaded into my controller and sent to the view to be displayed:
$data['blog'] = $this->blog_model->get_blog_last();
$data['stats'] = $this->blog_model->get_stats($data['blog']);
$this->load->view('index',$data);
The problem I face is how to get the get_stats() function to run for every entry I have on the index page, where the last three entries are displayed. So far I can only get it to run for one of them, therefore all three of the entries on my front page have the same date, the same time and the same amount of comments. I figured putting the code in a model would save myself from repeating myself when I had to load it for the archives page (where I display all the posts from the month) and the main entry page where I just display that entry and its comments.
So, the ultimate question here is:
How do I run get_stats for every entry I have on a given page?
I'm also having a bit of issue figuring out the correct value to pass to my get_stats() function.
Any guidance would be appreciated. Thank you in advance.
If I'm understanding correctly, you need to call get_stats for each of the three entries that you receive in get_blog_last. If that is the case, just change get_blog_last to this:
public function get_blog_last() {
$query = $this->db->order_by('id desc')->get('blog_posts',3);
$entries = $query->result_array(); // get the latest entries array
foreach ($entries as $index => $entry) { // loop through those entries
$stats = $this->get_stats($entry['id']); // call this model's `get_stats` method
$entries[$index]['stats'] = $stats; // add a `stats` key to the entry array
}
return $entries;
}
Why don't you put
$this->blog_model->get_stats($data['blog']);
inside loop ? ( i'd rather use normal loop )
example :
$stat_list = array();
for($i=0;$i<count($data['blog']);$i++){
$stat_list[] = $this->blog_model->get_stats($data['blog'][$i]);
}
$data['stats'] = $stat_list;
and in your view, you should try the same to print out each $stat_list
i'm playing a little bit with Symfony2 and Doctrine2.
I have an Entity that has a unique title for example:
class listItem
{
/**
* #orm:Id
* #orm:Column(type="integer")
* #orm:GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #orm:Column(type="string", length="255", unique="true")
* #assert:NotBlank()
*/
protected $title;
now i'm fetching a json and updating my database with those items:
$em = $this->get('doctrine.orm.entity_manager');
foreach($json->value->items as $item) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
}
$em->flush();
works fine the first time. but the second time i'm getting an sql error (of course): Integrity constraint violation: 1062 Duplicate entry
sometimes my json file gets updated and some of the items are new, some are not.
Is there a way to tell the entity manager to skip the duplicate files and just insert the new ones?
Whats the best way to do this?
Thanks for all help. Please leave a comment if something is unclear
Edit:
what works for me is doing something like this:
$uniqueness = $em->getRepository('ListItem')->checkUniqueness($item->title);
if(false == $uniqueness) {
continue;
}
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
$em->flush();
}
checkUniqueness is a method in my ListItem Repo that checks if the title is already in my db.
thats horrible. this are 2 database queries for each item. this ends up about 85 database queries for this action.
How about retrieving all the current titles into an array first and checking the inserting title against the current titles in that array
$existingTitles = $em->getRepository('ListItem')->getCurrentTitles();
foreach($json->value->items as $item) {
if (!in_array($item->title, $existingTitles)) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
}
}
$em->flush();
getCurrentTitles() would need to be added to ListItem Repo to simply return an array of titles.
This only requires one extra DB query but does cost you more in memory to hold the current titles in an array. There maybe problems with this method if your dataset for ListItem is very big.
If the number of items your want to insert each time isn't too large, you could modify the getCurrentTitles() function to query for all those items with the titles your trying to insert. This way the max amount of $existingTiles you will return will be the size of your insert data list. Then you could perform your checks as above.
// getCurrentTitles() - $newTitles is array of all new titles you want to insert
return $qb->select('title')
->from('Table', 't')
->in('t.title = ', $newTitles)
->getArrayResult();
If you are using an entity that may already exists in the manager you have to merge it.
Here is what I would do (did not test it yet) :
$repository = $this->get('doctrine.orm.entity_manager');
foreach($json->value->items as $item) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->merge($listItem); // return a managed entity
// no need to persist as long as the entity is now managed
}
$em->flush();
What is the best way of working with calculated fields of Propel objects?
Say I have an object "Customer" that has a corresponding table "customers" and each column corresponds to an attribute of my object. What I would like to do is: add a calculated attribute "Number of completed orders" to my object when using it on View A but not on Views B and C.
The calculated attribute is a COUNT() of "Order" objects linked to my "Customer" object via ID.
What I can do now is to first select all Customer objects, then iteratively count Orders for all of them, but I'd think doing it in a single query would improve performance. But I cannot properly "hydrate" my Propel object since it does not contain the definition of the calculated field(s).
How would you approach it?
There are several choices. First, is to create a view in your DB that will do the counts for you, similar to my answer here. I do this for a current Symfony project I work on where the read-only attributes for a given table are actually much, much wider than the table itself. This is my recommendation since grouping columns (max(), count(), etc) are read-only anyway.
The other options are to actually build this functionality into your model. You absolutely CAN do this hydration yourself, but it's a bit complicated. Here's the rough steps
Add the columns to your Table class as protected data members.
Write the appropriate getters and setters for these columns
Override the hydrate method and within, populate your new columns with the data from other queries. Make sure to call parent::hydrate() as the first line
However, this isn't much better than what you're talking about already. You'll still need N + 1 queries to retrieve a single record set. However, you can get creative in step #3 so that N is the number of calculated columns, not the number of rows returned.
Another option is to create a custom selection method on your TablePeer class.
Do steps 1 and 2 from above.
Write custom SQL that you will query manually via the Propel::getConnection() process.
Create the dataset manually by iterating over the result set, and handle custom hydration at this point as to not break hydration when use by the doSelect processes.
Here's an example of this approach
<?php
class TablePeer extends BaseTablePeer
{
public static function selectWithCalculatedColumns()
{
// Do our custom selection, still using propel's column data constants
$sql = "
SELECT " . implode( ', ', self::getFieldNames( BasePeer::TYPE_COLNAME ) ) . "
, count(" . JoinedTablePeer::ID . ") AS calc_col
FROM " . self::TABLE_NAME . "
LEFT JOIN " . JoinedTablePeer::TABLE_NAME . "
ON " . JoinedTablePeer::ID . " = " . self::FKEY_COLUMN
;
// Get the result set
$conn = Propel::getConnection();
$stmt = $conn->prepareStatement( $sql );
$rs = $stmt->executeQuery( array(), ResultSet::FETCHMODE_NUM );
// Create an empty rowset
$rowset = array();
// Iterate over the result set
while ( $rs->next() )
{
// Create each row individually
$row = new Table();
$startcol = $row->hydrate( $rs );
// Use our custom setter to populate the new column
$row->setCalcCol( $row->get( $startcol ) );
$rowset[] = $row;
}
return $rowset;
}
}
There may be other solutions to your problem, but they are beyond my knowledge. Best of luck!
I am doing this in a project now by overriding hydrate() and Peer::addSelectColumns() for accessing postgis fields:
// in peer
public static function locationAsEWKTColumnIndex()
{
return GeographyPeer::NUM_COLUMNS - GeographyPeer::NUM_LAZY_LOAD_COLUMNS;
}
public static function polygonAsEWKTColumnIndex()
{
return GeographyPeer::NUM_COLUMNS - GeographyPeer::NUM_LAZY_LOAD_COLUMNS + 1;
}
public static function addSelectColumns(Criteria $criteria)
{
parent::addSelectColumns($criteria);
$criteria->addAsColumn("locationAsEWKT", "AsEWKT(" . GeographyPeer::LOCATION . ")");
$criteria->addAsColumn("polygonAsEWKT", "AsEWKT(" . GeographyPeer::POLYGON . ")");
}
// in object
public function hydrate($row, $startcol = 0, $rehydrate = false)
{
$r = parent::hydrate($row, $startcol, $rehydrate);
if ($row[GeographyPeer::locationAsEWKTColumnIndex()]) // load GIS info from DB IFF the location field is populated. NOTE: These fields are either both NULL or both NOT NULL, so this IF is OK
{
$this->location_ = GeoPoint::PointFromEWKT($row[GeographyPeer::locationAsEWKTColumnIndex()]); // load gis data from extra select columns See GeographyPeer::addSelectColumns().
$this->polygon_ = GeoMultiPolygon::MultiPolygonFromEWKT($row[GeographyPeer::polygonAsEWKTColumnIndex()]); // load gis data from extra select columns See GeographyPeer::addSelectColumns().
}
return $r;
}
There's something goofy with AddAsColumn() but I can't remember at the moment, but this does work. You can read more about the AddAsColumn() issues.
Here's what I did to solve this without any additional queries:
Problem
Needed to add a custom COUNT field to a typical result set used with the Symfony Pager. However, as we know, Propel doesn't support this out the box. So the easy solution is to just do something like this in the template:
foreach ($pager->getResults() as $project):
echo $project->getName() . ' and ' . $project->getNumMembers()
endforeach;
Where getNumMembers() runs a separate COUNT query for each $project object. Of course, we know this is grossly inefficient because you can do the COUNT on the fly by adding it as a column to the original SELECT query, saving a query for each result displayed.
I had several different pages displaying this result set, all using different Criteria. So writing my own SQL query string with PDO directly would be way too much hassle as I'd have to get into the Criteria object and mess around trying to form a query string based on whatever was in it!
So, what I did in the end avoids all that, letting Propel's native code work with the Criteria and create the SQL as usual.
1 - First create the [get/set]NumMembers() equivalent accessor/mutator methods in the model object that gets returning by the doSelect(). Remember, the accessor doesn't do the COUNT query anymore, it just holds its value.
2 - Go into the peer class and override the parent doSelect() method and copy all code from it exactly as it is
3 - Remove this bit because getMixerPreSelectHook is a private method of the base peer (or copy it into your peer if you need it):
// symfony_behaviors behavior
foreach (sfMixer::getCallables(self::getMixerPreSelectHook(__FUNCTION__)) as $sf_hook)
{
call_user_func($sf_hook, 'BaseTsProjectPeer', $criteria, $con);
}
4 - Now add your custom COUNT field to the doSelect method in your peer class:
// copied into ProjectPeer - overrides BaseProjectPeer::doSelectJoinUser()
public static function doSelectJoinUser(Criteria $criteria, ...)
{
// copied from parent method, along with everything else
ProjectPeer::addSelectColumns($criteria);
$startcol = (ProjectPeer::NUM_COLUMNS - ProjectPeer::NUM_LAZY_LOAD_COLUMNS);
UserPeer::addSelectColumns($criteria);
// now add our custom COUNT column after all other columns have been added
// so as to not screw up Propel's position matching system when hydrating
// the Project and User objects.
$criteria->addSelectColumn('COUNT(' . ProjectMemberPeer::ID . ')');
// now add the GROUP BY clause to count members by project
$criteria->addGroupByColumn(self::ID);
// more parent code
...
// until we get to this bit inside the hydrating loop:
$obj1 = new $cls();
$obj1->hydrate($row);
// AND...hydrate our custom COUNT property (the last column)
$obj1->setNumMembers($row[count($row) - 1]);
// more code copied from parent
...
return $results;
}
That's it. Now you have the additional COUNT field added to your object without doing a separate query to get it as you spit out the results. The only drawback to this solution is that you've had to copy all the parent code because you need to add bits right in the middle of it. But in my situation, this seemed like a small compromise to save all those queries and not write my own SQL query string.
Add an attribute "orders_count" to a Customer, and then write something like this:
class Order {
...
public function save($conn = null) {
$customer = $this->getCustomer();
$customer->setOrdersCount($customer->getOrdersCount() + 1);
$custoner->save();
parent::save();
}
...
}
You can use not only the "save" method, but the idea stays the same. Unfortunately, Propel doesn't support any "magic" for such fields.
Propel actually builds an automatic function based on the name of the linked field. Let's say you have a schema like this:
customer:
id:
name:
...
order:
id:
customer_id: # links to customer table automagically
completed: { type: boolean, default false }
...
When you build your model, your Customer object will have a method getOrders() that will retrieve all orders associated with that customer. You can then simply use count($customer->getOrders()) to get the number of orders for that customer.
The downside is this will also fetch and hydrate those Order objects. On most RDBMS, the only performance difference between pulling the records or using COUNT() is the bandwidth used to return the results set. If that bandwidth would be significant for your application, you might want to create a method in the Customer object that builds the COUNT() query manually using Creole:
// in lib/model/Customer.php
class Customer extends BaseCustomer
{
public function CountOrders()
{
$connection = Propel::getConnection();
$query = "SELECT COUNT(*) AS count FROM %s WHERE customer_id='%s'";
$statement = $connection->prepareStatement(sprintf($query, CustomerPeer::TABLE_NAME, $this->getId());
$resultset = $statement->executeQuery();
$resultset->next();
return $resultset->getInt('count');
}
...
}