ModelCriteria.txt 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. = Propel Query Reference =
  2. [[PageOutline]]
  3. Propel's Query classes make it easy to write queries of any level of complexity in a simple and reusable way.
  4. == Overview ==
  5. Propel proposes an object-oriented API for writing database queries. That means that you don't need to write any SQL code to interact with the database. Object orientation also facilitates code reuse and readability. Here is how to query the database for records in the `book` table ordered by the `title` column and published in the last month:
  6. {{{
  7. #!php
  8. <?php
  9. $books = BookQuery::create()
  10. ->filterByPublishedAt(array('min' => time() - 30 * 24 * 60 * 60))
  11. ->orderByTitle()
  12. ->find();
  13. }}}
  14. The first thing to notice here is the fluid interface. Propel queries are made of method calls that return the current query object - `filterByPublishedAt()` and `orderByTitle()` return the current query augmented with conditions. `find()`, on the other hand, is a ''termination method'' that doesn't return the query, but its result - in this case, a collection of `Book` objects.
  15. Propel generates one `filterByXXX()` method for every column in the table. The column name used in the PHP method is not the actual column name in the database ('`published_at`'), but rather a CamelCase version of it ('`PublishedAt`'), called the column ''phpName''. Remember to always use the phpName in the PHP code ; the actual SQL name only appears in the SQL code.
  16. When a termination method like `find()` is called, Propel builds the SQL code and executes it. The previous example generates the following code when `find()` is called:
  17. {{{
  18. #!php
  19. <?php
  20. // example Query generated for a MySQL database
  21. $query = 'SELECT book.* from `book`
  22. WHERE book.PUBLISHED_AT >= :p1
  23. ORDER BY book.TITLE ASC';
  24. }}}
  25. Propel uses the column name in conjunction with the schema to determine the column type. In this case, `published_at` is defined in the schema as a `TIMESTAMP`. Then, Propel ''binds'' the value to the condition using the column type. This prevents SQL injection attacks that often plague web applications. Behind the curtain, Propel uses PDO to achieve this binding:
  26. {{{
  27. #!php
  28. <?php
  29. // $con is a PDO instance
  30. $stmt = $con->prepare($query);
  31. $stmt->bind(':p1', time() - 30 * 24 * 60 * 60, PDO::PARAM_INT);
  32. $res = $stmt->execute();
  33. }}}
  34. The final `find()` doesn't just execute the SQL query above, it also instanciates `Book` objects and populates them with the results of the query. Eventually, it returns a `PropelCollection` object with these `Book` objects inside. For the sake of clarity, you can consider this collection object as an array. In fact, you can use it as if it were a true PHP array and iterate over the result list the usual way:
  35. {{{
  36. #!php
  37. <?php
  38. foreach ($books as $book) {
  39. echo $book->getTitle();
  40. }
  41. }}}
  42. So Propel queries are a very powerful tool to write your queries in an object-oriented fashion. They are also very natural - if you know how to write an SQL query, chances are that you will write Propel queries in minutes.
  43. == Generated Query Methods ==
  44. For each object, Propel creates a few methods in the generated query object.
  45. === Column Filter Methods ===
  46. `filterByXXX()`, generated for each column, provides a different feature and a different functionality depending on the column type:
  47. * For all columns, `filterByXXX()` translates to a simple SQL `WHERE` condition by default:
  48. {{{
  49. #!php
  50. <?php
  51. $books = BookQuery::create()
  52. ->filterByTitle('War And Peace')
  53. ->find();
  54. // example Query generated for a MySQL database
  55. $query = 'SELECT book.* from `book`
  56. WHERE book.TITLE = :p1'; // :p1 => 'War And Peace'
  57. }}}
  58. * For string columns, `filterByXXX()` translates to a SQL `WHERE ... LIKE` if the value contains wildcards:
  59. {{{
  60. #!php
  61. <?php
  62. $books = BookQuery::create()
  63. ->filterByTitle('War%')
  64. ->find();
  65. // example Query generated for a MySQL database
  66. $query = 'SELECT book.* from `book`
  67. WHERE book.TITLE LIKE :p1'; // :p1 => 'War%'
  68. }}}
  69. * For integer columns, `filterByXXX()` translates into a SQL `WHERE ... IN` if the value is an array:
  70. {{{
  71. #!php
  72. <?php
  73. $books = BookQuery::create()
  74. ->filterByAuthorId(array(123, 456))
  75. ->find();
  76. // example Query generated for a MySQL database
  77. $query = 'SELECT book.* from `book`
  78. WHERE book.AUTHOR_ID IN (:p1, :p2)'; // :p1 => 123, :p2 => 456
  79. }}}
  80. * For Boolean columns, `filterByXXX()` translates the value to a boolean using smart casting:
  81. {{{
  82. #!php
  83. <?php
  84. $books = BookQuery::create()
  85. ->filterByIsPublished('yes')
  86. ->filterByIsSoldOut('no')
  87. ->find();
  88. // example Query generated for a MySQL database
  89. $query = 'SELECT book.* from `book`
  90. WHERE book.IS_PUBLISHED = :p1
  91. AND book.IS_SOLD_OUT = :p2'; // :p1 => true, :p2 => false
  92. }}}
  93. === Relation Filter Methods ===
  94. Propel also generates a `filterByXXX()` method for every foreign key. The filter expects an object of the related class as parameter:
  95. {{{
  96. #!php
  97. <?php
  98. $author = AuthorQuery::create()->findPk(123);
  99. $books = BookQuery::create()
  100. ->filterByAuthor($author)
  101. ->find();
  102. // example Query generated for a MySQL database
  103. $query = 'SELECT book.* from `book`
  104. WHERE book.AUTHOR_ID = :p1'; // :p1 => 123
  105. }}}
  106. Check the generated BaseQuery classes for a complete view of the generated query methods. Every generated method comes with a detailed phpDoc comment, making code completion very easy on supported IDEs.
  107. === Embedding a Related Query ===
  108. In order to add conditions on related tables, a propel query can ''embed'' the query of the related table. The generated `useXXXQuery()` serve that purpose. For instance, here is how to query the database for books written by 'Leo Tolstoi':
  109. {{{
  110. #!php
  111. <?php
  112. $books = BookQuery::create()
  113. ->useAuthorQuery()
  114. ->filterByName('Leo Tolstoi')
  115. ->endUse()
  116. ->find();
  117. }}}
  118. `useAuthorQuery()` returns a new instance of `AuthorQuery` already joined with the current `BookQuery` instance. The next method is therefore called on a different object - that's why the `filterByName()` call is further indented in the code example. Finally, `endUse()` merges the conditions applied on the `AuthorQuery` to the `BookQuery`, and returns the original `BookQuery` object.
  119. Propel knows how to join the `Book` model to the `Author` model, since you already defined a foreign key between the two tables in the `schema.xml`. Propel takes advantage of this knowledge of your model relationships to help you write faster queries and omit the most obvious data.
  120. {{{
  121. #!php
  122. <?php
  123. // example Query generated for a MySQL database
  124. $query = 'SELECT book.* from book
  125. INNER JOIN author ON book.AUTHOR_ID = author.ID
  126. WHERE author.NAME = :p1'; // :p1 => 'Leo Tolstoi'
  127. }}}
  128. You can customize the related table alias and the join type by passing arguments to the `useXXXQuery()` method:
  129. {{{
  130. #!php
  131. <?php
  132. $books = BookQuery::create()
  133. ->useAuthorQuery('a', 'left join')
  134. ->filterByName('Leo Tolstoi')
  135. ->endUse()
  136. ->find();
  137. // example Query generated for a MySQL database
  138. $query = 'SELECT book.* from book
  139. LEFT JOIN author a ON book.AUTHOR_ID = a.ID
  140. WHERE a.NAME = :p1'; // :p1 => 'Leo Tolstoi'
  141. }}}
  142. The `useXXXQuery()` methods allow for very complex queries. You can mix them, nest them, and reopen them to add more conditions.
  143. == Inherited Methods ==
  144. The generated Query classes extend a core Propel class named `ModelCriteria`, which provides even more methods for building your queries.
  145. === Finding An Object From Its Primary Key ===
  146. {{{
  147. #!php
  148. <?php
  149. // Finding the book having primary key 123
  150. $book = BookQuery::create()->findPk(123);
  151. // Finding the books having primary keys 123 and 456
  152. $books = BookQuery::create()->findPks(array(123, 456));
  153. // Also works for objects with composite primary keys
  154. $bookOpinion = BookOpinionQuery::create()->findPk(array($bookId, $userId));
  155. }}}
  156. === Finding Objects ===
  157. {{{
  158. #!php
  159. <?php
  160. // Finding all Books
  161. $articles = BookQuery::create()
  162. ->find();
  163. // Finding 3 Books
  164. $articles = BookQuery::create()
  165. ->limit(3)
  166. ->find();
  167. // Finding a single Book
  168. $article = BookQuery::create()
  169. ->findOne();
  170. }}}
  171. === Using Magic Query Methods ===
  172. {{{
  173. #!php
  174. <?php
  175. // The query recognizes method calls composed of `findOneBy` or `findBy`, and a column name.
  176. $book = BookQuery::create()->findOneByTitle('War And Peace');
  177. // same as
  178. $book = BookQuery::create()
  179. ->filterByTitle('War And Peace')
  180. ->findOne();
  181. $books = BookQuery::create()->findByTitle('War And Peace');
  182. // same as
  183. $books = BookQuery::create()
  184. ->filterByTitle('War And Peace')
  185. ->find();
  186. // You can even combine several column conditions in a method name, if you separate them with 'And'
  187. $book = BookQuery::create()->findOneByTitleAndAuthorId('War And Peace', 123);
  188. // same as
  189. $book = BookQuery::create()
  190. ->filterByTitle('War And Peace')
  191. ->filterById(123)
  192. ->findOne();
  193. }}}
  194. === Ordering Results ===
  195. {{{
  196. #!php
  197. <?php
  198. // Finding all Books ordered by published_at (ascending order by default)
  199. $books = BookQuery::create()
  200. ->orderByPublishedAt()
  201. ->find();
  202. // Finding all Books ordered by published_at desc
  203. $books = BookQuery::create()
  204. ->orderByPublishedAt('desc')
  205. ->find();
  206. }}}
  207. === Specifying A Connection ===
  208. {{{
  209. #!php
  210. <?php
  211. // All the termination methods accept a PropelPDO connection instance
  212. // So you can specify which connection to use
  213. $con = Propel::getConnection('bookstore', Propel::CONNECTION_READ);
  214. $nbBooks = BookQuery::create()
  215. ->findOne($con);
  216. }}}
  217. '''Tip''': In debug mode, the connection object provides a way to check the latest executed query, by calling `$con->getLastExecutedQuery()`. See the [wiki:Documentation/1.5/07-Logging Logging documentation] for more details.
  218. === Counting Objects ===
  219. {{{
  220. #!php
  221. <?php
  222. // Counting all Books
  223. $nbBooks = BookQuery::create()
  224. ->count($con);
  225. // This is much faster than counting the results of a find()
  226. // since count() doesn't populate Model objects
  227. }}}
  228. === Deleting Objects ===
  229. {{{
  230. #!php
  231. <?php
  232. // Deleting all Books
  233. $nbDeletedBooks = BookQuery::create()
  234. ->deleteAll($con);
  235. // Deleting a selection of Books
  236. $nbDeletedBooks = BookQuery::create()
  237. ->filterByTitle('Pride And Prejudice')
  238. ->delete($con);
  239. }}}
  240. === Updating Objects ===
  241. {{{
  242. #!php
  243. <?php
  244. // Test data
  245. $author1 = new Author();
  246. $author1->setName('Jane Austen');
  247. $author1->save();
  248. $author2 = new Author();
  249. $author2->setName('Leo Tolstoy');
  250. $author2->save();
  251. // update() issues an UPDATE ... SET query based on an associative array column => value
  252. $nbUpdatedRows = AuthorQuery::create()
  253. ->filterByName('Leo Tolstoy')
  254. ->update(array('Name' => 'Leo Tolstoi'), $con);
  255. // update() returns the number of modified columns
  256. echo $nbUpdatedRows; // 1
  257. // Beware that update() updates all records found in a single row
  258. // And bypasses any behavior registered on the save() hooks
  259. // You can force a one-by-one update by setting the third parameter of update() to true
  260. $nbUpdatedRows = AuthorQuery::create()
  261. ->filterByName('Leo Tolstoy')
  262. ->update(array('Name' => 'Leo Tolstoi'), $con, true);
  263. // Beware that it may take a long time
  264. }}}
  265. === Creating An Object Based on a Query ===
  266. You may often create a new object based on values used in conditions if a query returns no result. This happens a lot when dealing with cross-reference tables in many-to-many relationships. To avoid repeating yourself, use `findOneOrCreate()` instead of `findOne()` in such cases:
  267. {{{
  268. #!php
  269. <?php
  270. // The long way
  271. $bookTag = BookTagQuery::create()
  272. ->filterByBook($book)
  273. ->filterByTag('crime')
  274. ->findOne();
  275. if (!$bookTag) {
  276. $bookTag = new BookTag();
  277. $bookTag->setBook($book);
  278. $bookTag->setTag('crime');
  279. }
  280. // The short way
  281. $bookTag = BookTagQuery::create()
  282. ->filterByBook($book)
  283. ->filterByTag('crime')
  284. ->findOneOrCreate();
  285. }}}
  286. === Reusing A Query ===
  287. By default, termination methods like `findOne()`, `find()`, `count()`, `paginate()`, or `delete()` alter the original query. That means that if you need to reuse a query after a termination method, you must call the `keepQuery()` method first:
  288. {{{
  289. #!php
  290. <?php
  291. $q = BookQuery::create()->filterByIsPublished(true);
  292. $book = $q->findOneByTitle('War And Peace');
  293. // findOneByXXX() adds a limit() to the query
  294. // so further reuses of the query may show side effects
  295. echo $q->count(); // 1
  296. // to allos query reuse, call keepQuery() before the termination method
  297. $q = BookQuery::create()->filterByIsPublished(true)->keepQuery();
  298. $book = $q->findOneByTitle('War And Peace');
  299. echo $q->count(); // 34
  300. }}}
  301. == Relational API ==
  302. For more complex queries, you can use an alternative set of methods, closer to the relational logic of SQL, to make sure that Propel issues exactly the SQL query you need.
  303. This alternative API uses methods like `where()`, `join()` and `orderBy()` that translate directly to their SQL equivalent - `WHERE`, `JOIN`, etc. Here is an example:
  304. {{{
  305. #!php
  306. <?php
  307. $books = BookQuery::create()
  308. ->join('Book.Author')
  309. ->where('Author.Name = ?', 'Leo Tolstoi')
  310. ->orderBy('Book.Title', 'asc')
  311. ->find();
  312. }}}
  313. The names passed as parameters in these methods, like 'Book.Author', 'Author.Name', and 'Book.Title', are ''explicit column names''. These names are composed of the phpName of the model, and the phpName of the column, separated by a dot (e.g. 'Author.Name'). Manipulating object model names allows you to be detached from the actual data storage, and alter the database names without necessarily updating the PHP code. It also makes the use of table aliases much easier - more on that matter later.
  314. Propel knows how to map the explicit column names to database column names in order to translate the Propel query into an actual database query:
  315. {{{
  316. #!php
  317. <?php
  318. $query = 'SELECT book.* from book
  319. INNER JOIN author ON book.AUTHOR_ID = author.ID
  320. WHERE author.NAME = :p1
  321. ORDER BY book.TITLE ASC';
  322. }}}
  323. In a `where()` call, the condition appears as a string. `'Author.Name = ?'` is such a condition. Propel uses the column name in conjunction with the schema to determine the column type. In this case, `author.name` is defined in the schema as a `VARCHAR`. Then, Propel binds the value to the condition using PDO and the correct column type, as when using a `filterByXXX()` method.
  324. '''Tip''': Of course, you can mix the generated methods from your BaseQuery objects and the relational API methods in the same query object.
  325. Let's dive in the alternative API.
  326. === Adding A Simple Condition ===
  327. {{{
  328. #!php
  329. <?php
  330. // Finding all Books where title = 'War And Peace'
  331. $books = BookQuery::create()
  332. ->where('Book.Title = ?', 'War And Peace')
  333. ->find();
  334. // Finding all Books where title is like 'War%'
  335. $books = BookQuery::create()
  336. ->where('Book.Title LIKE ?', 'War%')
  337. ->find();
  338. // Finding all Books published after $date
  339. $books = BookQuery::create()
  340. ->where('Book.PublishedAt > ?', $date)
  341. ->find();
  342. // Finding all Books with no author
  343. $books = BookQuery::create()
  344. ->where('Book.AuthorId IS NULL')
  345. ->find();
  346. // Finding all books from a list of authors
  347. $books = BookQuery::create()
  348. ->where('Book.AuthorId IN ?', array(123, 542, 563))
  349. ->find();
  350. // You can even use SQL functions inside conditions
  351. $books = BookQuery::create()
  352. ->where('UPPER(Book.Title) = ?', 'WAR AND PEACE')
  353. ->find();
  354. }}}
  355. === Combining Several Conditions ===
  356. For speed reasons, `where()` only accepts simple conditions, with a single interrogation point for the value replacement. When you need to apply more than one condition, and combine them with a logical operator, you have to call `where()` multiple times.
  357. {{{
  358. #!php
  359. <?php
  360. // Finding all books where title = 'War And Peace' and published after $date
  361. $books = BookQuery::create()
  362. ->where('Book.Title = ?', 'War And Peace')
  363. ->where('Book.PublishedAt > ?', $date)
  364. ->find();
  365. // For conditions chained with OR, use orWhere() instead of where()
  366. $books = BookQuery::create()
  367. ->where('Book.Title = ?', 'War And Peace')
  368. ->orWhere('Book.Title LIKE ?', 'War%')
  369. ->find();
  370. }}}
  371. The use of `where()` and `orWhere()` doesn't allow logically complex conditions, that you would write in SQL with parenthesis. For such cases, create named conditions with `condition()`, and then combine them in an array that you can pass to `where()` instead of a single condition, as follows:
  372. {{{
  373. #!php
  374. <?php
  375. // Finding all books where title = 'War And Peace' or like 'War%'
  376. $books = BookQuery::create()
  377. ->condition('cond1', 'Book.Title = ?', 'War And Peace') // create a condition named 'cond1'
  378. ->condition('cond2', 'Book.Title LIKE ?', 'War%') // create a condition named 'cond2'
  379. ->where(array('cond1', 'cond2'), 'or')-> // combine 'cond1' and 'cond2' with a logical OR
  380. ->find();
  381. // SELECT book.* from book WHERE (book.TITLE = 'War And Peace' OR book.TITLE LIKE 'War%');
  382. // You can create a named condition from the combination of other named conditions by using `combine()`
  383. // That allows for any level of complexity
  384. $books = BookQuery::create()
  385. ->condition('cond1', 'Book.Title = ?', 'War And Peace') // create a condition named 'cond1'
  386. ->condition('cond2', 'Book.Title LIKE ?', 'War%') // create a condition named 'cond2'
  387. ->combine(array('cond1', 'cond2'), 'or', 'cond12') // create a condition named 'cond12' from 'cond1' and 'cond2'
  388. ->condition('cond3', 'Book.PublishedAt <= ?', $end) // create a condition named 'cond3'
  389. ->condition('cond4', 'Book.PublishedAt >= ?', $begin) // create a condition named 'cond4'
  390. ->combine(array('cond3', 'cond4'), 'and', 'cond34') // create a condition named 'cond34' from 'cond3' and 'cond4'
  391. ->where(array('cond12', 'cond34'), 'and') // combine the two conditions in a where
  392. ->find();
  393. // SELECT book.* FROM book WHERE (
  394. // (book.TITLE = 'War And Peace' OR book.TITLE LIKE 'War%')
  395. // AND
  396. // (book.PUBLISHED_AT <= $end AND book.PUBLISHED_AT >= $begin)
  397. // );
  398. }}}
  399. === Joining Tables ===
  400. {{{
  401. #!php
  402. <?php
  403. // Test data
  404. $author1 = new Book();
  405. $author1->setName('Jane Austen');
  406. $author1->save();
  407. $book1 = new Book();
  408. $book1->setTitle('Pride And Prejudice');
  409. $book1->setAuthor($author1);
  410. $book1->save();
  411. // Add a join statement
  412. // No need to tell the query which columns to use for the join, just the related Class
  413. // After all, the columns of the FK are already defined in the schema.
  414. $book = BookQuery::create()
  415. ->join('Book.Author')
  416. ->where('Author.Name = ?', 'Jane Austen')
  417. ->findOne();
  418. // SELECT book.* FROM book
  419. // INNER JOIN author ON book.AUTHOR_ID = author.ID
  420. // WHERE author.NAME = 'Jane Austin'
  421. // LIMIT 1;
  422. // The default join() call results in a SQL INNER JOIN clause
  423. // For LEFT JOIN or RIGHT JOIN clauses, use leftJoin() or rightJoin() instead of join()
  424. $book = BookQuery::create()
  425. ->leftJoin('Book.Author')
  426. ->where('Author.Name = ?', 'Jane Austen')
  427. ->findOne();
  428. // You can chain joins if you want to make more complex queries
  429. $review = new Review();
  430. $review->setBook($book1);
  431. $review->setRecommended(true);
  432. $review->save();
  433. $author = BookQuery::create()
  434. ->join('Author.Book')
  435. ->join('Book.Review')
  436. ->where('Review.Recommended = ?', true)
  437. ->findOne();
  438. // Alternatively, you can use the generated joinXXX() methods
  439. // Which are a bit faster than join(), but limited to the current model's relationships
  440. $book = BookQuery::create()
  441. ->joinAuthor()
  442. ->where('Author.Name = ?', 'Jane Austen')
  443. ->findOne();
  444. // The join type depends on the required attribute of the foreign key column
  445. // If the column is required, then the default join type is an INNER JOIN
  446. // Otherwise, the default join type is a LEFT JOIN
  447. // You can override the default join type for a given relationship
  448. // By setting the joinType attribute of the foreign key element in the schema.xml
  449. }}}
  450. === Table Aliases ===
  451. {{{
  452. #!php
  453. <?php
  454. // The first argument of BookQuery::create() defines a table alias
  455. $books = BookQuery::create('b')
  456. ->where('b.Title = ?', 'Pride And Prejudice')
  457. ->find();
  458. // join(), leftJoin() and rightJoin() also allow table aliases
  459. $author = AuthorQuery::create('a')
  460. ->join('a.Book b')
  461. ->join('b.Review r')
  462. ->where('r.Recommended = ?', true)
  463. ->findOne();
  464. // Table aliases can be used in all query methods (where, groupBy, orderBy, etc.)
  465. $books = BookQuery::create('b')
  466. ->where('b.Title = ?', 'Pride And Prejudice')
  467. ->orderBy('b.Title')
  468. ->find();
  469. // Table aliases are mostly useful to join the current table,
  470. // or to handle multiple foreign keys on the same column
  471. $employee = EmployeeQuery::create('e')
  472. ->innerJoin('e.Supervisor s')
  473. ->where('s.Name = ?', 'John')
  474. ->find();
  475. }}}
  476. === Minimizing Queries ===
  477. Even if you do a join, Propel will issue new queries when you fetch related objects:
  478. {{{
  479. #!php
  480. <?php
  481. $book = BookQuery::create()
  482. ->join('Book.Author')
  483. ->where('Author.Name = ?', 'Jane Austen')
  484. ->findOne();
  485. $author = $book->getAuthor(); // Needs another database query
  486. }}}
  487. Propel allows you to retrieve the main object together with related objects in a single query. You just have to call the `with()` method to specify which objects the main object should be hydrated with.
  488. {{{
  489. #!php
  490. <?php
  491. $book = BookQuery::create()
  492. ->join('Book.Author')
  493. ->with('Author')
  494. ->where('Author.Name = ?', 'Jane Austen')
  495. ->findOne();
  496. $author = $book->getAuthor(); // Same result, with no supplementary query
  497. }}}
  498. `with()` expects a relation name, as declared previously by `join()`. In practice, that means that `with()` and `join()` should always come one after the other. To avoid repetition, use `joinWith()` to both add a `join()` and a `with()` on a relation. So the shorter way to write the previous query is:
  499. {{{
  500. #!php
  501. <?php
  502. $book = BookQuery::create()
  503. ->joinWith('Book.Author')
  504. ->where('Author.Name = ?', 'Jane Austen')
  505. ->findOne();
  506. $author = $book->getAuthor(); // Same result, with no supplementary query
  507. }}}
  508. Since the call to `with()` adds the columns of the related object to the SELECT part of the query, and uses these columns to populate the related object, that means that `joinWith()` is slower and consumes more memory that `join()`. So use it only when you actually need the related objects afterwards.
  509. `with()` and `joinWith()` are not limited to immediate relationships. As a matter of fact, just like you can chain `join()` calls, you can chain `joinWith()` calls to populate a chain of objects:
  510. {{{
  511. #!php
  512. <?php
  513. $review = ReviewQuery::create()
  514. ->joinWith('Review.Book')
  515. ->joinWith('Book.Author')
  516. ->joinWith('Book.Publisher')
  517. ->findOne();
  518. $book = $review->getBook() // No additional query needed
  519. $author = $book->getAuthor(); // No additional query needed
  520. $publisher = $book->getPublisher(); // No additional query needed
  521. }}}
  522. So `joinWith()` is very useful to minimize the number of database queries. As soon as you see that the number of queries necessary to perform an action is proportional to the number of results, adding `With` after `join()` calls is the trick to get down to a more reasonnable query count.
  523. === Adding Columns ===
  524. Sometimes you don't need to hydrate a full object in addition to the main object. If you only need one additional column, the `withColumn()` method is a good alternative to `joinWith()`, and it speeds up the query:
  525. {{{
  526. #!php
  527. <?php
  528. $book = BookQuery::create()
  529. ->join('Book.Author')
  530. ->withColumn('Author.Name', 'AuthorName')
  531. ->findOne();
  532. $authorName = $book->getAuthorName();
  533. }}}
  534. Propel adds the 'with' column to the SELECT clause of the query, and uses the second argument of the `withColumn()` call as a column alias. This additional column is later available as a 'virtual' column, i.e. using a getter that does not correspond to a real column. You don't actually need to write the `getAuthorName()` method ; Propel uses the magic `__call()` method of the generated `Book` class to catch the call to a virtual column.
  535. `withColumn()` is also of great use to add calculated columns:
  536. {{{
  537. #!php
  538. <?php
  539. $authors = AuthorQuery::create()
  540. ->join('Author.Book')
  541. ->withColumn('COUNT(Book.Id)', 'NbBooks')
  542. ->groupBy('Author.Id')
  543. ->find();
  544. foreach ($authors as $author) {
  545. echo $author->getName() . ': ' . $author->getNbBooks() . " books\n";
  546. }
  547. }}}
  548. With a single SQL query, you can have both a list of objects and an additional column for each object. That makes of `withColumn()` a great query saver.
  549. '''Tip''': You can call `withColumn()` multiple times to add more than one virtual column to the resulting objects.
  550. === Adding A Comment ===
  551. {{{
  552. #!php
  553. <?php
  554. // You can add a comment to the query object
  555. // For easier SQL log parsing
  556. AuthorQuery::create()
  557. ->setComment('Author Deletion')
  558. ->filterByName('Leo Tolstoy')
  559. ->delete($con);
  560. // The comment ends up in the generated SQL query
  561. // DELETE /* Author Deletion */ FROM `author` WHERE author.NAME = 'Leo Tolstoy'
  562. }}}
  563. === Using Methods From Another Query Class ===
  564. After writing custom methods to query objects, developers often meet the need to use the method from another query. For instance, in order to select the authors of the most recent books, you may want to write:
  565. {{{
  566. #!php
  567. <?php
  568. // This doesn't work
  569. $authors = AuthorQuery::create()
  570. ->join('Author.Book')
  571. ->recent()
  572. ->find();
  573. }}}
  574. The problem is that `recent()` is a method of `BookQuery`, not of the `AuthorQuery` class that the `create()` factory returns.
  575. Does that mean that you must repeat the `BookQuery::recent()` code into a new `AuthorQuery::recentBooks()` method? That would imply repeating the same code in two classes, which is not a good practice. Instead, use the `useQuery()` and `endUse()` combination to use the methods of `BookQuery` inside `AuthorQuery`:
  576. {{{
  577. #!php
  578. <?php
  579. // This works
  580. $authors = AuthorQuery::create()
  581. ->join('Author.Book')
  582. ->useQuery('Book')
  583. ->recent()
  584. ->endUse()
  585. ->find();
  586. }}}
  587. This is excatly whath the generated `useBookQuery()` does, except that you have more control over the join type and alias when you use the relational API. Behind the scene, `useQuery('Book')` creates a `BookQuery` instance and returns it. So the `recent()` call is actually called on `BookQuery`, not on `ArticleQuery`. Upon calling `endUse()`, the `BookQuery` merges into the original `ArticleQuery` and returns it. So the final `find()` is indeed called on the `AuthorQuery` instance.
  588. You can nest queries in as many levels as you like, in order to avoid the repetition of code in your model.
  589. '''Tip''': If you define an alias for the relation in `join()`, you must pass this alias instead of the model name in `useQuery()`.
  590. {{{
  591. #!php
  592. <?php
  593. $authors = AuthorQuery::create('a')
  594. ->join('a.Book b')
  595. ->useQuery('b')
  596. ->recent()
  597. ->endUse()
  598. ->find();
  599. }}}
  600. === Fluid Conditions ===
  601. Thanks to the query factories and the fluid interface, developers can query the database without creating a variable for the Query object. This helps a lot to reduce the amount of code necessary to write a query, and it also makes the code more readable.
  602. But when you need to call a method on a Query object only if a certain condition is satisfied, it becomes compulsory to use a variable for the Query object:
  603. {{{
  604. #!php
  605. <?php
  606. // find all the published books, except if the user is an editor,
  607. // in which case also include non-published books
  608. $query = BookQuery::create();
  609. if (!$user->isEditor()) {
  610. $query->where('Book.IsPublished = ?', true);
  611. }
  612. $books = $query
  613. ->orderByTitle()
  614. ->find();
  615. }}}
  616. The `ModelCriteria` class offers a neat way to keep your code to a minimum in such occasions. It provides `_if()` and `_endif()` methods allowing for inline conditions. Using thses methods, the previous query can be written as follows:
  617. {{{
  618. #!php
  619. <?php
  620. // find all the published books, except if the user is an editor
  621. $books = BookQuery::create()
  622. ->_if(!$user->isEditor())
  623. ->where('Book.IsPublished = ?', true)
  624. ->_endif()
  625. ->orderByTitle()
  626. ->find();
  627. }}}
  628. The method calls enclosed between `_if($cond)` and `_endif()` will only be executed if the condition is true. To complete the list of tools available for fluid conditions, you can also use `_else()` and `_elseif($cond)`.
  629. === More Complex Queries ===
  630. The Propel Query objects have even more methods that allow you to write queries of any level of complexity. Check the API documentation for the `ModelCriteria` class to see all methods.
  631. {{{
  632. #!php
  633. <?php
  634. // Query Filters (return a query object)
  635. distinct()
  636. limit($limit)
  637. offset($offset)
  638. where($clause, $value)
  639. where($conditions, $operator)
  640. filterBy($column, $value, $comparison)
  641. filterByArray($conditions)
  642. orWhere($conditions, $operator)
  643. condition($name, $clause, $value)
  644. combine($conditions, $operator = 'and', $name)
  645. having($clause, $value)
  646. having($conditions, $operator)
  647. orderBy($columnName, $order = 'asc')
  648. groupBy($columnName)
  649. join($class, $joinType = 'inner join')
  650. with($relation, $joinType = 'inner join')
  651. withColumn($clause, $alias)
  652. prune($object)
  653. comment($comment)
  654. // termination methods (return model objects)
  655. count($con = null)
  656. find($con = null)
  657. findOne($con = null)
  658. finOneOrCreate($con = null)
  659. findBy($columnName, $value, $con = null)
  660. findByArray($conditions, $con = null)
  661. findOneBy($columnName, $value, $con = null)
  662. findByOneArray($conditions, $con = null)
  663. findPk($pk, $con = null)
  664. findPks($pks, $con = null)
  665. delete($con = null)
  666. update($values, $con = null, $forceIndividualSaves = false)
  667. }}}
  668. == Collections and Formatters ==
  669. === PropelCollection Methods ===
  670. {{{
  671. #!php
  672. <?php
  673. // find() returns a PropelCollection, which you can use just like an array
  674. $books = BookQuery::create()->find(); // $books behaves like an array
  675. ?>
  676. There are <?php echo count($books) ?> books:
  677. <ul>
  678. <?php foreach ($books as $book): ?>
  679. <li>
  680. <?php echo $book->getTitle() ?>
  681. </li>
  682. <?php endforeach; ?>
  683. </ul>
  684. <?php
  685. // But a PropelCollection is more than just an array.
  686. // That means you can call some special methods on it.
  687. $books = BookQuery::create()->find(); // $books is an object
  688. ?>
  689. <?php if($books->isEmpty()): ?>
  690. There are no books.
  691. <?php else: ?>
  692. There are <?php echo $books->count() ?> books:
  693. <ul>
  694. <?php foreach ($books as $book): ?>
  695. <li class="<?php echo $books->isOdd() ? 'odd' : 'even' ?>">
  696. <?php echo $book->getTitle() ?>
  697. </li>
  698. <?php if($books->isLast()): ?>
  699. <li>Do you want more books?</li>
  700. <?php endif; ?>
  701. <?php endforeach; ?>
  702. </ul>
  703. <?php endif; ?>
  704. }}}
  705. Here is the list of methods you can call on a PropelCollection:
  706. {{{
  707. #!php
  708. <?php
  709. // introspection methods
  710. array getData() // return a copy of the result array
  711. // information methods on the current element in the method
  712. bool isFirst()
  713. bool isLast()
  714. bool isEmpty()
  715. bool isOdd()
  716. bool isEven()
  717. bool contains($value)
  718. // access methods
  719. mixed getFirst()
  720. mixed getPrevious()
  721. mixed getCurrent()
  722. mixed getKey()
  723. mixed getNext()
  724. mixed getLast()
  725. mixed get($position)
  726. mixed search($value)
  727. // manipulation methods
  728. mixed pop()
  729. mixed shift()
  730. void append($value)
  731. int prepend($value)
  732. mixed set($position, $value)
  733. mixed remove($position)
  734. mixed clear()
  735. // Model methods
  736. void save() // save all the objects in the collection
  737. void delete() // delete all the objects in the collection
  738. array getPrimaryKeys() // get an array of the primary keys of all the objects in the collection
  739. coll populateRelation($name) // makes an additional query to populate the objects related to the current collection objects
  740. // Import/Export methods
  741. array toArray() // exports all the objects as array
  742. array toKeyValue($keyColumn, $valueColumn) // exports two columns as a hash
  743. void fromArray($array) // imports a collection from an array
  744. }}}
  745. '''Tip''': `PropelCollection` extends `ArrayObject`, so you can also call all the methods of this SPL class on a collection (including `count()`, `append()`, `ksort()`, etc.).
  746. === Using An Alternative Formatter ===
  747. By default, `find()` calls return a `PropelObjectCollection` of model objects. For performance reasons, you may want to get a collection of arrays instead. Use the `setFormatter()` to specify a custom result formatter.
  748. {{{
  749. #!php
  750. <?php
  751. $book = BookQuery::create()
  752. ->setFormatter('PropelArrayFormatter')
  753. ->findOne();
  754. print_r($book);
  755. => array('Id' => 123, 'Title' => 'War And Peace', 'ISBN' => '3245234535', 'AuthorId' => 456, 'PublisherId' => 567)
  756. }}}
  757. Of course, the formatters take the calls to `with()` into account, so you can end up with a precise array representation of a model object:
  758. {{{
  759. #!php
  760. <?php
  761. $book = BookQuery::create()
  762. ->setFormatter('PropelArrayFormatter')
  763. ->with('Book.Author')
  764. ->with('Book.Publisher')
  765. ->findOne();
  766. print_r($book);
  767. => array(
  768. 'Id' => 123,
  769. 'Title' => 'War And Peace',
  770. 'ISBN' => '3245234535',
  771. 'AuthorId' => 456,
  772. 'PublisherId' => 567
  773. 'Author' => array(
  774. 'Id' => 456,
  775. 'FirstName' => 'Leo',
  776. 'LastName' => 'Tolstoi'
  777. ),
  778. 'Publisher' => array(
  779. 'Id' => 567,
  780. 'Name' => 'Penguin'
  781. )
  782. )
  783. }}}
  784. Propel provides four formatters:
  785. * `PropelObjectFormatter`: The default formatter, returning a model object for `findOne()`, and a `PropelObjectCollection` of model objects for `find()`
  786. * `PropelOnDemandFormatter`: To save memory for large resultsets, prefer this formatter ; it hydrates rows one by one as they are iterated on, and doesn't create a new Propel Model object at each row. Note that this formatter doesn't use the Instance Pool.
  787. * `PropelArrayFormatter`: The array formatter, returning an associative array for `findOne()`, and a `PropelArrayCollection` of arrays for `find()`
  788. * `PropelStatementFormatter`: The 'raw' formatter, returning a `PDOStatement` in any case.
  789. You can easily write your own formatter to format the resultas the way you want. A formatter is basically a subclass of `PropelFormatter` providing a `format()` and a `formatOne()` method expecting a PDO statement.
  790. == Writing Your Own business Logic Into A Query ==
  791. === Custom Filters ===
  792. You can add custom methods to the query objects to make your queries smarter, more reusable, and more readable. Don't forget to return the current object (`$this`) in the new methods.
  793. {{{
  794. #!php
  795. <?php
  796. class BookQuery extends BaseBookQuery
  797. {
  798. public function recent($nbDays = 5)
  799. {
  800. return $this->filterByPublishedAt(array('min' => time() - $nbDays * 24 * 60 * 60));
  801. }
  802. public function mostRecentFirst()
  803. {
  804. return $this->orderByPublishedAt('desc');
  805. }
  806. }
  807. // You can now use your custom query and its methods together with the usual ones
  808. $books = BookQuery::create()
  809. ->recent()
  810. ->mostRecentFirst()
  811. ->find();
  812. }}}
  813. === Custom Hooks ===
  814. The query objects also allow you to add code to be executed before each query, by implementing one of the following methods: `preSelect()`, `preUpdate()`, and `preDelete()`. It makes the implementation of a 'soft delete' behavior very straightforward:
  815. {{{
  816. #!php
  817. <?php
  818. class BookQuery extends BaseBookQuery
  819. {
  820. public function preSelect(PropelPDO $con)
  821. {
  822. // filter out the rows with a deletion date
  823. $this->filterByDeletedAt(null);
  824. }
  825. public function preDelete($con)
  826. {
  827. // mark the records as deleted instead of deleting them
  828. return $this->update(array('DeletedAt' => time()));
  829. }
  830. }
  831. }}}
  832. '''Tip''': You can create several custom queries for a given model, in order to separate the methods into logical classes.
  833. {{{
  834. #!php
  835. <?php
  836. class frontendBookQuery extends BookQuery
  837. {
  838. public function preSelect()
  839. {
  840. return $this->where($this->getModelAliasOrName() . '.PublishedAt IS NOT NULL');
  841. }
  842. }
  843. // Use 'frontendBook' instead of 'Book' in the frontend to retrieve only published articles
  844. $q = new frontendBookQuery();
  845. $books = $q->find();
  846. }}}
  847. '''Tip''': Due to late static binding issues in PHP 5.2, you cannot use the `create()` factory on an inherited query - unless you override it yoursel in the descendant class. Alternatively, Propel offers a global query factory named `PropelQuery`:
  848. {{{
  849. #!php
  850. <?php
  851. // Use 'frontendBook' instead of 'Book' in the frontend to retrieve only published articles
  852. $books = PropelQuery::from('frontendBook')->find();
  853. }}}