Make Doctrine to add entities in original order - php

I have following code:
$em = $this->getDoctrine()->getManager();
$evaluation->getQuestions()->clear();
foreach ($questions_data as $data) {
$id = (int) $data['id'];
if ($id > 0) {
$question = $em->getRepository('MyBundle:Question')->find($id);
if ($question)
$evaluation->getQuestions()->add($question);
}
}
$em->persist($evaluation);
$em->flush();
Here is $questions_data — array(['id' => 2], ['id' => 1], ['id' => 3]).
And here is how doctrine persist questions to database:
So, how to make doctrine to don't sort questions?
Evaluation entity has ManyToMany relation with the Question entity, so ORDER BY couldn't help, because table evaluations_questions was created automatically by Doctrine and don't have field id.

When you flush newly persisted items in Doctrine, Doctrine must determine which order to commit them to the database using an internal function getCommitOrder. The purpose of that function is to ensure that an object's dependencies are committed before the object itself is committed. This is done to comply with any foreign key constraints that might be set up. As you observed, a consequence of ordering data to commit like this is that you lose the ability to finely tune the order that items are committed - this isn't necessarily a bad thing.
In SQL the only way you can order your results is by issuing a query with ORDER BY. If you choose not to specify a sorting method, you cannot expect the results to come back in any particular order. The PostgreSQL docs explain this:
If sorting is not chosen, the rows will be returned in an unspecified order. The actual order in that case will depend on the scan and join plan types and the order on disk, but it must not be relied on.
In other words, it shouldn't matter what order the content is stored in your database.
In your problem, the order by which questions appear to a user is an issue. You can't allow the questions to appear randomly in an evaluation - they must follow a preset order. Frankbeen touches on this in a comment, but the best solution would be to add a new field on the Evaluation that stores an array of the Questions in the proper order. The order can then be read when you present the evaluation to a user.
If you absolutely must order them in a specific order in your database, you should be able to just flush the new objects individually as they are persisted instead of scheduling them to be flushed together.
$em = $this->getDoctrine()->getManager();
$evaluation->getQuestions()->clear();
foreach ($questions_data as $data) {
$id = (int) $data['id'];
if ($id > 0) {
$question = $em->getRepository('MyBundle:Question')->find($id);
if ($question) {
$evaluation->getQuestions()->add($question);
$em->persist($evaluation);
$em->flush();
}
}
}
Please be aware, this will take much more time to complete and is a pretty poor solution to your problem.

new records are stored at the end of the table, always. If you want to get the questions in natural order (by id) then you should use ORDER BY. I guess that the Evaluation entity has a OneToMany relation with the Question entity.
In that case you can simple add a optional annotation above the $questions property in the Evalutation entity:
/**
* #ORM\OneToMany(targetEntity="Question")
* #ORM\OrderBy({"id" = "ASC"})
*/
private $questions;

The EntityManagerInterface have transaction support and I recommend using that feature for all multi statement operations. This will allow you to create multiple statements and commit them in order all att once. Each statement will be executed in the order that they were added and if one statement fails, no changes will be done to the database at all.
That being said, all database implementations might not store data in the order that they were added, so if data is expected to be returned in a certain order, you should always query with ORDER BY explicitly as mentioned in another reply.
Say that you want to abort the operation if you encounter a question with an unknown id. This can quite easily be accomplished with something like the following:
function storeQuestions($questions_data): bool
{
$em = $this->getDoctrine()->getManager();
$em->beginTransaction();
$evaluation->getQuestions()->clear();
foreach ($questions_data as $data) {
$id = (int) $data['id'];
if ($id > 0) {
$question = $em->getRepository('MyBundle:Question')->find($id);
if ($question) {
$evaluation->getQuestions()->add($question);
$em->persist($evaluation);
} else {
$em->rollback();
return false;
}
}
}
$em->flush();
$em->commit();
return true; // Success
}

Related

Laravel database optimization

