summaryrefslogtreecommitdiffstats
path: root/vendor/gipfl/zfdbstore
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:44:51 +0000
commita1ec78bf0dc93d0e05e5f066f1949dc3baecea06 (patch)
treeee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/zfdbstore
parentInitial commit. (diff)
downloadicingaweb2-module-incubator-a1ec78bf0dc93d0e05e5f066f1949dc3baecea06.tar.xz
icingaweb2-module-incubator-a1ec78bf0dc93d0e05e5f066f1949dc3baecea06.zip
Adding upstream version 0.20.0.upstream/0.20.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gipfl/zfdbstore')
-rw-r--r--vendor/gipfl/zfdbstore/composer.json16
-rw-r--r--vendor/gipfl/zfdbstore/src/BaseStore.php112
-rw-r--r--vendor/gipfl/zfdbstore/src/DbStorable.php79
-rw-r--r--vendor/gipfl/zfdbstore/src/DbStorableInterface.php36
-rw-r--r--vendor/gipfl/zfdbstore/src/NotFoundError.php9
-rw-r--r--vendor/gipfl/zfdbstore/src/Storable.php323
-rw-r--r--vendor/gipfl/zfdbstore/src/StorableInterface.php44
-rw-r--r--vendor/gipfl/zfdbstore/src/Store.php24
-rw-r--r--vendor/gipfl/zfdbstore/src/ZfDbStore.php241
9 files changed, 884 insertions, 0 deletions
diff --git a/vendor/gipfl/zfdbstore/composer.json b/vendor/gipfl/zfdbstore/composer.json
new file mode 100644
index 0000000..dc84939
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "gipfl/zfdbstore",
+ "description": "Storable class helpers for ZfDb",
+ "type": "library",
+ "require": {
+ "php": ">=5.6"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\ZfDbStore\\": "src"
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/BaseStore.php b/vendor/gipfl/zfdbstore/src/BaseStore.php
new file mode 100644
index 0000000..176ec33
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/BaseStore.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use Evenement\EventEmitterTrait;
+use RuntimeException;
+
+/**
+ * Class BaseStore
+ *
+ * This class handles creation/update/delete of $storable
+ * and save records them into a log table in the DB
+ */
+abstract class BaseStore implements Store
+{
+ use EventEmitterTrait;
+
+ /**
+ * If a new element is created, it stores it into the DB
+ * and emits the "insert" event, in order to create the corresponding activity log
+ *
+ * If an element is updated, it updates the DB record and
+ * emits the "update" event
+ *
+ * @param StorableInterface $storable
+ * @return bool Whether the store() applied any change to the stored object
+ */
+ public function store(StorableInterface $storable)
+ {
+ $affected = false;
+
+ if ($storable->isNew()) {
+ $this->insertIntoStore($storable);
+ $this->emit('insert', [$storable]);
+ $affected = true;
+ } else {
+ if ($storable->isModified() && $this->updateStore($storable)) {
+ $this->emit('update', [$storable]);
+ $affected = true;
+ }
+ }
+ $storable->setStored();
+
+ return $affected;
+ }
+
+ /**
+ * If a new element is deleted, it deletes the record from the DB
+ * and emits the "delete" event, in order to create the corresponding activity log
+ *
+ * @param StorableInterface $storable
+ * @return bool
+ */
+ public function delete(StorableInterface $storable)
+ {
+ if ($this->deleteFromStore($storable)) {
+ $this->emit('delete', [$storable]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Loads $storable by it's key property/properties
+ *
+ * @param StorableInterface $storable
+ * @param $key
+ * @return StorableInterface
+ */
+ abstract protected function loadFromStore(StorableInterface $storable, $key);
+
+ /**
+ * Deletes this record from the store
+ *
+ * @param StorableInterface $storable
+ */
+ abstract protected function deleteFromStore(StorableInterface $storable);
+
+ /**
+ * Inserts the $storable, refreshes the object in case storing implies
+ * changes (like auto-incremental IDs)
+ *
+ * @param StorableInterface $storable
+ * @return bool
+ * @throws \gipfl\ZfDb\Adapter\Exception\AdapterException
+ * @throws \gipfl\ZfDb\Statement\Exception\StatementException
+ * @throws \Zend_Db_Adapter_Exception
+ */
+ abstract protected function insertIntoStore(StorableInterface $storable);
+ abstract protected function updateStore(StorableInterface $storable);
+
+ /**
+ * Load $storable from DB
+ *
+ * @param array|string $key
+ * @param string $className
+ * @return StorableInterface
+ */
+ public function load($key, $className)
+ {
+ $storable = new $className();
+ if ($storable instanceof StorableInterface) {
+ $storable = $storable::create();
+ return $this->loadFromStore($storable, $key);
+ } else {
+ throw new RuntimeException(
+ get_class($this) . "::load: '$className' is not a StorableInterface implementation"
+ );
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/DbStorable.php b/vendor/gipfl/zfdbstore/src/DbStorable.php
new file mode 100644
index 0000000..4bd1783
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/DbStorable.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use RuntimeException;
+
+/**
+ * DbStorable
+ *
+ * This trait provides all you need to create an object implementing the
+ * DbStorableInterface
+ */
+trait DbStorable
+{
+ use Storable;
+
+ // protected $tableName;
+
+ public function getTableName()
+ {
+ if (isset($this->tableName)) {
+ return $this->tableName;
+ } else {
+ throw new RuntimeException('A DbStorable needs a tableName');
+ }
+ }
+
+ public function hasAutoIncKey()
+ {
+ return $this->getAutoIncKeyName() !== null;
+ }
+
+ public function getAutoIncKeyName()
+ {
+ if (isset($this->autoIncKeyName)) {
+ return $this->autoIncKeyName;
+ } else {
+ return null;
+ }
+ }
+
+ protected function requireAutoIncKeyName()
+ {
+ $key = $this->getAutoIncKeyName();
+ if ($key === null) {
+ throw new RuntimeException('This DbStorable has no autoinc key');
+ }
+
+ return $key;
+ }
+
+ public function getAutoIncId()
+ {
+ $key = $this->requireAutoIncKeyName();
+ if (isset($this->properties[$key])) {
+ return (int) $this->properties[$key];
+ }
+
+ return null;
+ }
+
+ protected function forgetAutoIncId()
+ {
+ $key = $this->requireAutoIncKeyName();
+ if (isset($this->properties[$key])) {
+ $this->properties[$key] = null;
+ }
+
+ return $this;
+ }
+
+ public function __invoke($properties = [])
+ {
+ $storable = new static();
+ $storable->setProperties($properties);
+
+ return $storable;
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/DbStorableInterface.php b/vendor/gipfl/zfdbstore/src/DbStorableInterface.php
new file mode 100644
index 0000000..ba9a49f
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/DbStorableInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+interface DbStorableInterface extends StorableInterface
+{
+ /**
+ * The table where this Storable will be loaded from and stored to
+ *
+ * @return string
+ */
+ public function getTableName();
+
+ /**
+ * Whether this Storable has an auto-incrementing key column
+ * @return bool
+ */
+ public function hasAutoIncKey();
+
+ /**
+ * Returns the name of the auto-incrementing key column
+ *
+ * @return string
+ */
+ public function getAutoIncKeyName();
+
+ /**
+ * Get the AutoInc value if set
+ *
+ * Should throw and Exception in case no such key has been defined. This
+ * will return null for unstored DbStorables
+ *
+ * @return int|null
+ */
+ public function getAutoIncId();
+}
diff --git a/vendor/gipfl/zfdbstore/src/NotFoundError.php b/vendor/gipfl/zfdbstore/src/NotFoundError.php
new file mode 100644
index 0000000..fb2413e
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/NotFoundError.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use Exception;
+
+class NotFoundError extends Exception
+{
+}
diff --git a/vendor/gipfl/zfdbstore/src/Storable.php b/vendor/gipfl/zfdbstore/src/Storable.php
new file mode 100644
index 0000000..9980efd
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/Storable.php
@@ -0,0 +1,323 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use InvalidArgumentException;
+use RuntimeException;
+
+/**
+ * Trait Storable
+ *
+ * This trait implements a generic trait used for storing
+ * information about users activity (i.e. creation of new elements,
+ * update/delete existing records)
+ *
+ * Each storable is characterized by:
+ * - $defaultProperties (array of properties set by default)
+ * - $modifiedProperties (array of properties modified by the user)
+ * - $storedProperties (array of properties loaded from the DB)
+ * - key property represents the primary key in the DB
+ */
+trait Storable
+{
+ /** @var null|array */
+ protected $storedProperties;
+
+ ///** @var array */
+ //protected $defaultProperties = [];
+
+ /** @var array */
+ protected $modifiedProperties = [];
+
+ /** @var array */
+ protected $properties = [];
+
+ ///** @var string|array */
+ //protected $keyProperty;
+
+ /**
+ * If a $storable has no stored properties it means that
+ * it is a new element -> the user is creating it right now
+ *
+ * @return bool
+ */
+ public function isNew()
+ {
+ return null === $this->storedProperties;
+ }
+
+ /**
+ * This function returns the key property (it can be an array of properties) of the $storable
+ * i.e. it returns the primary key in the case of DB object
+ *
+ * @return array|mixed
+ */
+ public function getKey()
+ {
+ $property = $this->getKeyProperty();
+ if (is_string($property)) {
+ return $this->get($property);
+ } else {
+ return $this->getProperties($property);
+ }
+ }
+
+ /**
+ * @return string|array
+ */
+ public function getKeyProperty()
+ {
+ if (isset($this->keyProperty)) {
+ return $this->keyProperty;
+ } else {
+ throw new RuntimeException('A storable needs a key property.');
+ }
+ }
+
+ /**
+ * Create a $storable setting its properties
+ *
+ * @param array $properties
+ * @return static
+ */
+ public static function create(array $properties = [])
+ {
+ $storable = new static();
+ $storable->properties = $storable->getDefaultProperties();
+ $storable->setProperties($properties);
+
+ return $storable;
+ }
+
+ /**
+ * Loads an already existing $storable
+ *
+ * @param Store $store
+ * @param $key
+ * @return mixed
+ */
+ public static function load(Store $store, $key)
+ {
+ return $store->load($key, get_called_class());
+ return $store->load(get_called_class(), $key);
+ }
+
+ /**
+ * Returns the value of $property (if this property exists)
+ *
+ * @param $property
+ * @return mixed
+ */
+ public function get($property, $default = null)
+ {
+ $this->assertPropertyExists($property);
+
+ if (array_key_exists($property, $this->properties)) {
+ if ($this->properties[$property] === null) {
+ return $default;
+ } else {
+ return $this->properties[$property];
+ }
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Returns the array of values corresponding to the requested array of properties
+ *
+ * @param array|null $properties
+ * @return array
+ */
+ public function getProperties(array $properties = null)
+ {
+ if ($properties === null) {
+ $properties = array_keys($this->properties);
+ }
+
+ $result = [];
+ foreach ($properties as $property) {
+ $result[$property] = $this->get($property);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the array of properties modified by the user
+ *
+ * @return array
+ */
+ public function getModifiedProperties()
+ {
+ return $this->getProperties($this->listModifiedProperties());
+ }
+
+ /**
+ * Returns the array of stored properties
+ * It can be used only in case of already existing $storable
+ */
+ public function getStoredProperties()
+ {
+ if ($this->isNew()) {
+ throw new RuntimeException(
+ 'Trying to access stored properties of an unstored Storable'
+ );
+ }
+
+ return $this->storedProperties;
+ }
+
+ /**
+ * Set the value of a given property
+ *
+ * @param $property
+ * @param $value
+ * @return bool
+ */
+ public function set($property, $value)
+ {
+ $this->assertPropertyExists($property);
+
+ if ($value === $this->get($property)) {
+ return false;
+ }
+
+ $this->properties[$property] = $value;
+
+ if ($this->storedProperties !== null && $this->storedProperties[$property] === $value) {
+ $this->resetModifiedProperty($property);
+ } else {
+ $this->setModifiedProperty($property);
+ }
+
+ return true;
+ }
+
+ /**
+ * Initialize the stored property at the first loading of the $storable element
+ *
+ * @param $property
+ * @param $value
+ */
+ public function setStoredProperty($property, $value)
+ {
+ $this->assertPropertyExists($property);
+
+ $this->storedProperties[$property] = $value;
+ $this->properties[$property] = $value;
+ unset($this->modifiedProperties[$property]);
+ }
+
+ /**
+ * Set array of values for the given array of properties
+ *
+ * @param array $properties
+ * @return $this
+ */
+ public function setProperties(array $properties)
+ {
+ foreach ($properties as $property => $value) {
+ $this->set($property, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Initialize the stored property array
+ *
+ * @param array $properties
+ * @return $this
+ */
+ public function setStoredProperties(array $properties)
+ {
+ foreach ($properties as $property => $value) {
+ $this->setStoredProperty($property, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param $property
+ */
+ public function assertPropertyExists($property)
+ {
+ if (! $this->hasProperty($property)) {
+ throw new InvalidArgumentException(sprintf(
+ "Trying to access invalid property '%s'",
+ $property
+ ));
+ }
+ }
+
+ /**
+ * @param $property
+ * @return bool
+ */
+ public function hasProperty($property)
+ {
+ return array_key_exists($property, $this->defaultProperties);
+ }
+
+ /**
+ * @param $property
+ */
+ private function setModifiedProperty($property)
+ {
+ $this->modifiedProperties[$property] = true;
+ }
+
+ /**
+ * @param $property
+ */
+ private function resetModifiedProperty($property)
+ {
+ unset($this->modifiedProperties[$property]);
+ }
+
+ /**
+ * Check if $storable has changed,
+ * if not the $modifiedProperties array is empty
+ *
+ * @return bool
+ */
+ public function isModified()
+ {
+ return !empty($this->modifiedProperties);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getDefaultProperties()
+ {
+ if (isset($this->defaultProperties)) {
+ return $this->defaultProperties;
+ } else {
+ throw new RuntimeException('A storable needs default properties.');
+ }
+ }
+
+ /**
+ * Get the array key of the modifies properties
+ *
+ * @return array
+ */
+ public function listModifiedProperties()
+ {
+ return array_keys($this->modifiedProperties);
+ }
+
+ /**
+ * @return $this
+ */
+ public function setStored()
+ {
+ $this->storedProperties = $this->properties;
+ $this->modifiedProperties = [];
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/StorableInterface.php b/vendor/gipfl/zfdbstore/src/StorableInterface.php
new file mode 100644
index 0000000..8ae49d2
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/StorableInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+interface StorableInterface
+{
+ public function isNew();
+
+ public function getKey();
+
+ public function getKeyProperty();
+
+ public static function create(array $properties = []);
+
+ public static function load(Store $store, $key);
+
+ public function get($property);
+
+ public function getProperties(array $properties = null);
+
+ public function hasProperty($property);
+
+ public function getModifiedProperties();
+
+ public function getStoredProperties();
+
+ public function set($property, $value);
+
+ public function setStoredProperty($property, $value);
+
+ public function setProperties(array $properties);
+
+ public function setStoredProperties(array $properties);
+
+ public function assertPropertyExists($property);
+
+ public function isModified();
+
+ public function getDefaultProperties();
+
+ public function listModifiedProperties();
+
+ public function setStored();
+}
diff --git a/vendor/gipfl/zfdbstore/src/Store.php b/vendor/gipfl/zfdbstore/src/Store.php
new file mode 100644
index 0000000..cdc7ab1
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/Store.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+interface Store
+{
+ /**
+ * Function used for saving changes and log the activity.
+ * To be extended as needed (see BaseStore.php)
+ *
+ * @param StorableInterface $storable
+ * @return mixed
+ */
+ public function store(StorableInterface $storable);
+
+ /**
+ * @param array|string $key
+ * @param string $className
+ * @return StorableInterface
+ */
+ public function load($key, $className);
+ public function exists(StorableInterface $storable);
+ public function delete(StorableInterface $storable);
+}
diff --git a/vendor/gipfl/zfdbstore/src/ZfDbStore.php b/vendor/gipfl/zfdbstore/src/ZfDbStore.php
new file mode 100644
index 0000000..d34541c
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/ZfDbStore.php
@@ -0,0 +1,241 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use InvalidArgumentException;
+use RuntimeException;
+use gipfl\ZfDb\Adapter\Adapter;
+use Zend_Db_Adapter_Abstract as ZfDb;
+use function array_key_exists;
+use function assert;
+use function implode;
+use function is_array;
+use function is_string;
+use function method_exists;
+
+/**
+ * Class DbStore
+ *
+ * Extends BaseStore for DB object
+ */
+class ZfDbStore extends BaseStore
+{
+ /** @var Adapter|ZfDb */
+ protected $db;
+
+ /**
+ * ZfDbStore constructor.
+ * @param Adapter|ZfDb $db
+ */
+ public function __construct($db)
+ {
+ if ($db instanceof Adapter || $db instanceof ZfDb) {
+ $this->db = $db;
+ } else {
+ throw new InvalidArgumentException('ZfDb Adapter is required');
+ }
+ }
+
+ /**
+ * @return Adapter|ZfDb
+ */
+ public function getDb()
+ {
+ return $this->db;
+ }
+
+ /**
+ * Checks whether the passed $storable already exists in the DB
+ *
+ * @param DbStorableInterface $storable
+ * @return bool
+ */
+ public function exists(StorableInterface $storable)
+ {
+ return (int) $this->db->fetchOne(
+ $this->db
+ ->select()
+ ->from($this->getTableName($storable), '(1)')
+ ->where($this->createWhere($storable))
+ ) === 1;
+ }
+
+ /**
+ * @param DbStorableInterface $storable
+ * @param string|null $keyColumn
+ * @param string|null $labelColumn
+ * @return array
+ */
+ public function enum(StorableInterface $storable, $keyColumn = null, $labelColumn = null)
+ {
+ assert($storable instanceof DbStorableInterface);
+ if ($keyColumn === null) {
+ $key = $storable->getKeyProperty();
+ if (is_array($key)) {
+ if ($storable->hasAutoIncKey()) {
+ $key = $storable->getAutoIncKeyName();
+ } else {
+ throw new InvalidArgumentException(
+ 'Cannot provide an enum for a multi-key column'
+ );
+ }
+ }
+ } else {
+ $key = $keyColumn;
+ }
+
+ if ($labelColumn === null) {
+ if (method_exists($storable, 'getDisplayColumn')) {
+ $label = $storable->getDisplayColumn();
+ } else {
+ $label = $storable->getKeyProperty();
+ if (is_array($label)) {
+ $label = $key;
+ }
+ }
+ } else {
+ $label = $labelColumn;
+ }
+
+ $columns = [
+ 'key_col' => $key,
+ 'label_col' => $label
+ ];
+
+ $query = $this->db->select()->from(
+ $this->getTableName($storable),
+ $columns
+ );
+
+ return $this->db->fetchPairs($query);
+ }
+
+ protected function insertIntoStore(StorableInterface $storable)
+ {
+ assert($storable instanceof DbStorableInterface);
+ $result = $this->db->insert(
+ $this->getTableName($storable),
+ $storable->getProperties()
+ );
+ /** @var DbStorable $storable */
+ if ($storable->hasAutoIncKey()) {
+ $storable->set(
+ $storable->getAutoIncKeyName(),
+ $this->db->lastInsertId($this->getTableName($storable))
+ );
+ }
+
+ return $result > 0;
+ }
+
+ protected function updateStore(StorableInterface $storable)
+ {
+ assert($storable instanceof DbStorableInterface);
+ $this->db->update(
+ $this->getTableName($storable),
+ $storable->getProperties(),
+ $this->createWhere($storable)
+ );
+
+ return true;
+ }
+
+ protected function deleteFromStore(StorableInterface $storable)
+ {
+ assert($storable instanceof DbStorableInterface);
+ return $this->db->delete(
+ $this->getTableName($storable),
+ $this->createWhere($storable)
+ );
+ }
+
+ protected function loadFromStore(StorableInterface $storable, $key)
+ {
+ assert($storable instanceof DbStorableInterface);
+ $keyColumn = $storable->getKeyProperty();
+ $select = $this->db->select()->from($this->getTableName($storable));
+
+ if (is_string($keyColumn)) {
+ $select->where("$keyColumn = ?", $key);
+ } else {
+ foreach ($keyColumn as $column) {
+ if (array_key_exists($column, $key)) {
+ $select->where("$column = ?", $key[$column]);
+ } else {
+ throw new RuntimeException('Multicolumn key required, got no %s', $column);
+ }
+ }
+ }
+
+ $result = $this->db->fetchAll($select);
+ // TODO: properties should be changed in storeProperties
+ // when you load the element from db before changing it.
+ if (empty($result)) {
+ throw new NotFoundError('Not found: ' . $this->describeKey($storable, $key));
+ }
+
+ if (count($result) > 1) {
+ throw new NotFoundError(sprintf(
+ 'One row expected, got %s: %s',
+ count($result),
+ $this->describeKey($storable, $key)
+ ));
+ }
+
+ $storable->setStoredProperties((array) $result[0]);
+
+ return $storable;
+ }
+
+ protected function describeKey(StorableInterface $storable, $key)
+ {
+
+ assert($storable instanceof DbStorableInterface);
+ $keyColumn = $storable->getKeyProperty();
+ if (is_string($keyColumn)) {
+ return (string) $key;
+ }
+
+ $parts = [];
+ foreach ($keyColumn as $column) {
+ if (array_key_exists($column, $key)) {
+ $parts[$column] = $key[$column];
+ } else {
+ $parts[$column] = '?';
+ }
+ }
+ return implode(', ', $parts);
+ }
+
+ /**
+ * Returns $storable table name
+ *
+ * @param DbStorableInterface $storable
+ * @return string
+ */
+ protected function getTableName(DbStorableInterface $storable)
+ {
+ return $storable->getTableName();
+ }
+
+ /**
+ * @param DbStorableInterface $storable
+ * @return string
+ */
+ protected function createWhere($storable)
+ {
+ $where = [];
+ foreach ((array) $storable->getKeyProperty() as $key) {
+ $value = $storable->get($key);
+ // TODO, eventually:
+ // $key = $this->db->quoteIdentifier($key);
+ if ($value === null) {
+ $where[] = "$key IS NULL";
+ } else {
+ $where[] = $this->db->quoteInto("$key = ?", $value);
+ }
+ }
+
+ return implode(' AND ', $where);
+ }
+}