1<?php
2// +----------------------------------------------------------------------+
3// | PEAR :: Cache                                                        |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2003 The PHP Group                                |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 2.0 of the PHP license,       |
8// | that is bundled with this package in the file LICENSE, and is        |
9// | available at through the world-wide-web at                           |
10// | http://www.php.net/license/2_02.txt.                                 |
11// | If you did not receive a copy of the PHP license and are unable to   |
12// | obtain it through the world-wide-web, please send a note to          |
13// | license@php.net so we can mail you a copy immediately.               |
14// +----------------------------------------------------------------------+
15// | Authors: Ulf Wendel <ulf.wendel@phpdoc.de>                           |
16// |          Sebastian Bergmann <sb@sebastian-bergmann.de>               |
17// +----------------------------------------------------------------------+
18//
19// $Id: phplib.php 174777 2004-12-15 09:09:33Z dufuz $
20
21require_once 'Cache/Container.php';
22
23/**
24* Stores cache data into a database table using PHPLibs DB abstraction.
25*
26* WARNING: Other systems might or might not support certain datatypes of
27* the tables shown. As far as I know there's no large binary
28* type in SQL-92 or SQL-99. Postgres seems to lack any
29* BLOB or TEXT type, for MS-SQL you could use IMAGE, don't know
30* about other databases. Please add sugestions for other databases to
31* the inline docs.
32*
33* The field 'changed' is used by the garbage collection. Depending on
34* your databasesystem you might have to subclass fetch() and garbageCollection().
35*
36* For _MySQL_ you need this DB table:
37*
38* CREATE TABLE cache (
39*   id          CHAR(32) NOT null DEFAULT '',
40*   cachegroup  VARCHAR(127) NOT null DEFAULT '',
41*   cachedata   BLOB NOT null DEFAULT '',
42*   userdata    VARCHAR(255) NOT null DEFAUL '',
43*   expires     INT(9) NOT null DEFAULT 0,
44*
45*   changed     TIMESTAMP(14) NOT null,
46*
47*   INDEX (expires),
48*   PRIMARY KEY (id, cachegroup)
49* )
50*
51*
52* @author   Ulf Wendel  <ulf.wendel@phpdoc.de>, Sebastian Bergmann <sb@sebastian-bergmann.de>
53* @version  $Id: phplib.php 174777 2004-12-15 09:09:33Z dufuz $
54* @package  Cache
55* @see      save()
56*/
57class Cache_Container_phplib extends Cache_Container
58{
59    /**
60    * Name of the DB table to store caching data
61    *
62    * @see  Cache_Container_file::$filename_prefix
63    */
64    var $cache_table = 'cache';
65
66    /**
67    * PHPLib object
68    *
69    * @var  object PEAR_DB
70    */
71    var $db;
72
73    /**
74    * Name of the PHPLib DB class to use
75    *
76    * @var  string
77    * @see  $db_path, $local_path
78    */
79    var $db_class = '';
80
81    /**
82    * Filename of your local.inc
83    *
84    * If empty, 'local.inc' is assumed.
85    *
86    * @var       string
87    */
88    var $local_file = '';
89
90    /**
91    * Include path for you local.inc
92    *
93    * HINT: If your're not using PHPLib's prepend.php you must
94    * take care that all classes (files) references by you
95    * local.inc are included automatically. So you might
96    * want to write a new local2.inc that only referrs to
97    * the database class (file) you're using and includes all required files.
98    *
99    * @var  string  path to your local.inc - make sure to add a trailing slash
100    * @see  $local_file
101    */
102    var $local_path = '';
103
104    /**
105    * Creates an instance of a phplib db class to use it for storage.
106    *
107    * @param    mixed   If empty the object tries to used the
108    *                   preconfigured class variables. If given it
109    *                   must be an array with:
110    *                     db_class => name of the DB class to use
111    *                   optional:
112    *                     db_file => filename of the DB class
113    *                     db_path => path to the DB class
114    *                     local_file => kind of local.inc
115    *                     local_patk => path to the local.inc
116    *                   see $local_path for some hints.s
117    * @see  $local_path
118    */
119    function Cache_Container_phplib($options = '')
120    {
121        if (is_array($options)) {
122            $this->setOptions($options,  array_merge($this->allowed_options, array('db_class', 'db_file', 'db_path', 'local_file', 'local_path')));
123        }
124        if (!$this->db_class) {
125            return new Cache_Error('No database class specified.', __FILE__, __LINE__);
126        }
127        // include the required files
128        if ($this->db_file) {
129            include_once $this->db_path . $this->db_file;
130        }
131        if ($this->local_file) {
132            include_once $this->local_path . $this->local_file;
133        }
134        // create a db object
135        $this->db = new $this->db_class;
136    } // end constructor
137
138    function fetch($id, $group)
139    {
140        $query = sprintf("SELECT expires, cachedata, userdata FROM %s WHERE id = '%s' AND cachegroup = '%s'",
141                            $this->cache_table,
142                            $id,
143                            $group
144                         );
145        $this->db->query($query);
146        if (!$this->db->Next_Record()) {
147            return array(null, null, null);
148        }
149        // last used required by the garbage collection
150        // WARNING: might be MySQL specific
151        $query = sprintf("UPDATE %s SET changed = (NOW() + 0) WHERE id = '%s' AND cachegroup = '%s'",
152                            $this->cache_table,
153                            $id,
154                            $group
155                          );
156        $this->db->query($query);
157        return array($this->db->f('expires'), $this->decode($this->db->f('cachedata')), $this->db->f('userdata'));
158    } // end func fetch
159
160    /**
161    * Stores a dataset.
162    *
163    * WARNING: we use the SQL command REPLACE INTO this might be
164    * MySQL specific. As MySQL is very popular the method should
165    * work fine for 95% of you.
166    */
167    function save($id, $data, $expires, $group)
168    {
169        $this->flushPreload($id, $group);
170
171        $query = sprintf("REPLACE INTO %s (cachedata, expires, id, cachegroup) VALUES ('%s', %d, '%s', '%s')",
172                            $this->cache_table,
173                            addslashes($this->encode($data)),
174                            $this->getExpiresAbsolute($expires) ,
175                            $id,
176                            $group
177                         );
178        $this->db->query($query);
179
180        return (boolean)$this->db->affected_rows();
181    } // end func save
182
183    function remove($id, $group)
184    {
185        $this->flushPreload($id, $group);
186        $this->db->query(
187                        sprintf("DELETE FROM %s WHERE id = '%s' AND cachegroup = '%s'",
188                            $this->cache_table,
189                            $id,
190                            $group
191                          )
192                    );
193
194        return (boolean)$this->db->affected_rows();
195    } // end func remove
196
197    function flush($group)
198    {
199        $this->flushPreload();
200
201        if ($group) {
202            $this->db->query(sprintf("DELETE FROM %s WHERE cachegroup = '%s'", $this->cache_table, $group));
203        } else {
204            $this->db->query(sprintf("DELETE FROM %s", $this->cache_table));
205        }
206
207        return $this->db->affected_rows();
208    } // end func flush
209
210    function idExists($id, $group)
211    {
212        $this->db->query(
213                        sprintf("SELECT id FROM %s WHERE ID = '%s' AND cachegroup = '%s'",
214                            $this->cache_table,
215                            $id,
216                            $group
217                        )
218                    );
219
220        return (boolean)$this->db->nf();
221    } // end func isExists
222
223    function garbageCollection($maxlifetime)
224    {
225        $this->flushPreload();
226
227        $this->db->query(
228                        sprintf("DELETE FROM %s WHERE (expires <= %d AND expires > 0) OR changed <= (NOW() - %d)",
229                            $this->cache_table,
230                            time(),
231                            $maxlifetime
232                        )
233                    );
234
235        //check for total size of cache
236        $query = sprintf('select sum(length(cachedata)) as CacheSize from %s',
237                         $this->cache_table
238                       );
239
240        $this->db->query($query);
241        $this->db->Next_Record();
242        $cachesize = $this->db->f('CacheSize');
243        //if cache is to big.
244        if ($cachesize > $this->highwater) {
245            //find the lowwater mark.
246            $query = sprintf('select length(cachedata) as size, changed from %s order by changed DESC',
247                                     $this->cache_table
248                       );
249            $this->db->query($query);
250
251            $keep_size=0;
252            while ($keep_size < $this->lowwater && $this->db->Next_Record()) {
253                $keep_size += $this->db->f('size');
254            }
255            //delete all entries, which were changed before the "lowwwater mark"
256            $query = sprintf('delete from %s where changed <= '.$this->db->f('changed'),
257                                     $this->cache_table
258                                   );
259
260            $this->db->query($query);
261        }
262    } // end func garbageCollection
263}
264?>