I have a code which fetches data from external API and commits it to DB afterwards:
protected function saveWidgetsToDatabase($widgetsDaily, Boost $boost, $date)
{
echo "Saving widgets to DB... ";
$widgets = Widget::all();
foreach ($widgetsDaily as $widgetDaily) {
$existingWidget = $widgets
->where('widget_id', $widgetDaily->id)
->where('date', $date)
->first();
if ($existingWidget === null)
$boost->widgets()->save(new Widget([
...
]));
else
$existingWidget->update([
...
]);
}
}
Relation I have is that one Boost has many Widgets. Now, the issue I'm facing is bottleneck DB saving/updating as I need to update a widget only if it has same date and ID, otherwise I need to create new one.
We are talking about few thousands of records, so I believe that where clauses are pretty intensive.
I wanted to make a batch save, though I didn't quite make it.
Are there any chances of making this faster?
When you call Widget::all();, that gets every single widget record in your database and creates a Widget instance for it. Therefore, $widgets will be a Collection of every Widget object stored in the database. If you have 10000 widget records, you'll have a Collection of 10000 Widget objects. This is obviously not what you want.
That also means that when you call $widgets->where()..., you're calling where() on the Collection object, which is using PHP to filter through the collection of objects, instead of using SQL to filter the database results.
There are a couple things you can do.
First, you know you only care about those widgets that have an id in the list of $widgetsDaily. So, limit your Widget query to only include those records that have a widget_id in that list of ids.
Second, add the date lookup to the database query as well.
Third, key the resulting collection by the widget_id field, so that you can directly access the item by the widget_id without having to loop through the entire collection looking for it every time.
protected function saveWidgetsToDatabase($widgetsDaily, Boost $boost, $date)
{
// Get the only widget_ids we care about (assumes $widgetsDaily is a collection)
$ids = $widgetsDaily->pluck('id')->all();
// Get the target widgets from the database. This collection will only
// contain widgets that we actually care about.
$widgets = Widget::whereIn('widget_id', $ids)
->where('date', $date)
->get()
->keyBy('widget_id'); // rekey the resulting collection
foreach ($widgetsDaily as $widgetDaily) {
// Because the collection was rekeyed on widget_id, you can use
// get(id) instead of having to use where('widget_id', id)->first()
$existingWidget = $widgets->get($widgetDaily->id);
if ($existingWidget === null)
$boost->widgets()->save(new Widget([
...
]));
else
$existingWidget->update([
...
]);
}
}

Elegant way to walk backward through OneToOne table entities with Doctrine

I have a very simply structured entity that contains a simple association
Database_Entity_Tenant
id (primary key)
parentId (id of the parent entry)
code (a simple identifier for the tenant, unique)
I defined parentId in my entity accordingly:
/**
* #Column(type="integer")
* #OneToOne(targetEntity="Tenant")
* #JoinColumn(name="parentTenantId", referencedColumnName="id")
* **/
protected $parentId;
This works fine - the generated database schema resembles my choices and its good.
Now i am writing my first method which basically has to return an array of all the tenants that are chained together, in reverse order (i use this for walking backward through a chain of tenants).
In order to do that i came up with the idea to use a while() loop.
$currentTenant = {DATABASE_ENTITY_TENANT}; // In my real code i fetch the entity object of the current tenant
$chain[] = $currentTenant;
$repository = Database::entityManager()->getRepository('Database_Entity_Tenant');
while(!$currentTenant->getParentId()){
$currentTenant = $repository->findOneBy(array(
'id' => $currentTenant->getParentId()
));
$chain[] = $currentTenant;
}
Any tenant that has no parent (such as the base tenant) will have no parent id (or null), so that would end the while loop.
Now all this may work, but it seems really rough to me. I am fairly new to Doctrine so i don't know much about it but i am sure there is some way to do this more elegantly.
QUESTION
Does Doctrine 2 provide me with any set of functions i could use to solve the above problem in a better way?
If not, then is there any other way to do this more elegantly?
If I'm not getting your problem wrong, you just need to find all the entries in your association table ordered by the parentId. In Doctrine2 you can do the following:
$currentTenant = {DATABASE_ENTITY_TENANT}; // assuming a valid entity
$repository = Database::entityManager()
->getRepository('Database_Entity_Tenant')
->createQueryBuilder('t')
->where('t.parentId IS NOT NULL')
->andWhere('t.parentId < :current') /* < or > */
->setParameter('current', $currentTenant->getParentId()->getId())
->orderBy('t.parentId', 'ASC') /* ASC or DESC, no array_reverse */
->getQuery()
->getResult();
/* At this point $repository contains all what you need because of Doctrine,
* but if you want a chain variable: */
$chain = array();
foreach ($repository as $tenant) {
$chain[] = $tenant->getCode(); // your tenant entity if your entity is mapped correctly
}
Hope this helps!

