summaryrefslogtreecommitdiff
path: root/idiorm.php
diff options
context:
space:
mode:
authorJamie Matthews <[email protected]>2010-02-10 20:59:03 +0000
committerJamie Matthews <[email protected]>2010-02-10 20:59:03 +0000
commite2f1b7f8e19a9c428ed5fee6baf96c46e529f04a (patch)
tree91a07be1a17825ad619b01fd8ab8b47f4ece96d0 /idiorm.php
parent59f4bdae494589dded0dcbac8eae1ef3f5f82a94 (diff)
Improved comments, added license etc
Diffstat (limited to 'idiorm.php')
-rw-r--r--idiorm.php223
1 files changed, 208 insertions, 15 deletions
diff --git a/idiorm.php b/idiorm.php
index f0d5b38..7b53bd3 100644
--- a/idiorm.php
+++ b/idiorm.php
@@ -1,8 +1,48 @@
<?php
+ /**
+ *
+ * Idiorm
+ *
+ * A single-class super-simple database abstraction layer for PHP.
+ * Provides (nearly) zero-configuration object-relational mapping
+ * and a fluent interface for building basic, commonly-used queries.
+ *
+ * Version 0.1
+ *
+ * BSD Licensed.
+ *
+ * Copyright (c) 2009, Jamie Matthews
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
class ORM {
- // -- Class constants -- //
+ // ----------------------- //
+ // --- CLASS CONSTANTS --- //
+ // ----------------------- //
// Select WHERE operators
const EQUALS = '=';
@@ -21,8 +61,11 @@
const VALUE = 1;
const OPERATOR = 2;
+ // ------------------------ //
+ // --- CLASS PROPERTIES --- //
+ // ------------------------ //
- // -- Class properties -- //
+ // Class configuration
private static $config = array(
'connection_string' => 'sqlite://:memory:',
'id_column' => 'id',
@@ -32,21 +75,44 @@
// Database connection, instance of the PDO class
private static $db;
- // -- Instance properties -- //
+ // --------------------------- //
+ // --- INSTANCE PROPERTIES --- //
+ // --------------------------- //
+
+ // The name of the table the current ORM instance is associated with
private $table_name;
- private $find_type; // will be FIND_ONE or FIND_MANY
- private $values = array(); // Values to be bound to the query
+
+ // Will be FIND_ONE or FIND_MANY
+ private $find_type;
+
+ // Values to be bound to the query
+ private $values = array();
+
+ // Array of WHERE clauses
private $where = array();
+ // The data for a hydrated instance of the class
private $data = array();
+
+ // Fields that have been modified during the
+ // lifetime of the object
private $dirty_fields = array();
// Are we updating or inserting?
private $update_or_insert = self::UPDATE;
- // -- Static methods -- //
+ // ---------------------- //
+ // --- STATIC METHODS --- //
+ // ---------------------- //
+
+ /**
+ * Pass configuration settings to the class in the form of
+ * key/value pairs. As a shortcut, if the second argument
+ * is omitted, the setting is assumed to be the DSN string
+ * used by PDO to connect to the database. Often, this
+ * will be the only configuration required to use Idiorm.
+ */
public static function configure($key, $value=null) {
-
// Shortcut: If only one argument is passed,
// assume it's a connection string
if (is_null($value)) {
@@ -56,16 +122,40 @@
self::$config[$key] = $value;
}
+ /**
+ * Despite its slightly odd name, this is actually the factory
+ * method used to acquire instances of the class. It is named
+ * this way for the sake of a readable interface, ie
+ * ORM::for_table('table_name')->find_one()-> etc. As such,
+ * this will normally be the first method called in a chain.
+ */
public static function for_table($table_name) {
return new self($table_name);
}
+ /**
+ * Set up the database connection used by the class.
+ */
private static function setup_db() {
self::$db = new PDO(self::$config['connection_string']);
self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
+ /**
+ * This can be called if the ORM should use a ready-instantiated
+ * PDO object as its database connection. Won't be used in normal
+ * operation, but it's here in case it's needed.
+ */
+ public static function set_db($db) {
+ self::$db = $db;
+ }
+
+ /**
+ * Returns the PDO instance used by the the ORM to communicate with
+ * the database. This can be called if any low-level DB access is
+ * required outside the class.
+ */
public static function get_db() {
if (!is_object(self::$db)) {
self::setup_db();
@@ -73,31 +163,89 @@
return self::$db;
}
- // -- Instance methods -- //
+ // ------------------------ //
+ // --- INSTANCE METHODS --- //
+ // ------------------------ //
+
+ /**
+ * Private constructor; can't be called directly.
+ * Use a factory method instead (probably ORM::for_table)
+ */
private function __construct($table_name, $data=array()) {
$this->table_name = $table_name;
$this->data = $data;
}
- public function create() {
+ /**
+ * Create a new, empty instance of the class. Used
+ * to add a new row to your database. May optionally
+ * be passed an associative array of data to populate
+ * the instance. If so, all fields will be flagged as
+ * dirty so all will be saved to the database when
+ * save() is called.
+ */
+ public function create($data=null) {
$this->update_or_insert = self::INSERT;
- return $this;
- }
- public function hydrate($table_name, $data=array()) {
- return new self($table_name, $data);
+ if (!is_null($data)) {
+ return $this->hydrate($data)->force_all_dirty();
+ }
+ return $this;
}
+ /**
+ * Tell the ORM that you are expecting a single result
+ * back from your query. If this method has been called
+ * in your chain, when you call run() you will receive
+ * a single instance of the ORM class, or false if no
+ * rows were returned.
+ */
public function find_one() {
$this->find_type = self::FIND_ONE;
return $this;
}
+ /**
+ * Tell the ORM that you are expecting multiple results
+ * from your query. If this method has been called in your
+ * chain, when you call run() you will receive an array
+ * of instances of the ORM class, or an empty array if
+ * no rows were returned.
+ */
public function find_many() {
$this->find_type = self::FIND_MANY;
return $this;
}
+ /**
+ * This method can be called hydrate (populate) this
+ * instance of the class from an associative array of data.
+ * This will usually be called only from inside the class,
+ * but it's public in case you need to call it directly.
+ */
+ public function hydrate($data=array()) {
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * Force the ORM to flag all the fields in the $data array
+ * as "dirty" and therefore update them when save() is called.
+ */
+ public function force_all_dirty() {
+ $this->dirty_fields = $this->data;
+ return $this;
+ }
+
+ /**
+ * Add a WHERE clause to your query. Each time this is called
+ * in the chain, an additional WHERE will be added, and these
+ * will be ANDed together when the final query is built.
+ * By default, the operator used is '=', but the third
+ * parameter to this method may be used to indicate other
+ * operators such as LIKE. Class constants should be used to
+ * provide this operator.
+ */
public function where($column_name, $value, $operator=self::EQUALS) {
$this->where[] = array(
self::COLUMN_NAME => $column_name,
@@ -107,6 +255,10 @@
return $this;
}
+ /**
+ * Build a SELECT statement based on the clauses that have
+ * been passed to this instance by chaining method calls.
+ */
private function build_select() {
$query = array();
$query[] = 'SELECT * FROM ' . $this->table_name;
@@ -135,6 +287,13 @@
return join(" ", $query);
}
+ /**
+ * Execute the SELECT query that has been built up by chaining methods
+ * on this class. This should usually be the last method in your chain.
+ * If find_one() has been called, this will return a single instance of
+ * the class or false. If find_many() has been called, this will return
+ * an array of instances of the class.
+ */
public function run() {
self::setup_db();
$statement = self::$db->prepare($this->build_select());
@@ -142,16 +301,23 @@
if ($this->find_type == self::FIND_ONE) {
$result = $statement->fetch(PDO::FETCH_ASSOC);
- return $result ? self::hydrate($this->table_name, $result) : $result;
+ return $result ? self::for_table($this->table_name)->hydrate($result) : $result;
} else {
$instances = array();
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
- $instances[] = self::hydrate($this->table_name, $row);
+ $instances[] = self::for_table($this->table_name)->hydrate($row);
}
return $instances;
}
}
+ /**
+ * For debugging only. Returns a string representation of the query
+ * that would be executed by calling run() on the current instance of the
+ * class. Because PDO works using prepared statements, this can provide
+ * only a rough representation of the query, but this will usually be enough
+ * to check that your query has been build as expected.
+ */
public function as_sql() {
$sql = $this->build_select();
$sql = str_replace("?", "%s", $sql);
@@ -163,10 +329,18 @@
return vsprintf($sql, $quoted_values);
}
+ /**
+ * Return the value of a property of this object (database row)
+ * or null if not present.
+ */
public function get($key) {
return isset($this->data[$key]) ? $this->data[$key] : null;
}
+ /**
+ * Return the name of the column in the database table which contains
+ * the primary key ID of the row.
+ */
private function get_id_column_name() {
if (isset(self::$config['id_column_overrides'][$this->table_name])) {
return self::$config['id_column_overrides'][$this->table_name];
@@ -175,20 +349,38 @@
}
}
+ /**
+ * Get the primary key ID of this object.
+ */
public function id() {
return $this->get($this->get_id_column_name());
}
+ /**
+ * Set a property to a particular value on this object.
+ * Flags that property as 'dirty' so it will be saved to the
+ * database when save() is called.
+ */
public function set($key, $value) {
$this->data[$key] = $value;
$this->dirty_fields[$key] = $value;
}
+ /**
+ * Save any fields which have been modified on this object
+ * to the database.
+ */
public function save() {
$query = array();
$values = array_values($this->dirty_fields);
if ($this->update_or_insert == self::UPDATE) {
+
+ // If there are no dirty values, do nothing
+ if (count($values) == 0) {
+ return true;
+ }
+
$query[] = "UPDATE";
$query[] = $this->table_name;
$query[] = "SET";
@@ -217,6 +409,7 @@
$query = join(" ", $query);
$statement = self::$db->prepare($query);
$statement->execute($values);
+ return true;
}
}