use standard SplObserver
[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 sprintf("postgresql://%s:%s@%s:%d/%s?%s#%s",
113 $this->conn->user,
114 $this->conn->pass,
115 $this->conn->host,
116 $this->conn->port,
117 $this->conn->db,
118 $this->conn->options,
119 $this->getName()
120 );
121 }
122
123 /**
124 * Set the rowset prototype
125 * @param mixed $rowset
126 * @return \pq\Gateway\Table
127 */
128 function setRowsetPrototype($rowset) {
129 $this->rowset = $rowset;
130 return $this;
131 }
132
133 /**
134 * Get the rowset prototype
135 * @return mixed
136 */
137 function getRowsetPrototype() {
138 return $this->rowset;
139 }
140
141 /**
142 * Set the query writer
143 * @param \pq\Query\WriterInterface $query
144 * @return \pq\Gateway\Table
145 */
146 function setQueryWriter(\pq\Query\WriterInterface $query) {
147 $this->query = $query;
148 return $this;
149 }
150
151 /**
152 * Get the query writer
153 * @return \pq\Query\WriterInterface
154 */
155 function getQueryWriter() {
156 if (!$this->query) {
157 $this->query = new QueryWriter;
158 }
159 return $this->query;
160 }
161
162 /**
163 * Set the query executor
164 * @param \pq\Query\ExecutorInterface $exec
165 * @return \pq\Gateway\Table
166 */
167 function setQueryExecutor(\pq\Query\ExecutorInterface $exec) {
168 $this->exec = $exec;
169 return $this;
170 }
171
172 /**
173 * Get the query executor
174 * @return \pq\Query\ExecutorInterface
175 */
176 function getQueryExecutor() {
177 if (!$this->exec) {
178 $this->exec = new QueryExecutor($this->conn);
179 }
180 return $this->exec;
181 }
182
183 /**
184 * Get the metadata cache
185 * @return \pq\Gateway\Table\CacheInterface
186 */
187 function getMetadataCache() {
188 if (!isset($this->metadatCache)) {
189 $this->metadataCache = static::$defaultMetadataCache ?: new Table\StaticCache;
190 }
191 return $this->metadataCache;
192 }
193
194 /**
195 * Set the metadata cache
196 * @param \pq\Gateway\Table\CacheInterface $cache
197 */
198 function setMetadataCache(Table\CacheInterface $cache) {
199 $this->metadataCache = $cache;
200 return $this;
201 }
202
203 /**
204 * Get the primary key
205 * @return \pq\Gateway\Table\Identity
206 */
207 function getIdentity() {
208 if (!isset($this->identity)) {
209 $this->identity = new Table\Identity($this);
210 }
211 return $this->identity;
212 }
213
214 function getAttributes() {
215 if (!isset($this->attributes)) {
216 $this->attributes = new Table\Attributes($this);
217 }
218 return $this->attributes;
219 }
220
221 /**
222 * Get foreign key relations
223 * @param string $to fkey
224 * @return \pq\Gateway\Table\Relations|stdClass
225 */
226 function getRelations($to = null) {
227 if (!isset($this->relations)) {
228 $this->relations = new Table\Relations($this);
229 }
230 if (isset($to)) {
231 if (!isset($this->relations->$to)) {
232 return null;
233 }
234 return $this->relations->$to;
235 }
236 return $this->relations;
237 }
238
239 /**
240 * Check whether a certain relation exists
241 * @param string $name
242 * @param string $table
243 * @return bool
244 */
245 function hasRelation($name, $table = null) {
246 if (!($rel = $this->getRelations($name))) {
247 return false;
248 }
249 if (!isset($table)) {
250 return true;
251 }
252 return isset($rel->$table);
253 }
254
255 /**
256 * @return \pq\Connection
257 */
258 function getConnection() {
259 return $this->conn;
260 }
261
262 /**
263 * @return string
264 */
265 function getName() {
266 return $this->name;
267 }
268
269 /**
270 * Attach an observer
271 * @param \SplObserver
272 * @return \pq\Gateway\Table
273 */
274 function attach(\SplObserver $observer) {
275 $this->observers->attach($observer);
276 return $this;
277 }
278
279 /**
280 * Detach an observer
281 * @param \SplObserver
282 * @return \pq\Gateway\Table
283 */
284 function detach(\SplObserver $observer) {
285 $this->observers->attach($observer);
286 return $this;
287 }
288
289 /**
290 * Implements \SplSubject
291 */
292 function notify(\pq\Gateway\Row $row = null, $event = null, array &$where = null) {
293 foreach ($this->observers as $observer) {
294 $observer->update($this, $row, $event, $where);
295 }
296 }
297
298 /**
299 * Execute the query
300 * @param \pq\Query\WriterInterface $query
301 * @return mixed
302 */
303 protected function execute(QueryWriter $query) {
304 return $this->getQueryExecutor()->execute($query, array($this, "onResult"));
305 }
306
307 /**
308 * Retreives the result of an executed query
309 * @param \pq\Result $result
310 * @return mixed
311 */
312 public function onResult(\pq\Result $result = null) {
313 if ($result && $result->status != \pq\Result::TUPLES_OK) {
314 return $result;
315 }
316
317 $rowset = $this->getRowsetPrototype();
318 if (is_callable($rowset)) {
319 return $rowset($result);
320 } elseif ($rowset) {
321 return new $rowset($this, $result);
322 }
323
324 return $result;
325 }
326
327 /**
328 * Find rows in the table
329 * @param array $where
330 * @param array|string $order
331 * @param int $limit
332 * @param int $offset
333 * @param string $lock
334 * @return mixed
335 */
336 function find(array $where = null, $order = null, $limit = 0, $offset = 0, $lock = null) {
337 $query = $this->getQueryWriter()->reset();
338 $query->write("SELECT * FROM", $this->conn->quoteName($this->name));
339 if ($where) {
340 $query->write("WHERE")->criteria($where);
341 }
342 if ($order) {
343 $query->write("ORDER BY", $order);
344 }
345 if ($limit) {
346 $query->write("LIMIT", $limit);
347 }
348 if ($offset) {
349 $query->write("OFFSET", $offset);
350 }
351 if ($lock) {
352 $query->write("FOR", $lock);
353 }
354 return $this->execute($query);
355 }
356
357 /**
358 * Get the child rows of a row by foreign key
359 * @param \pq\Gateway\Row $foreign
360 * @param string $name optional fkey name
361 * @param string $order
362 * @param int $limit
363 * @param int $offset
364 * @return mixed
365 */
366 function of(Row $foreign, $name = null, $order = null, $limit = 0, $offset = 0) {
367 // select * from $this where $this->$foreignColumn = $foreign->$referencedColumn
368
369 if (!isset($name)) {
370 $name = $foreign->getTable()->getName();
371 }
372
373 if (!$foreign->getTable()->hasRelation($name, $this->getName())) {
374 return $this->onResult(null);
375 }
376 $rel = $foreign->getTable()->getRelations($name)->{$this->getName()};
377
378 return $this->find(
379 array($rel->foreignColumn . "=" => $foreign->{$rel->referencedColumn}),
380 $order, $limit, $offset
381 );
382 }
383
384 /**
385 * Get the parent rows of a row by foreign key
386 * @param \pq\Gateway\Row $me
387 * @param string $foreign
388 * @param string $order
389 * @param int $limit
390 * @param int $offset
391 * @return mixed
392 */
393 function by(Row $me, $foreign, $order = null, $limit = 0, $offset = 0) {
394 // select * from $foreign where $foreign->$referencedColumn = $me->$foreignColumn
395
396 if (!$this->hasRelation($foreign, $this->getName())) {
397 return $this->onResult(null);
398 }
399 $rel = $this->getRelations($foreign)->{$this->getName()};
400
401 return static::resolve($rel->referencedTable)->find(
402 array($rel->referencedColumn . "=" => $me->{$rel->foreignColumn}),
403 $order, $limit, $offset
404 );
405 }
406
407 /**
408 * Get rows dependent on other rows by foreign keys
409 * @param array $relations
410 * @param array $where
411 * @param string $order
412 * @param int $limit
413 * @param int $offset
414 * @return mixed
415 */
416 function with(array $relations, array $where = null, $order = null, $limit = 0, $offset = 0) {
417 $qthis = $this->conn->quoteName($this->getName());
418 $query = $this->getQueryWriter()->reset();
419 $query->write("SELECT", "$qthis.*", "FROM", $qthis);
420 foreach ($relations as $relation) {
421 $query->write("JOIN", $relation->foreignTable)->write("ON")->criteria(
422 array(
423 "{$relation->referencedTable}.{$relation->referencedColumn}=" =>
424 new QueryExpr("{$relation->foreignTable}.{$relation->foreignColumn}")
425 )
426 );
427 }
428 if ($where) {
429 $query->write("WHERE")->criteria($where);
430 }
431 if ($order) {
432 $query->write("ORDER BY", $order);
433 }
434 if ($limit) {
435 $query->write("LIMIT", $limit);
436 }
437 if ($offset) {
438 $query->write("OFFSET", $offset);
439 }
440 return $this->execute($query);
441 }
442
443 /**
444 * Insert a row into the table
445 * @param array $data
446 * @param string $returning
447 * @return mixed
448 */
449 function create(array $data = null, $returning = "*") {
450 $query = $this->getQueryWriter()->reset();
451 $query->write("INSERT INTO", $this->conn->quoteName($this->name));
452 if ($data) {
453 $first = true;
454 $params = array();
455 foreach ($data as $key => $val) {
456 $query->write($first ? "(" : ",", $key);
457 $params[] = $query->param($val, $this->getAttributes()->getColumn($key)->type);
458 $first and $first = false;
459 }
460 $query->write(") VALUES (", $params, ")");
461 } else {
462 $query->write("DEFAULT VALUES");
463 }
464
465 if (strlen($returning)) {
466 $query->write("RETURNING", $returning);
467 }
468 return $this->execute($query);
469 }
470
471 /**
472 * Update rows in the table
473 * @param array $where
474 * @param array $data
475 * @param string $returning
476 * @retunr mixed
477 */
478 function update(array $where, array $data, $returning = "*") {
479 $query = $this->getQueryWriter()->reset();
480 $query->write("UPDATE", $this->conn->quoteName($this->name));
481 $first = true;
482 foreach ($data as $key => $val) {
483 $query->write($first ? "SET" : ",", $key, "=",
484 $query->param($val, $this->getAttributes()->getColumn($key)->type));
485 $first and $first = false;
486 }
487 $query->write("WHERE")->criteria($where);
488 if (strlen($returning)) {
489 $query->write("RETURNING", $returning);
490 }
491 return $this->execute($query);
492 }
493
494 /**
495 * Delete rows from the table
496 * @param array $where
497 * @param string $returning
498 * @return mixed
499 */
500 function delete(array $where, $returning = null) {
501 $query = $this->getQueryWriter()->reset();
502 $query->write("DELETE FROM", $this->conn->quoteName($this->name));
503 $query->write("WHERE")->criteria($where);
504 if (strlen($returning)) {
505 $query->write("RETURNING", $returning);
506 }
507 return $this->execute($query);
508 }
509 }