Doctrine/Symfony 2: Entities with too many relationships

I'm working with Entity objects from Doctrine queries and i end up with a very big array, with all information from all entities related. This ends up being a huge data tree... how can i limit this? Avoid listing all data from all relationships?
You can always remove not needed associations (this is a best practice for speeding up Doctrine). Or you can select only fields that you need in your presentation layer (as read-only data):
public function getAll()
{
$qb = $this->createQueryBuilder('u'); // Where are in User custom repository
return $qb
->select(array('u.id', 'u.first', 'u.last'))
->getQuery()
->getResult();
}
If you still need to work with objects (or for complex queries that needs plain SQL) a possibility is filling only needed properties (and eventually, associations/nested collections) of your domain object.
An example, more on native SQL:
public function getAll()
{
$mapping = new \Doctrine\ORM\Query\ResultSetMapping();
$mapping->addEntityResult('Acme\HelloBundle\User', 'e');
$mapping->addFieldResult('e', 'id', 'id');
$mapping->addFieldResult('e', 'first', 'first');
$mapping->addFieldResult('e', 'last', 'last');
$sql = "SELECT id, first, last FROM user ";
$result = $this->_em->createNativeQuery($sql, $mapping)->getResult();
// Or hust return $result itself (array)
return new \Doctrine\Common\Collections\ArrayCollection($result);
}
Of course the disadvance (?) is use of native SQL. I don't believe that ResultSetMapping can be used with DQL.
EDIT: take a look at http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/best-practices.html

How do one use ACL to filter a list of domain-objects according to a certain user's permissions (e.g. EDIT)?

