# Source for file Parser.php

Documentation is available at Parser.php

1. <?php
2. /*
3. * $Id: Table.php 1397 2007-05-19 19:54:15Z zYne $
4. *
5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16. *
17. * This software consists of voluntary contributions made by many individuals
18. * and is licensed under the LGPL. For more information, see
19. * <http://www.phpdoctrine.com>.
20. */
21. /**
22. * Doctrine_Relation_Parser
23. *
24. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
25. * @package Doctrine
26. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
27. * @version $Revision: 1397 $
28. * @category Object Relational Mapping
29. * @link www.phpdoctrine.com
30. * @since 1.0
31. */
32. class Doctrine_Relation_Parser
33. {
34. /**
35. * @var Doctrine_Table $_table the table object this parser belongs to
36. */
37. protected $_table;
38. /**
39. * @var array $_relations an array containing all the Doctrine_Relation objects for this table
40. */
41. protected $_relations = array();
42. /**
43. * @var array $_pending relations waiting for parsing
44. */
45. protected $_pending = array();
46. /**
47. * constructor
48. *
49. * @param Doctrine_Table $table the table object this parser belongs to
50. */
51. public function __construct(Doctrine_Table $table)
52. {
53. $this->_table = $table;
54. }
55. /**
56. * getTable
57. *
58. * @return Doctrine_Table the table object this parser belongs to
59. */
60. public function getTable()
61. {
62. return $this->_table;
63. }
64. /**
65. * getPendingRelation
66. *
67. * @return array an array defining a pending relation
68. */
69. public function getPendingRelation($name)
70. {
71. if ( ! isset($this->_pending[$name])) {
72. throw new Doctrine_Relation_Exception('Unknown pending relation ' . $name);
73. }
75. return $this->_pending[$name];
76. }
78. public function hasRelation($name)
79. {
80. if ( ! isset($this->_pending[$name]) && ! isset($this->_relations[$name])) {
81. return false;
82. }
84. return true;
85. }
86. /**
87. * binds a relation
88. *
89. * @param string $name
90. * @param string $field
91. * @return void
92. */
93. public function bind($name, $options = array())
94. {
95. if (isset($this->relations[$name])) {
96. unset($this->relations[$name]);
97. }
99. $lower = strtolower($name);
101. if ($this->_table->hasColumn($lower)) {
102. throw new Doctrine_Relation_Exception("Couldn't bind relation. Column with name " . $lower . ' already exists!');
103. }
105. $e = explode(' as ', $name);
106. $name = $e[0];
107. $alias = isset($e[1]) ? $e[1] : $name;
109. if ( ! isset($options['type'])) {
110. throw new Doctrine_Relation_Exception('Relation type not set.');
111. }
113. $this->_pending[$alias] = array_merge($options, array('class' => $name, 'alias' => $alias));
115. $m = Doctrine_Manager::getInstance();
117. if (isset($options['onDelete'])) {
118. $m->addDeleteAction($name, $this->_table->getComponentName(), $options['onDelete']);
119. }
120. if (isset($options['onUpdate'])) {
121. $m->addUpdateAction($name, $this->_table->getComponentName(), $options['onUpdate']);
122. }
124. return $this->_pending[$alias];
125. }
126. /**
127. * getRelation
128. *
129. * @param string $alias relation alias
130. */
131. public function getRelation($alias, $recursive = true)
132. {
133. if (isset($this->_relations[$alias])) {
134. return $this->_relations[$alias];
135. }
137. if (isset($this->_pending[$alias])) {
138. $def = $this->_pending[$alias];
140. // check if reference class name exists
141. // if it does we are dealing with association relation
142. if (isset($def['refClass'])) {
143. $def = $this->completeAssocDefinition($def);
144. $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
146. if ( ! isset($this->_pending[$def['refClass']]) &&
147. ! isset($this->_relations[$def['refClass']])) {
149. $parser = $def['refTable']->getRelationParser();
150. if ( ! $parser->hasRelation($this->_table->getComponentName())) {
151. $parser->bind($this->_table->getComponentName(),
152. array('type' => Doctrine_Relation::ONE,
153. 'local' => $def['local'],
154. 'foreign' => $this->_table->getIdentifier(),
155. 'localKey' => true,
156. ));
157. }
159. if ( ! $this->hasRelation($def['refClass'])) {
160. $this->bind($def['refClass'], array('type' => Doctrine_Relation::MANY,
161. 'foreign' => $def['local'],
162. 'local' => $this->_table->getIdentifier()));
163. }
164. }
165. if (in_array($def['class'], $localClasses)) {
166. $rel = new Doctrine_Relation_Nest($def);
167. } else {
168. $rel = new Doctrine_Relation_Association($def);
169. }
170. } else {
171. // simple foreign key relation
172. $def = $this->completeDefinition($def);
174. if (isset($def['localKey'])) {
176. $rel = new Doctrine_Relation_LocalKey($def);
177. } else {
178. $rel = new Doctrine_Relation_ForeignKey($def);
179. }
180. }
181. if (isset($rel)) {
182. // unset pending relation
183. unset($this->_pending[$alias]);
185. $this->_relations[$alias] = $rel;
186. return $rel;
187. }
188. }
189. if ($recursive) {
190. $this->getRelations();
192. return $this->getRelation($alias, false);
193. } else {
194. throw new Doctrine_Table_Exception('Unknown relation alias ' . $alias);
195. }
196. }
197. /**
198. * getRelations
199. * returns an array containing all relation objects
200. *
201. * @return array an array of Doctrine_Relation objects
202. */
203. public function getRelations()
204. {
205. foreach ($this->_pending as $k => $v) {
206. $this->getRelation($k);
207. }
209. return $this->_relations;
210. }
211. /**
212. * getImpl
213. * returns the table class of the concrete implementation for given template
214. * if the given template is not a template then this method just returns the
215. * table class for the given record
216. *
217. * @param string $template
218. */
219. public function getImpl($template)
220. {
221. $conn = $this->_table->getConnection();
223. if (in_array('Doctrine_Template', class_parents($template))) {
224. $impl = $this->_table->getImpl($template);
226. if ($impl === null) {
227. throw new Doctrine_Relation_Parser_Exception("Couldn't find concrete implementation for template " . $template);
228. }
229. } else {
230. $impl = $template;
231. }
233. return $conn->getTable($impl);
234. }
235. /**
236. * Completes the given association definition
237. *
238. * @param array $def definition array to be completed
239. * @return array completed definition array
240. */
241. public function completeAssocDefinition($def)
242. {
243. $conn = $this->_table->getConnection();
244. $def['table'] = $this->getImpl($def['class']);
245. $def['class'] = $def['table']->getComponentName();
246. $def['refTable'] = $this->getImpl($def['refClass']);
248. $id = $def['refTable']->getIdentifier();
250. if (count($id) > 1) {
251. if ( ! isset($def['foreign'])) {
252. // foreign key not set
253. // try to guess the foreign key
255. $def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0];
256. }
257. if ( ! isset($def['local'])) {
258. // foreign key not set
259. // try to guess the foreign key
261. $def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0];
262. }
263. } else {
265. if ( ! isset($def['foreign'])) {
266. // foreign key not set
267. // try to guess the foreign key
269. $columns = $this->getIdentifiers($def['table']);
271. $def['foreign'] = $columns;
272. }
273. if ( ! isset($def['local'])) {
274. // local key not set
275. // try to guess the local key
276. $columns = $this->getIdentifiers($this->_table);
278. $def['local'] = $columns;
279. }
280. }
281. return $def;
282. }
283. /**
284. * getIdentifiers
285. * gives a list of identifiers from given table
286. *
287. * the identifiers are in format:
288. * [componentName].[identifier]
289. *
290. * @param Doctrine_Table $table table object to retrieve identifiers from
291. */
292. public function getIdentifiers(Doctrine_Table $table)
293. {
294. if (is_array($table->getIdentifier())) {
295. $columns = array();
296. foreach((array) $table->getIdentifier() as $identifier) {
297. $columns[] = strtolower($table->getComponentName())
298. . '_' . $table->getIdentifier();
299. }
300. } else {
301. $columns = strtolower($table->getComponentName())
302. . '_' . $table->getIdentifier();
303. }
305. return $columns;
306. }
307. /**
308. * guessColumns
309. *
310. * @param array $classes an array of class names
311. * @param Doctrine_Table $foreignTable foreign table object
312. * @return array an array of column names
313. */
314. public function guessColumns(array $classes, Doctrine_Table $foreignTable)
315. {
316. $conn = $this->_table->getConnection();
318. foreach ($classes as $class) {
319. try {
320. $table = $conn->getTable($class);
321. } catch (Doctrine_Table_Exception $e) {
322. continue;
323. }
324. $columns = $this->getIdentifiers($table);
325. $found = true;
327. foreach ((array) $columns as $column) {
328. if ( ! $foreignTable->hasColumn($column)) {
329. $found = false;
330. break;
331. }
332. }
333. if ($found) {
334. break;
335. }
336. }
338. if ( ! $found) {
339. throw new Doctrine_Relation_Exception("Couldn't find columns.");
340. }
342. return $columns;
343. }
344. /**
345. * Completes the given definition
346. *
347. * @param array $def definition array to be completed
348. * @return array completed definition array
349. */
350. public function completeDefinition($def)
351. {
352. $conn = $this->_table->getConnection();
353. $def['table'] = $this->getImpl($def['class']);
354. $def['class'] = $def['table']->getComponentName();
356. $foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class']));
357. $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
359. if (isset($def['local'])) {
360. if ( ! isset($def['foreign'])) {
361. // local key is set, but foreign key is not
362. // try to guess the foreign key
364. if ($def['local'] === $this->_table->getIdentifier()) {
365. $def['foreign'] = $this->guessColumns($localClasses, $def['table']);
366. } else {
367. // the foreign field is likely to be the
368. // identifier of the foreign class
369. $def['foreign'] = $def['table']->getIdentifier();
370. $def['localKey'] = true;
371. }
372. } else {
373. if ($def['local'] !== $this->_table->getIdentifier() &&
374. $def['type'] == Doctrine_Relation::ONE) {
375. $def['localKey'] = true;
376. }
377. }
378. } else {
379. if (isset($def['foreign'])) {
380. // local key not set, but foreign key is set
381. // try to guess the local key
382. if ($def['foreign'] === $def['table']->getIdentifier()) {
383. $def['localKey'] = true;
384. try {
385. $def['local'] = $this->guessColumns($foreignClasses, $this->_table);
386. } catch (Doctrine_Relation_Exception $e) {
387. $def['local'] = $this->_table->getIdentifier();
388. }
389. } else {
390. $def['local'] = $this->_table->getIdentifier();
391. }
392. } else {
393. // neither local or foreign key is being set
394. // try to guess both keys
396. $conn = $this->_table->getConnection();
398. // the following loops are needed for covering inheritance
399. foreach ($localClasses as $class) {
400. $table = $conn->getTable($class);
401. $column = strtolower($table->getComponentName())
402. . '_' . $table->getIdentifier();
404. foreach ($foreignClasses as $class2) {
405. $table2 = $conn->getTable($class2);
406. if ($table2->hasColumn($column)) {
407. $def['foreign'] = $column;
408. $def['local'] = $table->getIdentifier();
410. return $def;
411. }
412. }
413. }
415. foreach ($foreignClasses as $class) {
416. $table = $conn->getTable($class);
417. $column = strtolower($table->getComponentName())
418. . '_' . $table->getIdentifier();
420. foreach ($localClasses as $class2) {
421. $table2 = $conn->getTable($class2);
422. if ($table2->hasColumn($column)) {
423. $def['foreign'] = $table->getIdentifier();
424. $def['local'] = $column;
425. $def['localKey'] = true;
426. return $def;
427. }
428. }
429. }
431. // auto-add columns and auto-build relation
432. $columns = array();
433. foreach ((array) $this->_table->getIdentifier() as $id) {
434. $column = strtolower($table->getComponentName())
435. . '_' . $id;
437. $col = $this->_table->getDefinitionOf($id);
438. $type = $col['type'];
439. $length = $col['length'];
441. unset($col['type']);
442. unset($col['length']);
443. unset($col['autoincrement']);
444. unset($col['sequence']);
445. unset($col['primary']);
447. $def['table']->setColumn($column, $type, $length, $col);
449. $columns[] = $column;
450. }
451. if (count($columns) > 1) {
452. $def['foreign'] = $columns;
453. } else {
454. $def['foreign'] = $columns[0];
455. }
456. $def['local'] = $this->_table->getIdentifier();
457. }
458. }
459. return $def;
460. }
461. }