diff --git a/classes/Database/LDAP/Result/Cached.php b/classes/Database/LDAP/Result/Cached.php new file mode 100644 index 0000000..ab5843a --- /dev/null +++ b/classes/Database/LDAP/Result/Cached.php @@ -0,0 +1,4 @@ + diff --git a/classes/Kohana/Database/LDAP/Result.php b/classes/Kohana/Database/LDAP/Result.php index 296b180..ca26776 100644 --- a/classes/Kohana/Database/LDAP/Result.php +++ b/classes/Kohana/Database/LDAP/Result.php @@ -27,7 +27,37 @@ class Kohana_Database_LDAP_Result extends Database_Result { ldap_free_result($this->_result['r']); } + public function as_array($key=NULL,$value=NULL) { + $result = array(); + + if ($key === NULL AND $value === NULL) { + // Indexed rows + foreach ($this as $dn => $row) + $result[$dn] = $row; + + } elseif ($key === NULL) { + throw HTTP_Exception::factory(501,'Not implemented'); + + // Indexed columns + + } elseif ($value === NULL) { + throw HTTP_Exception::factory(501,'Not implemented'); + + // Associative rows + + } else { + throw HTTP_Exception::factory(501,'Not implemented'); + + // Associative columns + } + + return $result; + } + public function current() { + if (! $this->_current_entry) + return array(); + if ($this->_as_object === TRUE) { // Return an stdClass throw HTTP_Exception::factory(501,'Not implemented'); @@ -67,6 +97,28 @@ class Kohana_Database_LDAP_Result extends Database_Result { return ldap_get_dn($this->_result['l'],$this->_current_entry); } + /** + * Implements [Iterator::next], moves to the next row. + * + * next($result); + * + * @return $this + */ + public function next() { + $this->_current_entry = ldap_next_entry($this->_result['l'],$this->_current_entry); + ++$this->_current_row; + + return $this; + } + + public function offsetExists($offset) { + return $this->valid(); + } + + public function prev() { + throw new Kohana_Exception('Cant go backwards'); + } + public function seek($dn) { $this->_current_entry = ldap_first_entry($this->_result['l'],$this->_result['r']); @@ -82,4 +134,23 @@ class Kohana_Database_LDAP_Result extends Database_Result { // We didnt find it return FALSE; } + + public function rewind() { + if ($this->_total_rows) + $this->_current_entry = ldap_first_entry($this->_result['l'],$this->_result['r']); + + $this->_current_row = 0; + } + + /** + * Implements [Iterator::valid], checks if the current row exists. + * + * [!!] This method is only used internally. + * + * @return boolean + */ + public function valid() { + return (boolean)$this->_current_entry; + } + } // End Database_LDAP_Result diff --git a/classes/Kohana/Database/LDAP/Result/Cached.php b/classes/Kohana/Database/LDAP/Result/Cached.php new file mode 100644 index 0000000..b10e497 --- /dev/null +++ b/classes/Kohana/Database/LDAP/Result/Cached.php @@ -0,0 +1,69 @@ +_total_rows = count($result); + $this->_current_row = key($result); + } + + public function __destruct() { + // Cached results do not use resources + } + + public function cached() { + return $this; + } + + public function current() { + // Return an array of the row + return $this->valid() ? $this->_result[$this->_current_row] : NULL; + } + + public function key() { + return $this->_current_row; + } + + public function next() { + next($this->_result); + $this->_current_row = key($this->_result); + + return $this; + } + + public function offsetExists($offset) { + return isset($this->_result[$offset]); + } + + public function rewind() { + // We dont index by numbers, so we can just return + reset($this->_result); + $this->_current_row = key($this->_result); + + return $this; + } + + public function seek($offset) { + if (isset($this->_result[$offset])) { + $this->_current_row = $offset; + + return TRUE; + + } else { + return FALSE; + } + } + +} // End Database_LDAP_Result_Cached diff --git a/classes/Kohana/Database/LDAP/Search.php b/classes/Kohana/Database/LDAP/Search.php index 7311a7c..e3f3e17 100644 --- a/classes/Kohana/Database/LDAP/Search.php +++ b/classes/Kohana/Database/LDAP/Search.php @@ -21,6 +21,9 @@ abstract class Kohana_Database_LDAP_Search { private $_time_limit = '60'; // LDAP Search Time Limit private $_db_pending = array(); // LDAP Query Filter to compile + // Execute the query during a cache hit + protected $_force_execute = FALSE; + // Cache lifetime protected $_lifetime = NULL; @@ -126,6 +129,26 @@ abstract class Kohana_Database_LDAP_Search { return isset($u['']['namingcontexts']) ? $u['']['namingcontexts'] : array(); } + /** + * Enables the query to be cached for a specified amount of time. + * + * @param integer $lifetime number of seconds to cache, 0 deletes it from the cache + * @param boolean whether or not to execute the query during a cache hit + * @return $this + * @uses Kohana::$cache_life + */ + public function cached($lifetime=NULL,$force=FALSE) { + if ($lifetime === NULL) { + // Use the global setting + $lifetime = Kohana::$cache_life; + } + + $this->_force_execute = $force; + $this->_lifetime = $lifetime; + + return $this; + } + public function deref($val) { $this->_deref = $val; @@ -147,21 +170,21 @@ abstract class Kohana_Database_LDAP_Search { $result = array(); foreach ($this->_base as $base) { - $search = NULL; + $result[$base] = NULL; if ($this->_lifetime !== NULL AND $this->_db->caching()) { // Set the cache key based on the database instance name and SQL $cache_key = 'Database::query("'.$this->_db.'","'.$base.'","'.$this->_scope.'","'.$this->_filter.'")'; // Read the cache first to delete a possible hit with lifetime <= 0 - if (($result = Kohana::cache($cache_key, NULL, $this->_lifetime)) !== NULL AND ! $this->_force_execute) { + if (($search = Kohana::cache($cache_key, NULL, $this->_lifetime)) !== NULL AND ! $this->_force_execute) { // Return a cached result - $search = new Database_Result_Cached($result, array('b'=>$base,'s'=>$this->_scope,'f'=>$this->_filter), $as_object, $object_params); + $result[$base] = new Database_LDAP_Result_Cached($search, array('b'=>$base,'s'=>$this->_scope,'f'=>$this->_filter), $as_object, $object_params); } } // Search is not cached, OR caching is disabled, so we'll query - if (! $search) { + if (! $result[$base]) { switch ($this->_scope) { case 'base': $search = ldap_read($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref); @@ -178,6 +201,10 @@ abstract class Kohana_Database_LDAP_Search { } $result[$base] = new Database_LDAP_Result(array('l'=>$this->_db->connection(),'r'=>$search),array('b'=>$base,'s'=>$this->_scope,'f'=>$this->_filter),$as_object,$object_params); + + // Cache the result array + if (isset($cache_key) AND $this->_lifetime > 0) + Kohana::cache($cache_key,$result[$base]->as_array(),$this->_lifetime); } } diff --git a/tests/classes/LDAPCaching.php b/tests/classes/LDAPCaching.php new file mode 100644 index 0000000..acd766b --- /dev/null +++ b/tests/classes/LDAPCaching.php @@ -0,0 +1,58 @@ +load('database.default'),array( + 'caching'=>$caching, + )); + + $x = LDAP::factory('user',NULL,$connection); + $x->connect(); + + // We'll do a query first. + $u = $x->search(array('')) + ->scope('base') + ->cached($cached,$force) + ->run(); + + $u = $x->search(array('')) + ->scope('base') + ->cached($cached,$force) + ->run(); + + if ($expect) + $this->assertInstanceOf('Database_LDAP_Result',$u['']); + else + $this->assertInstanceOf('Database_LDAP_Result_Cached',$u['']); + + $x->disconnect(); + } +} diff --git a/tests/classes/LDAPConnection.php b/tests/classes/LDAPConnection.php index 9057372..4986a25 100644 --- a/tests/classes/LDAPConnection.php +++ b/tests/classes/LDAPConnection.php @@ -27,10 +27,9 @@ Class LDAPConnection extends Unittest_TestCase { * @dataProvider hosts */ function testConnect($host,$port,$instance,$expect) { - $connection = array( - 'type'=>'LDAP', + $connection = Arr::merge(Kohana::$config->load('database.default'),array( 'connection'=>array('hostname'=>$host,'port'=>$port), - ); + )); $x = LDAP::factory($instance,NULL,$connection); $x->connect(); @@ -79,8 +78,7 @@ Class LDAPConnection extends Unittest_TestCase { * @depends testConnect */ function testAuthConfiguration($user,$password,$expect) { - $connection = array( - 'type'=>'LDAP', + $connection = Arr::merge(Kohana::$config->load('database.default'),array( 'login_attr'=>'uid', 'connection'=>array( 'hostname'=>'localhost', @@ -88,7 +86,7 @@ Class LDAPConnection extends Unittest_TestCase { 'username'=>$user, 'password'=>$password ), - ); + )); // Ensure we start with a clean auth connection. LDAP::factory('auth')->disconnect();