When using the ACL implementation in Symfony2 in a web application, we have come across a use case where the suggested way of using the ACLs (checking a users permissions on a single domain object) becomes unfeasible. Thus, we wonder if there exists some part of the ACL API we can use to solve our problem.
The use case is in a controller that prepares a list of domain objects to be presented in a template, so that the user can choose which of her objects she wants to edit. The user does not have permission to edit all of the objects in the database, so the list must be filtered accordingly.
This could (among other solutions) be done according to two strategies:
1) A query filter that appends a given query with the valid object ids from the present user's ACL for the object(or objects). I.e:
WHERE <other conditions> AND u.id IN(<list of legal object ids here>)
2) A post-query filter that removes the objects the user does not have the correct permissions for after the complete list has been retrieved from the database. I.e:
$objs = <query for objects>
$objIds = <getting all the permitted obj ids from the ACL>
for ($obj in $objs) {
if (in_array($obj.id, $objIds) { $result[] = $obj; }
}
return $result;
The first strategy is preferable as the database is doing all the filtering work, and both require two database queries. One for the ACLs and one for the actual query, but that is probably unavoidable.
Is there any implementation of one of these strategies (or something achieving the desired results) in Symfony2?
Assuming that you have a collection of domain objects that you want to check, you can use the security.acl.provider service's findAcls() method to batch load in advance of the isGranted() calls.
Conditions:
Database was populated with test entities, with object permissions of MaskBuilder::MASK_OWNER for a random user from my database, and class permissions of MASK_VIEW for role IS_AUTHENTICATED_ANONYMOUSLY; MASK_CREATE for ROLE_USER; and MASK_EDIT and MASK_DELETE for ROLE_ADMIN.
Test Code:
$repo = $this->getDoctrine()->getRepository('Foo\Bundle\Entity\Bar');
$securityContext = $this->get('security.context');
$aclProvider = $this->get('security.acl.provider');
$barCollection = $repo->findAll();
$oids = array();
foreach ($barCollection as $bar) {
$oid = ObjectIdentity::fromDomainObject($bar);
$oids[] = $oid;
}
$aclProvider->findAcls($oids); // preload Acls from database
foreach ($barCollection as $bar) {
if ($securityContext->isGranted('EDIT', $bar)) {
// permitted
} else {
// denied
}
}
RESULTS:
With the call to $aclProvider->findAcls($oids);, the profiler shows that my request contained 3 database queries (as anonymous user).
Without the call to findAcls(), the same request contained 51 queries.
Note that the findAcls() method loads in batches of 30 (with 2 queries per batch), so your number of queries will go up with larger datasets. This test was done in about 15 minutes at the end of the work day; when I have a chance, I'll go through and review the relevant methods more thoroughly to see if there are any other helpful uses of the ACL system and report back here.
Itinerating over the entities is not feasible if you have a couple of thousandth entities - it will keep getting slower and consuming more memory, forcing you to use doctrine batching capabilities, thus making your code more complex (and innefective because after all you need only the ids to make a query - not the whole acl/entities in memory)
What we did to solve this problem is to replace acl.provider service with our own and in that service add a method to make a direct query to the database:
private function _getEntitiesIdsMatchingRoleMaskSql($className, array $roles, $requiredMask)
{
$rolesSql = array();
foreach($roles as $role) {
$rolesSql[] = 's.identifier = ' . $this->connection->quote($role);
}
$rolesSql = '(' . implode(' OR ', $rolesSql) . ')';
$sql = <<<SELECTCLAUSE
SELECT
oid.object_identifier
FROM
{$this->options['entry_table_name']} e
JOIN
{$this->options['oid_table_name']} oid ON (
oid.class_id = e.class_id
)
JOIN {$this->options['sid_table_name']} s ON (
s.id = e.security_identity_id
)
JOIN {$this->options['class_table_nambe']} class ON (
class.id = e.class_id
)
WHERE
{$this->connection->getDatabasePlatform()->getIsNotNullExpression('e.object_identity_id')} AND
(e.mask & %d) AND
$rolesSql AND
class.class_type = %s
GROUP BY
oid.object_identifier
SELECTCLAUSE;
return sprintf(
$sql,
$requiredMask,
$this->connection->quote($role),
$this->connection->quote($className)
);
}
Then calling this method from the actual public method that gets the entities ids:
/**
* Get the entities Ids for the className that match the given role & mask
*
* #param string $className
* #param string $roles
* #param integer $mask
* #param bool $asString - Return a comma-delimited string with the ids instead of an array
*
* #return bool|array|string - True if its allowed to all entities, false if its not
* allowed, array or string depending on $asString parameter.
*/
public function getAllowedEntitiesIds($className, array $roles, $mask, $asString = true)
{
// Check for class-level global permission (its a very similar query to the one
// posted above
// If there is a class-level grant permission, then do not query object-level
if ($this->_maskMatchesRoleForClass($className, $roles, $requiredMask)) {
return true;
}
// Query the database for ACE's matching the mask for the given roles
$sql = $this->_getEntitiesIdsMatchingRoleMaskSql($className, $roles, $mask);
$ids = $this->connection->executeQuery($sql)->fetchAll(\PDO::FETCH_COLUMN);
// No ACEs found
if (!count($ids)) {
return false;
}
if ($asString) {
return implode(',', $ids);
}
return $ids;
}
This way now we can use the code to add filters to DQL queries:
// Some action in a controller or form handler...
// This service is our own aclProvider version with the methods mentioned above
$aclProvider = $this->get('security.acl.provider');
$ids = $aclProvider->getAllowedEntitiesIds('SomeEntityClass', array('role1'), MaskBuilder::VIEW, true);
if (is_string($ids)) {
$queryBuilder->andWhere("entity.id IN ($ids)");
}
// No ACL found: deny all
elseif ($ids===false) {
$queryBuilder->andWhere("entity.id = 0")
}
elseif ($ids===true) {
// Global-class permission: allow all
}
// Run query...etc
Drawbacks: This methods have to be improved to take into account the complexities of ACL inheritance and strategies, but for simple use cases it works fine. Also a cache has to be implemented to avoid the repetitive double query (one with class-level, another with objetc-level)
Coupling Symfony ACL back to application and using it as sorting, is not good approach. You are mixing and coupling 2 or 3 layers of application together.
ACL functionality is to answer "YES/NO" to question "Am I allowed to do this?" If you need some sort of owned/editable articles, you can use some column like CreatedBy or group CreatedBy by criteria from another table. Some usergroups or accounts.
Use joins, and in case you're using Doctrine, get it to generate joins for you, as they are almost always faster. Therefore you should design your ACL schema that doing these fast filters are feasible.

Symfony app - how to add calculated fields to Propel objects?

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');
}
...
}

Categories