1<?php
2/**
3 * Link tool for DB_DataObject
4 *
5 * PHP versions 5
6 *
7 * LICENSE: This source file is subject to version 3.01 of the PHP license
8 * that is available through the world-wide-web at the following URI:
9 * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
10 * the PHP License and are unable to obtain it through the web, please
11 * send a note to license@php.net so we can mail you a copy immediately.
12 *
13 * @category   Database
14 * @package    DB_DataObject
15 * @author     Alan Knowles <alan@akbkhome.com>
16 * @copyright  1997-2006 The PHP Group
17 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
18 * @version    : FIXME
19 * @link       http://pear.php.net/package/DB_DataObject
20 */
21
22
23/**
24 *
25 * Example of how this could be used..
26 *
27 * The lind method are now in here.
28 *
29 * Currenly only supports existing methods, and new 'link()' method
30 *
31 */
32
33
34/**
35 * Links class
36 *
37 * @package DB_DataObject
38 */
39class DB_DataObject_Links
40{
41     /**
42     * @property {DB_DataObject}      do   DataObject to apply this to.
43     */
44    var $do = false;
45
46
47    /**
48     * @property {Array|String} load    What to load, 'all' or an array of properties. (default all)
49     */
50    var $load = 'all';
51    /**
52     * @property {String|Boolean}       scanf   use part of column name as resulting
53     *                                          property name. (default false)
54     */
55    var $scanf = false;
56    /**
57     * @property {String|Boolean}       printf  use column name as sprintf for resulting property name..
58     *                                     (default %s_link if apply is true, otherwise it is %s)
59     */
60    var $printf = false;
61    /**
62     * @property {Boolean}      cached  cache the result, so future queries will use cache rather
63     *                                  than running the expensive sql query.
64     */
65    var $cached = false;
66    /**
67     * @property {Boolean}      apply   apply the result to this object, (default true)
68     */
69    var $apply = true;
70
71
72    //------------------------- RETURN ------------------------------------
73    /**
74     * @property {Array}      links    key value associative array of links.
75     */
76    var $links;
77
78
79    /**
80     * Constructor
81     *   -- good ole style..
82     *  @param {DB_DataObject}           do  DataObject to apply to.
83     *  @param {Array}           cfg  Configuration (basically properties of this object)
84     */
85
86    function DB_DataObject_Links($do,$cfg= array())
87    {
88        // check if do is set!!!?
89        $this->do = $do;
90
91        foreach($cfg as $k=>$v) {
92            $this->$k = $v;
93        }
94
95
96    }
97
98    /**
99     * return name from related object
100     *
101     * The relies on  a <dbname>.links.ini file, unless you specify the arguments.
102     *
103     * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
104     *
105     *
106     * @param string $field|array    either row or row.xxxxx or links spec.
107     * @param string|DB_DataObject $table  (optional) name of table to look up value in
108     * @param string $link   (optional)  name of column in other table to match
109     * @author Tim White <tim@cyface.com>
110     * @access public
111     * @return mixed object on success false on failure or '0' when not linked
112     */
113    function getLink($field, $table= false, $link='')
114    {
115
116        static $cache = array();
117
118        // GUESS THE LINKED TABLE.. (if found - recursevly call self)
119
120        if ($table == false) {
121
122
123            $info = $this->linkInfo($field);
124
125            if ($info) {
126                return $this->getLink($field, $info[0],  $link === false ? $info[1] : $link );
127            }
128
129            // no links defined.. - use borked BC method...
130                  // use the old _ method - this shouldnt happen if called via getLinks()
131            if (!($p = strpos($field, '_'))) {
132                return false;
133            }
134            $table = substr($field, 0, $p);
135            return $this->getLink($field, $table);
136
137
138
139        }
140
141        $tn = is_string($table) ? $table : $table->tableName();
142
143
144
145        if (!isset($this->do->$field)) {
146            $this->do->raiseError("getLink: row not set $field", DB_DATAOBJECT_ERROR_NODATA);
147            return false;
148        }
149
150        // check to see if we know anything about this table..
151
152
153        if (empty($this->do->$field) || $this->do->$field < 0) {
154            return 0; // no record.
155        }
156
157        if ($this->cached && isset($cache[$tn.':'. $link .':'. $this->do->$field])) {
158            return $cache[$tn.':'. $link .':'. $this->do->$field];
159        }
160
161        $obj = is_string($table) ? $this->do->factory($tn) : $table;;
162
163        if (!is_a($obj,'DB_DataObject')) {
164            $this->do->raiseError(
165                "getLink:Could not find class for row $field, table $tn",
166                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
167            return false;
168        }
169        // -1 or 0 -- no referenced record..
170
171        $ret = false;
172        if ($link) {
173
174            if ($obj->get($link, $this->do->$field)) {
175                $ret = $obj;
176            }
177
178
179        // this really only happens when no link config is set (old BC stuff)
180        } else if ($obj->get($this->do->$field)) {
181            $ret= $obj;
182
183        }
184        if ($this->cached) {
185            $cache[$tn.':'. $link .':'. $this->do->$field] = $ret;
186        }
187        return $ret;
188
189    }
190    /**
191     * get link information for a field or field specification
192     *
193     * alll link (and join methods accept the 'link' info ) in various ways
194     * string : 'field' = which field to get (uses ???.links.ini to work out what)
195     * array(2) : 'field', 'table:remote_col' << just like the links.ini def.
196     * array(3) : 'field', $dataobject, 'remote_col'  (handy for joinAdd to do nested joins.)
197     *
198     * @param string|array $field or link spec to use.
199     * @return (false|array) array of dataobject and linked field or false.
200     *
201     *
202     */
203
204    function linkInfo($field)
205    {
206
207        if (is_array($field)) {
208            if (count($field) == 3) {
209                // array with 3 args:
210                // local_col , dataobject, remote_col
211                return array(
212                    $field[1],
213                    $field[2],
214                    $field[0]
215                );
216
217            }
218            list($table,$link) = explode(':', $field[1]);
219
220            return array(
221                $this->do->factory($table),
222                $link,
223                $field[0]
224            );
225
226        }
227        // work out the link.. (classic way)
228
229        $links = $this->do->links();
230
231        if (empty($links) || !is_array($links)) {
232
233            return false;
234        }
235
236
237        if (!isset($links[$field])) {
238
239            return false;
240        }
241        list($table,$link) = explode(':', $links[$field]);
242
243
244        //??? needed???
245        if ($p = strpos($field,".")) {
246            $field = substr($field,0,$p);
247        }
248
249        return array(
250            $this->do->factory($table),
251            $link,
252            $field
253        );
254
255
256
257
258    }
259
260
261
262    /**
263     *  a generic geter/setter provider..
264     *
265     *  provides a generic getter setter for the referenced object
266     *  eg.
267     *  $link->link('company_id') returns getLink for the object
268     *  if nothing is linked (it will return an empty dataObject)
269     *  $link->link('company_id', array(1)) - just sets the
270     *
271     *  also array as the field speck supports
272     *      $link->link(array('company_id', 'company:id'))
273     *
274     *
275     *  @param  string|array   $field   the field to fetch or link spec.
276     *  @params array          $args    the arguments sent to the getter setter
277     *  @return mixed true of false on set, the object on getter.
278     *
279     */
280    function link($field, $args = array())
281    {
282        $info = $this->linkInfo($field);
283
284        if (!$info) {
285            $this->do->raiseError(
286                "getLink:Could not find link for row $field",
287                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
288            return false;
289        }
290        $field = $info[2];
291
292
293        if (empty($args)) { // either an empty array or really empty....
294
295            if (!isset($this->do->$field)) {
296                return $info[0]; // empty dataobject.
297            }
298
299            $ret = $this->getLink($field);
300            // nothing linked -- return new object..
301            return ($ret === 0) ? $info[0] : $ret;
302
303        }
304        $assign = is_array($args) ? $args[0] : $args;
305
306        // otherwise it's a set call..
307        if (!is_a($assign , 'DB_DataObject')) {
308
309            if (is_numeric($assign) && is_integer($assign * 1)) {
310                if ($assign  > 0) {
311
312                    if (!$info) {
313                        return false;
314                    }
315                    // check that record exists..
316                    if (!$info[0]->get($info[1], $assign )) {
317                        return false;
318                    }
319
320                }
321
322                $this->do->$field = $assign ;
323                return true;
324            }
325
326            return false;
327        }
328
329        // otherwise we are assigning it ...
330
331        $this->do->$field = $assign->{$info[1]};
332        return true;
333
334
335    }
336    /**
337     * load related objects
338     *
339     * Generally not recommended to use this.
340     * The generator should support creating getter_setter methods which are better suited.
341     *
342     * Relies on  <dbname>.links.ini
343     *
344     * Sets properties on the calling dataobject  you can change what
345     * object vars the links are stored in by  changeing the format parameter
346     *
347     *
348     * @param  string format (default _%s) where %s is the table name.
349     * @author Tim White <tim@cyface.com>
350     * @access public
351     * @return boolean , true on success
352     */
353
354    function applyLinks($format = '_%s')
355    {
356
357        // get table will load the options.
358        if ($this->do->_link_loaded) {
359            return true;
360        }
361
362        $this->do->_link_loaded = false;
363        $cols  = $this->do->table();
364        $links = $this->do->links();
365
366        $loaded = array();
367
368        if ($links) {
369            foreach($links as $key => $match) {
370                list($table,$link) = explode(':', $match);
371                $k = sprintf($format, str_replace('.', '_', $key));
372                // makes sure that '.' is the end of the key;
373                if ($p = strpos($key,'.')) {
374                      $key = substr($key, 0, $p);
375                }
376
377                $this->do->$k = $this->getLink($key, $table, $link);
378
379                if (is_object($this->do->$k)) {
380                    $loaded[] = $k;
381                }
382            }
383            $this->do->_link_loaded = $loaded;
384            return true;
385        }
386        // this is the autonaming stuff..
387        // it sends the column name down to getLink and lets that sort it out..
388        // if there is a links file then it is not used!
389        // IT IS DEPRECATED!!!! - DO NOT USE
390        if (!is_null($links)) {
391            return false;
392        }
393
394
395        foreach (array_keys($cols) as $key) {
396            if (!($p = strpos($key, '_'))) {
397                continue;
398            }
399            // does the table exist.
400            $k =sprintf($format, $key);
401            $this->do->$k = $this->getLink($key);
402            if (is_object($this->do->$k)) {
403                $loaded[] = $k;
404            }
405        }
406        $this->do->_link_loaded = $loaded;
407        return true;
408    }
409
410    /**
411     * getLinkArray
412     * Fetch an array of related objects. This should be used in conjunction with a
413     * <dbname>.links.ini file configuration (see the introduction on linking for details on this).
414     *
415     * You may also use this with all parameters to specify, the column and related table.
416     *
417     * @access public
418     * @param string $field- either column or column.xxxxx
419     * @param string $table (optional) name of table to look up value in
420     * @param string $fkey (optional) fetchall key see DB_DataObject::fetchAll()
421     * @param string $fval (optional)fetchall val DB_DataObject::fetchAll()
422     * @param string $fval (optional) fetchall method DB_DataObject::fetchAll()
423     * @return array - array of results (empty array on failure)
424     *
425     * Example - Getting the related objects
426     *
427     * $person = new DataObjects_Person;
428     * $person->get(12);
429     * $children = $person->getLinkArray('children');
430     *
431     * echo 'There are ', count($children), ' descendant(s):<br />';
432     * foreach ($children as $child) {
433     *     echo $child->name, '<br />';
434     * }
435     *
436     */
437    function getLinkArray($field, $table = null, $fkey = false, $fval = false, $fmethod = false)
438    {
439
440        $ret = array();
441        if (!$table)  {
442
443
444            $links = $this->do->links();
445
446            if (is_array($links)) {
447                if (!isset($links[$field])) {
448                    // failed..
449                    return $ret;
450                }
451                list($table,$link) = explode(':',$links[$field]);
452                return $this->getLinkArray($field,$table);
453            }
454            if (!($p = strpos($field,'_'))) {
455                return $ret;
456            }
457            return $this->getLinkArray($field,substr($field,0,$p));
458
459
460        }
461
462        $c  = $this->do->factory($table);
463
464        if (!is_object($c) || !is_a($c,'DB_DataObject')) {
465            $this->do->raiseError(
466                "getLinkArray:Could not find class for row $field, table $table",
467                DB_DATAOBJECT_ERROR_INVALIDCONFIG
468            );
469            return $ret;
470        }
471
472        // if the user defined method list exists - use it...
473        if (method_exists($c, 'listFind')) {
474            $c->listFind($this->id);
475            while ($c->fetch()) {
476                $ret[] = clone($c);
477            }
478            return $ret;
479        }
480        return $c->fetchAll($fkey, $fval, $fmethod);
481
482
483    }
484
485}