e365e3772f836ce19a58bc8f2e7a788b07a4f4ee
[m6w6/pq-gateway] / lib / pq / Gateway / Table.php
1 <?php
2
3 namespace pq\Gateway;
4
5 use \pq\Query\Expr as QueryExpr;
6 use \pq\Query\Writer as QueryWriter;
7 use \pq\Query\Executor as QueryExecutor;
8
9 class Table implements \SplSubject
10 {
11 /**
12 * @var \pq\Connection
13 */
14 public static $defaultConnection;
15
16 /**
17 * @var callable
18 */
19 public static $defaultResolver;
20
21 /**
22 * @var \pq\Gateway\Table\CacheInterface
23 */
24 public static $defaultMetadataCache;
25
26 /**
27 * @var \pq\Connection
28 */
29 protected $conn;
30
31 /**
32 * @var string
33 */
34 protected $name;
35
36 /**
37 * @var string
38 */
39 protected $rowset = "\\pq\\Gateway\\Rowset";
40
41 /**
42 * @var \pq\Query\WriterIterface
43 */
44 protected $query;
45
46 /**
47 * @var \pq\Query\ExecutorInterface
48 */
49 protected $exec;
50
51 /**
52 * @var \pq\Gateway\Table\Identity
53 */
54 protected $identity;
55
56 /**
57 * @var \pq\Gateway\Table\Attributes
58 */
59 protected $attributes;
60
61 /**
62 * @var \pq\Gateway\Table\Relations
63 */
64 protected $relations;
65
66 /**
67 * @var \pq\Gateway\Table\CacheInterface
68 */
69 protected $metadataCache;
70
71 /**
72 * @var \SplObjectStorage
73 */
74 protected $observers;
75
76 /**
77 * @param string $table
78 * @return \pq\Gateway\Table
79 */
80 public static function resolve($table) {
81 if ($table instanceof Table) {
82 return $table;
83 }
84 if (is_callable(static::$defaultResolver)) {
85 if (($resolved = call_user_func(static::$defaultResolver, $table))) {
86 return $resolved;
87 }
88 }
89 return new Table($table);
90 }
91
92 /**
93 * @param string $name
94 * @param \pq\Connection $conn
95 * @param array $dependents
96 */
97 function __construct($name = null, \pq\Connection $conn = null) {
98 if (isset($name)) {
99 $this->name = $name;
100 } elseif (!isset($this->name)) {
101 throw new \InvalidArgumentException("Table must have a name");
102 }
103 $this->conn = $conn ?: static::$defaultConnection ?: new \pq\Connection;
104 $this->observers = new \SplObjectStorage;
105 }
106
107 /**
108 * Get the complete PostgreSQL connection string
109 * @return string
110 */
111 function __toString() {
112 return (string) sprintf("postgresql://%s:%s@%s:%d/%s#%s",
113 $this->conn->user,
114 $this->conn->pass,
115 $this->conn->host,
116 $this->conn->port,
117 $this->conn->db,
118 $this->getName()
119 );
120 }
121
122 /**
123 * Set the rowset prototype
124 * @param mixed $rowset
125 * @return \pq\Gateway\Table
126 */
127 function setRowsetPrototype($rowset) {
128 $this->rowset = $rowset;
129 return $this;
130 }
131
132 /**
133 * Get the rowset prototype
134 * @return mixed
135 */
136 function getRowsetPrototype() {
137 return $this->rowset;
138 }
139
140 /**
141 * Set the query writer
142 * @param \pq\Query\WriterInterface $query
143 * @return \pq\Gateway\Table
144 */
145 function setQueryWriter(\pq\Query\WriterInterface $query) {
146 $this->query = $query;
147 return $this;
148 }
149
150 /**
151 * Get the query writer
152 * @return \pq\Query\WriterInterface
153 */
154 function getQueryWriter() {
155 if (!$this->query) {
156 $this->query = new QueryWriter;
157 }
158 return $this->query;
159 }
160
161 /**
162 * Set the query executor
163 * @param \pq\Query\ExecutorInterface $exec
164 * @return \pq\Gateway\Table
165 */
166 function setQueryExecutor(\pq\Query\ExecutorInterface $exec) {
167 $this->exec = $exec;
168 return $this;
169 }
170
171 /**
172 * Get the query executor
173 * @return \pq\Query\ExecutorInterface
174 */
175 function getQueryExecutor() {
176 if (!$this->exec) {
177 $this->exec = new QueryExecutor($this->conn);
178 }
179 return $this->exec;
180 }
181
182 /**
183 * Get the metadata cache
184 * @return \pq\Gateway\Table\CacheInterface
185 */
186 function getMetadataCache() {
187 if (!isset($this->metadatCache)) {
188 $this->metadataCache = static::$defaultMetadataCache ?: new Table\StaticCache;
189 }
190 return $this->metadataCache;
191 }
192
193 /**
194 * Set the metadata cache
195 * @param \pq\Gateway\Table\CacheInterface $cache
196 */
197 function setMetadataCache(Table\CacheInterface $cache) {
198 $this->metadataCache = $cache;
199 return $this;
200 }
201
202 /**
203 * Get the primary key
204 * @return \pq\Gateway\Table\Identity
205 */
206 function getIdentity() {
207 if (!isset($this->identity)) {
208 $this->identity = new Table\Identity($this);
209 }
210 return $this->identity;
211 }
212
213 /**
214 * Get the table attribute definition (column list)
215 * @return \pq\Table\Attributes
216 */
217 function getAttributes() {
218 if (!isset($this->attributes)) {
219 $this->attributes = new Table\Attributes($this);
220 }
221 return $this->attributes;
222 }
223
224 /**
225 * Get foreign key relations
226 * @return \pq\Gateway\Table\Relations
227 */
228 function getRelations() {
229 if (!isset($this->relations)) {
230 $this->relations = new Table\Relations($this);
231 }
232 return $this->relations;
233 }
234
235 /**
236 * Get a foreign key relation
237 * @param string $table
238 * @param string $ref
239 * @return \pq\Gateway\Table\Reference
240 */
241 function getRelation($table, $ref = null) {
242 return $this->getRelations()->getReference($table, $ref);
243 }
244
245 /**
246 * @return \pq\Connection
247 */
248 function getConnection() {
249 return $this->conn;
250 }
251
252 /**
253 * @return string
254 */
255 function getName() {
256 return $this->name;
257 }
258
259 /**
260 * Attach an observer
261 * @param \SplObserver
262 * @return \pq\Gateway\Table
263 */
264 function attach(\SplObserver $observer) {
265 $this->observers->attach($observer);
266 return $this;
267 }
268
269 /**
270 * Detach an observer
271 * @param \SplObserver
272 * @return \pq\Gateway\Table
273 */
274 function detach(\SplObserver $observer) {
275 $this->observers->attach($observer);
276 return $this;
277 }
278
279 /**
280 * Implements \SplSubject
281 */
282 function notify(\pq\Gateway\Row $row = null, $event = null, array &$where = null) {
283 foreach ($this->observers as $observer) {
284 $observer->update($this, $row, $event, $where);
285 }
286 }
287
288 /**
289 * Execute the query
290 * @param \pq\Query\WriterInterface $query
291 * @return mixed
292 */
293 protected function execute(QueryWriter $query) {
294 return $this->getQueryExecutor()->execute($query, array($this, "onResult"));
295 }
296
297 /**
298 * Retreives the result of an executed query
299 * @param \pq\Result $result
300 * @return mixed
301 */
302 public function onResult(\pq\Result $result = null) {
303 if ($result && $result->status != \pq\Result::TUPLES_OK) {
304 return $result;
305 }
306
307 $rowset = $this->getRowsetPrototype();
308 if (is_callable($rowset)) {
309 return $rowset($result);
310 } elseif ($rowset) {
311 return new $rowset($this, $result);
312 }
313
314 return $result;
315 }
316
317 /**
318 * Find rows in the table
319 * @param array $where
320 * @param array|string $order
321 * @param int $limit
322 * @param int $offset
323 * @param string $lock
324 * @return mixed
325 */
326 function find(array $where = null, $order = null, $limit = 0, $offset = 0, $lock = null) {
327 $query = $this->getQueryWriter()->reset();
328 $query->write("SELECT * FROM", $this->conn->quoteName($this->name));
329 if ($where) {
330 $query->write("WHERE")->criteria($where);
331 }
332 if ($order) {
333 $query->write("ORDER BY", $order);
334 }
335 if ($limit) {
336 $query->write("LIMIT", $limit);
337 }
338 if ($offset) {
339 $query->write("OFFSET", $offset);
340 }
341 if ($lock) {
342 $query->write("FOR", $lock);
343 }
344 return $this->execute($query);
345 }
346
347 /**
348 * Get the child rows of a row by foreign key
349 * @param \pq\Gateway\Row $foreign
350 * @param string $ref optional fkey name
351 * @param string $order
352 * @param int $limit
353 * @param int $offset
354 * @return mixed
355 */
356 function of(Row $foreign, $ref = null, $order = null, $limit = 0, $offset = 0) {
357 // select * from $this where $this->$foreignColumn = $foreign->$referencedColumn
358
359 if (!($rel = $this->getRelation($foreign->getTable()->getName(), $ref))) {
360 return $this->onResult(null);
361 }
362
363 $where = array();
364 foreach ($rel as $key => $ref) {
365 $where["$key="] = $foreign->$ref;
366 }
367
368 return $this->find($where, $order, $limit, $offset);
369 }
370
371 /**
372 * Get the parent rows of a row by foreign key
373 * @param \pq\Gateway\Row $foreign
374 * @param string $ref
375 * @return mixed
376 */
377 function by(Row $foreign, $ref = null) {
378 // select * from $this where $this->$referencedColumn = $me->$foreignColumn
379
380 if (!($rel = $foreign->getTable()->getRelation($this->getName(), $ref))) {
381 return $this->onResult(null);
382 }
383
384 $where = array();
385 foreach ($rel as $key => $ref) {
386 $where["$ref="] = $foreign->$key;
387 }
388 return $this->find($where);
389 }
390
391 /**
392 * Get rows dependent on other rows by foreign keys
393 * @param array $relations
394 * @param array $where
395 * @param string $order
396 * @param int $limit
397 * @param int $offset
398 * @return mixed
399 */
400 function with(array $relations, array $where = null, $order = null, $limit = 0, $offset = 0) {
401 $qthis = $this->conn->quoteName($this->getName());
402 $query = $this->getQueryWriter()->reset();
403 $query->write("SELECT", "$qthis.*", "FROM", $qthis);
404 foreach ($relations as $relation) {
405 if (!($relation instanceof Table\Reference)) {
406 $relation = static::resolve($relation)->getRelation($this->getName());
407 }
408 $query->write("JOIN", $relation->foreignTable)->write("ON");
409 foreach ($relation as $key => $ref) {
410 $query->criteria(
411 array(
412 "{$relation->referencedTable}.{$ref}=" =>
413 new QueryExpr("{$relation->foreignTable}.{$key}")
414 )
415 );
416 }
417 }
418 if ($where) {
419 $query->write("WHERE")->criteria($where);
420 }
421 if ($order) {
422 $query->write("ORDER BY", $order);
423 }
424 if ($limit) {
425 $query->write("LIMIT", $limit);
426 }
427 if ($offset) {
428 $query->write("OFFSET", $offset);
429 }
430 return $this->execute($query);
431 }
432
433 /**
434 * Insert a row into the table
435 * @param array $data
436 * @param string $returning
437 * @return mixed
438 */
439 function create(array $data = null, $returning = "*") {
440 $query = $this->getQueryWriter()->reset();
441 $query->write("INSERT INTO", $this->conn->quoteName($this->name));
442 if ($data) {
443 $first = true;
444 $params = array();
445 foreach ($data as $key => $val) {
446 $query->write($first ? "(" : ",", $key);
447 $params[] = $query->param($val, $this->getAttributes()->getColumn($key)->type);
448 $first and $first = false;
449 }
450 $query->write(") VALUES (", $params, ")");
451 } else {
452 $query->write("DEFAULT VALUES");
453 }
454
455 if (strlen($returning)) {
456 $query->write("RETURNING", $returning);
457 }
458 return $this->execute($query);
459 }
460
461 /**
462 * Update rows in the table
463 * @param array $where
464 * @param array $data
465 * @param string $returning
466 * @retunr mixed
467 */
468 function update(array $where, array $data, $returning = "*") {
469 $query = $this->getQueryWriter()->reset();
470 $query->write("UPDATE", $this->conn->quoteName($this->name));
471 $first = true;
472 foreach ($data as $key => $val) {
473 $query->write($first ? "SET" : ",", $key, "=",
474 $query->param($val, $this->getAttributes()->getColumn($key)->type));
475 $first and $first = false;
476 }
477 $query->write("WHERE")->criteria($where);
478 if (strlen($returning)) {
479 $query->write("RETURNING", $returning);
480 }
481 return $this->execute($query);
482 }
483
484 /**
485 * Delete rows from the table
486 * @param array $where
487 * @param string $returning
488 * @return mixed
489 */
490 function delete(array $where, $returning = null) {
491 $query = $this->getQueryWriter()->reset();
492 $query->write("DELETE FROM", $this->conn->quoteName($this->name));
493 $query->write("WHERE")->criteria($where);
494 if (strlen($returning)) {
495 $query->write("RETURNING", $returning);
496 }
497 return $this->execute($query);
498 }
499 }