diff --git a/MysqliDb.php b/MysqliDb.php
index 85a97500..fc0f68ae 100644
--- a/MysqliDb.php
+++ b/MysqliDb.php
@@ -264,7 +264,7 @@ public function rawQuery ($query, $bindParams = null, $sanitize = true)
/**
*
* @param string $query Contains a user-provided select query.
- * @param int $numRows The number of rows total to return.
+ * @param integer|array $numRows Array to define SQL limit in format Array ($count, $offset)
*
* @return array Contains the returned rows from the query.
*/
@@ -321,7 +321,8 @@ public function withTotalCount () {
* A convenient SELECT * function.
*
* @param string $tableName The name of the database table to work with.
- * @param integer $numRows The number of rows total to return.
+ * @param integer|array $numRows Array to define SQL limit in format Array ($count, $offset)
+ * or only $count
*
* @return array Contains the returned rows from the select query.
*/
@@ -453,7 +454,8 @@ public function update($tableName, $tableData)
* Delete query. Call the "where" method first.
*
* @param string $tableName The name of the database table to work with.
- * @param integer $numRows The number of rows to delete.
+ * @param integer|array $numRows Array to define SQL limit in format Array ($count, $offset)
+ * or only $count
*
* @return boolean Indicates success. 0 or 1.
*/
@@ -694,7 +696,8 @@ protected function _buildPair ($operator, $value) {
* any passed update data, and the desired rows.
* It then builds the SQL query.
*
- * @param int $numRows The number of rows total to return.
+ * @param integer|array $numRows Array to define SQL limit in format Array ($count, $offset)
+ * or only $count
* @param array $tableData Should contain an array of data for updating the database.
*
* @return mysqli_stmt Returns the $stmt object.
@@ -937,7 +940,6 @@ protected function _buildGroupBy () {
/**
* Abstraction method that will build the LIMIT part of the WHERE statement
*
- * @param int $numRows The number of rows total to return.
*/
protected function _buildOrderBy () {
if (empty ($this->_orderBy))
@@ -957,7 +959,8 @@ protected function _buildOrderBy () {
/**
* Abstraction method that will build the LIMIT part of the WHERE statement
*
- * @param int $numRows The number of rows total to return.
+ * @param integer|array $numRows Array to define SQL limit in format Array ($count, $offset)
+ * or only $count
*/
protected function _buildLimit ($numRows) {
if (!isset ($numRows))
diff --git a/dbObject.md b/dbObject.md
new file mode 100644
index 00000000..c5af81fe
--- /dev/null
+++ b/dbObject.md
@@ -0,0 +1,225 @@
+dbObject - model implementation on top of the MysqliDb
+Please note, that this library is not pretending to be a full stack ORM but a simple OOP wrapper for mysqlidb
+
+
+###Initialization
+
+1. Include mysqlidb and dbObject classes.
+
+2. If you want to use model autoloading instead of manually including them in the scripts use autoload () method.
+
+```php
+require_once ("libs/MysqliDb.php");
+require_once ("libs/dbObject.php");
+
+// db instance
+$db = new Mysqlidb ('localhost', 'user', '', 'testdb');
+// enable class autoloading
+dbObject::autoload ("models");
+```
+
+3. Create simple user class (models/user.php):
+
+```php
+class user extends dbObject {
+ protected $dbTable = "users";
+ protected $primaryKey = "id";
+ protected $dbFields = Array (
+ 'login' => Array ('text', 'required'),
+ 'password' => Array ('text'),
+ 'createdAt' => Array ('datetime'),
+ 'updatedAt' => Array ('datetime'),
+ );
+}
+```
+###Insert Row
+1. OOP Way. Just create new object of a needed class, fill it in and call save () method. Save will return
+record id in case of success and false in case if insert will fail.
+```php
+$user = new user;
+$user->login = 'demo';
+$user->password = 'demo';
+$id = $user->save ();
+if ($id)
+ echo "user created with id = " . $id;
+```
+
+2. Using arrays
+```php
+$data = Array ('login' => 'demo',
+ 'password' => 'demo');
+$user = new user ($data);
+$id = $user->save ();
+if ($id == null) {
+ print_r ($user->errors);
+ echo $db->getLastError;
+} else
+ echo "user created with id = " . $id;
+```
+
+3. Multisave
+
+```php
+$user = new user;
+$user->login = 'demo';
+$user->pass = 'demo';
+
+$p = new product;
+$p->title = "Apples";
+$p->price = 0.5;
+$p->seller = $user;
+$p->save ();
+```
+
+After save() is call both new objects (user and product) will be saved.
+
+###Selects
+
+Retrieving objects from the database is pretty much the same process of a get ()/getOne () execution without a need to specify table name.
+
+All mysqlidb functions like where(), orWhere(), orderBy(), join etc are supported.
+Please note that objects returned with join() will not save changes to a joined properties. For this you can use relationships.
+
+Select row by primary key
+
+```php
+$user = user::byId (1);
+echo $user->login;
+```
+
+Get all users
+```php
+$users = user::orderBy ('id')->get ();
+foreach (users as $u) {
+ echo $u->login;
+}
+```
+
+Using where with limit
+```php
+$users = user::where ("login", "demo")->get (Array (10, 20));
+foreach (users as $u) ...
+```
+
+###Update
+To update model properties just set them and call save () method. As well values that needed to by changed could be passed as an array to the save () method.
+
+```php
+$user = user::byId (1);
+$user->password = 'demo2';
+$user->save ();
+```
+```php
+$data = Array ('password', 'demo2');
+$user = user::byId (1);
+$user->save ($data);
+```
+
+###Delete
+Use delete() method on any loaded object.
+```php
+$user = user::byId (1);
+$user->delete ();
+```
+
+###Relations
+Currently dbObject supports only hasMany and hasOne relations. To use them declare $relations array in the model class.
+After that you can get related object via variable names defined as keys.
+
+HasOne example:
+```php
+ protected $relations = Array (
+ 'person' => Array ("hasOne", "person", 'id');
+ );
+
+ ...
+
+ $user = user::byId (1);
+ // sql: select * from $persontable where id = $personValue
+ echo $user->person->firstName . " " . $user->person->lastName . " have the following products:\n";
+```
+
+In HasMany Array should be defined target object name (product in example) and a relation key (userid).
+
+HasMany example:
+```php
+ protected $relations = Array (
+ 'products' => Array ("hasMany", "product", 'userid')
+ );
+
+ ...
+
+ $user = user::byId (1);
+ // sql: select * from $product_table where userid = $userPrimaryKey
+ foreach ($user->products as $p) {
+ echo $p->title;
+ }
+```
+###Timestamps
+Library provides a transparent way to set timestamps of an object creation and its modification:
+To enable that define $timestamps array as follows:
+```php
+protected $timestamps = Array ('createdAt', 'updatedAt');
+```
+Field names cant be changed.
+
+###Validation and Error checking
+Before saving and updating the row dbObject do input validation. In case validation rules are set but their criteria is not met
+then save() will return an error with its description. For example:
+```php
+$id = $user->save();
+if (!$id) {
+ // show all validation errors
+ print_r ($user->errors);
+ echo $db->getLastQuery();
+ echo $db->getLastError();
+}
+echo "user were created with id" . $id;
+```
+Validation rules must be defined in $dbFields array.
+```php
+ protected $dbFields = Array (
+ 'login' => Array ('text', 'required'),
+ 'password' => Array ('text'),
+ 'createdAt' => Array ('datetime'),
+ 'updatedAt' => Array ('datetime'),
+ 'custom' => Array ('/^test/'),
+ );
+```
+First parameter is a field type. Types could be the one of following: text, bool, int, datetime or a custom regexp.
+Second parameter is 'required' and its defines that following entry field be always defined.
+
+###Array as return values
+dbObject can return its data as array instead of object. To do that ArrayBuilder() function should be used in the beginning of the call.
+```php
+ $user = user::ArrayBuilder()->byId (1);
+ echo $user['login'];
+
+ $users = user::ArrayBuilder()->orderBy ("id", "desc")->get ();
+ foreach ($users as $u)
+ echo $u['login'];
+```
+
+Following call will return data only of the called instance without any relations data. Use with() function to include relation data as well.
+
+```php
+ $user = user::ArrayBuilder()->with ("product")->byId (1);
+ print_r ($user['products']);
+```
+###Object serialization
+
+Object could be easily converted to a json string or an array.
+
+```php
+ $user = user::byId (1);
+ // echo will display json representation of an object
+ echo $user;
+ // userJson will contain json representation of an object
+ $userJson = $user->toJson ();
+ // userArray will contain array representation of an object
+ $userArray = $user->toArray ();
+```
+
+###Examples
+
+Please look for a use examples in tests/dbObjectTests.php file and test models inside the tests/models/ directory
diff --git a/dbObject.php b/dbObject.php
new file mode 100644
index 00000000..e1ca50cb
--- /dev/null
+++ b/dbObject.php
@@ -0,0 +1,578 @@
+
+ * @copyright Copyright (c) 2015
+ * @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
+ * @version 2.0
+ *
+ * @method int count ()
+ * @method mixed byId (string $id, mixed $fields)
+ * @method mixed get (mixed $limit, mixed $fields)
+ * @method mixed getOne (mixed $fields)
+ * @method mixed paginate (int $page, array $fields)
+ * @method dbObject query ($query, $numRows)
+ * @method dbObject rawQuery ($query, $bindParams, $sanitize)
+ * @method dbObject join (string $objectName, string $key, string $joinType)
+ * @method dbObject with (string $objectName)
+ * @method dbObject groupBy (string $groupByField)
+ * @method dbObject orderBy ($orderByField, $orderbyDirection, $customFields)
+ * @method dbObject where ($whereProp, $whereValue, $operator)
+ * @method dbObject orWhere ($whereProp, $whereValue, $operator)
+ * @method dbObject setQueryOption ($options)
+ * @method dbObject setTrace ($enabled, $stripPrefix)
+ * @method dbObject withTotalCount ()
+ * @method dbObject startTransaction ()
+ * @method dbObject commit ()
+ * @method dbObject rollback ()
+ * @method dbObject ping ()
+ * @method string getLastError ()
+ * @method string getLastQuery ()
+ **/
+class dbObject {
+ /**
+ * Working instance of MysqliDb created earlier
+ *
+ * @var MysqliDb
+ */
+ private $db;
+ /**
+ * Models path
+ *
+ * @var modelPath
+ */
+ private static $modelPath;
+ /**
+ * An array that holds object data
+ *
+ * @var array
+ */
+ public $data;
+ /**
+ * Flag to define is object is new or loaded from database
+ *
+ * @var boolean
+ */
+ public $isNew = true;
+ /**
+ * Return type: 'Array' to return results as array, 'Object' as object
+ *
+ * @var string
+ */
+ public $returnType = 'Object';
+ /**
+ * An array that holds has* objects which should be loaded togeather with main
+ * object togeather with main object
+ *
+ * @var string
+ */
+ private $_with = Array();
+ /**
+ * Per page limit for pagination
+ *
+ * @var int
+ */
+ public $pageLimit = 20;
+ /**
+ * Variable that holds total pages count of last paginate() query
+ *
+ * @var int
+ */
+ public $totalPages = 0;
+ /**
+ * An array that holds insert/update/select errors
+ *
+ * @var array
+ */
+ public $errors = null;
+
+ /**
+ * @param array $data Data to preload on object creation
+ */
+ public function __construct ($data = null) {
+ $this->db = MysqliDb::getInstance();
+ if ($data)
+ $this->data = $data;
+ }
+
+ /**
+ * Magic setter function
+ *
+ * @return mixed
+ */
+ public function __set ($name, $value) {
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * Magic getter function
+ *
+ * @param $name Variable name
+ *
+ * @return mixed
+ */
+ public function __get ($name) {
+ if (property_exists ($this, 'relations') && isset ($this->relations[$name])) {
+ $relationType = strtolower ($this->relations[$name][0]);
+ $modelName = $this->relations[$name][1];
+ switch ($relationType) {
+ case 'hasone':
+ $obj = new $modelName;
+ $obj->returnType = $this->returnType;
+ return $obj->byId($this->data[$name]);
+ break;
+ case 'hasmany':
+ $key = $this->relations[$name][2];
+ $obj = new $modelName;
+ $obj->returnType = $this->returnType;
+ return $obj->where($key, $this->data[$this->primaryKey])->get();
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (isset ($this->data[$name])) {
+ return $this->data[$name];
+ }
+
+ if (property_exists ($this->db, $name))
+ return $this->db->$name;
+ }
+
+ public function __isset ($name) {
+ if (isset ($this->data[$name]))
+ return isset ($this->data[$name]);
+
+ if (property_exists ($this->db, $name))
+ return isset ($this->db->$name);
+ }
+
+ public function __unset ($name) {
+ unset ($this->data[$name]);
+ }
+
+
+ /**
+ * Helper function to create dbObject with Array return type
+ *
+ * @return dbObject
+ */
+ public static function ArrayBuilder () {
+ $obj = new static;
+ $obj->returnType = 'Array';
+ return $obj;
+ }
+
+ /**
+ * Helper function to create dbObject with Object return type.
+ * Added for consistency. Works same way as new $objname ()
+ *
+ * @return dbObject
+ */
+ public static function ObjectBuilder () {
+ $obj = new static;
+ return $obj;
+ }
+
+ /**
+ * @return mixed insert id or false in case of failure
+ */
+ public function insert () {
+ if (!empty ($this->timestamps) && in_array ("createdAt", $this->timestamps))
+ $this->createdAt = date("Y-m-d H:i:s");
+ $sqlData = $this->prepareData ();
+ if (!$this->validate ($sqlData))
+ return false;
+
+ $id = $this->db->insert ($this->dbTable, $sqlData);
+ if (!empty ($this->primaryKey))
+ $this->data[$this->primaryKey] = $id;
+ $this->isNew = false;
+
+ return $id;
+ }
+
+ /**
+ * @param array $data Optional update data to apply to the object
+ */
+ public function update ($data = null) {
+ if (empty ($this->dbFields))
+ return false;
+
+ if (empty ($this->data[$this->primaryKey]))
+ return false;
+
+ if ($data) {
+ foreach ($data as $k => $v)
+ $this->$k = $v;
+ }
+
+ if (!empty ($this->timestamps) && in_array ("updatedAt", $this->timestamps))
+ $this->updatedAt = date("Y-m-d H:i:s");
+
+ $sqlData = $this->prepareData ();
+ if (!$this->validate ($sqlData))
+ return false;
+
+ $this->db->where ($this->primaryKey, $this->data[$this->primaryKey]);
+ return $this->db->update ($this->dbTable, $sqlData);
+ }
+
+ /**
+ * Save or Update object
+ *
+ * @return mixed insert id or false in case of failure
+ */
+ public function save ($data = null) {
+ if ($this->isNew)
+ return $this->insert();
+ return $this->update($data);
+ }
+
+ /**
+ * Delete method. Works only if object primaryKey is defined
+ *
+ * @return boolean Indicates success. 0 or 1.
+ */
+ public function delete () {
+ if (empty ($this->data[$this->primaryKey]))
+ return false;
+
+ $this->db->where ($this->primaryKey, $this->data[$this->primaryKey]);
+ return $this->db->delete ($this->dbTable);
+ }
+
+ /**
+ * Get object by primary key.
+ *
+ * @access public
+ * @param $id Primary Key
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ *
+ * @return dbObject|array
+ */
+ private function byId ($id, $fields = null) {
+ $this->db->where ($this->dbTable . '.' . $this->primaryKey, $id);
+ return $this->getOne ($fields);
+ }
+
+ /**
+ * Convinient function to fetch one object. Mostly will be togeather with where()
+ *
+ * @access public
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ *
+ * @return dbObject
+ */
+ private function getOne ($fields = null) {
+ $results = $this->db->getOne ($this->dbTable, $fields);
+ $this->processArrays ($results);
+ $this->processWith ($results);
+ if ($this->returnType == 'Array')
+ return $results;
+
+ $item = new static ($results);
+ $item->isNew = false;
+
+ return $item;
+ }
+
+ /**
+ * Fetch all objects
+ *
+ * @access public
+ * @param integer|array $limit Array to define SQL limit in format Array ($count, $offset)
+ or only $count
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ *
+ * @return array Array of dbObjects
+ */
+ private function get ($limit = null, $fields = null) {
+ $objects = Array ();
+ $results = $this->db->get ($this->dbTable, $limit, $fields);
+ foreach ($results as &$r) {
+ $this->processArrays ($r);
+ $this->processWith ($r);
+ if ($this->returnType == 'Object') {
+ $item = new static ($r);
+ $item->isNew = false;
+ $objects[] = $item;
+ }
+ }
+ if ($this->returnType == 'Object')
+ return $objects;
+
+ return $results;
+ }
+
+ /**
+ * Function to set witch hasOne or hasMany objects should be loaded togeather with a main object
+ *
+ * @access public
+ * @param string $objectName Object Name
+ *
+ * @return dbObject
+ */
+ private function with ($objectName) {
+ $this->_with[] = $objectName;
+
+ return $this;
+ }
+
+ /**
+ * Function to join object with another object.
+ *
+ * @access public
+ * @param string $objectName Object Name
+ * @param string $key Key for a join from primary object
+ * @param string $joinType SQL join type: LEFT, RIGHT, INNER, OUTER
+ *
+ * @return dbObject
+ */
+ private function join ($objectName, $key = null, $joinType = 'LEFT') {
+ $joinObj = new $objectName;
+ if (!$key)
+ $key = $objectName . "id";
+ $joinStr = "{$this->dbTable}.{$key} = {$joinObj->dbTable}.{$joinObj->primaryKey}";
+ $this->db->join ($joinObj->dbTable, $joinStr, $joinType);
+ return $this;
+ }
+
+ /**
+ * Function to get a total records count
+ *
+ * @return int
+ */
+ private function count () {
+ $res = $this->db->getValue ($this->dbTable, "count(*)");
+ return $res['cnt'];
+ }
+
+ /**
+ * Pagination wraper to get()
+ *
+ * @access public
+ * @param int $page Page number
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ * @return array
+ */
+ private function paginate ($page, $fields = null) {
+ $offset = $this->pageLimit * ($page - 1);
+ $this->db->withTotalCount();
+ $results = $this->get (Array ($this->pageLimit, $offset), $fields);
+ $this->totalPages = round ($this->db->totalCount / $this->pageLimit);
+
+ return $results;
+ }
+
+ /**
+ * Catches calls to undefined methods.
+ *
+ * Provides magic access to private functions of the class and native public mysqlidb functions
+ *
+ * @param string $method
+ * @param mixed $arg
+ *
+ * @return mixed
+ */
+ public function __call ($method, $arg) {
+ if (method_exists ($this, $method))
+ return call_user_func_array (array ($this, $method), $arg);
+
+ call_user_func_array (array ($this->db, $method), $arg);
+ return $this;
+ }
+
+ /**
+ * Catches calls to undefined static methods.
+ *
+ * Transparently creating dbObject class to provide smooth API like name::get() name::orderBy()->get()
+ *
+ * @param string $method
+ * @param mixed $arg
+ *
+ * @return mixed
+ */
+ public static function __callStatic ($method, $arg) {
+ $obj = new static;
+ $result = call_user_func_array (array ($obj, $method), $arg);
+ if (method_exists ($obj, $method))
+ return $result;
+ return $obj;
+ }
+
+ /**
+ * Converts object data to an associative array.
+ *
+ * @return array Converted data
+ */
+ public function toArray () {
+ $data = $this->data;
+ $this->processWith ($data);
+ foreach ($data as &$d) {
+ if ($d instanceof dbObject)
+ $d = $d->data;
+ }
+ return $data;
+ }
+
+ /**
+ * Converts object data to a JSON string.
+ *
+ * @return string Converted data
+ */
+ public function toJson () {
+ return json_encode ($this->toArray());
+ }
+
+ /**
+ * Converts object data to a JSON string.
+ *
+ * @return string Converted data
+ */
+ public function __toString () {
+ return $this->toJson ();
+ }
+
+ /**
+ * @param array $data
+ */
+ private function processWith (&$data) {
+ if (count ($this->_with) == 0)
+ return;
+ foreach ($this->_with as $w)
+ $data[$w] = $this->$w;
+ $this->_with = Array();
+ }
+
+ /**
+ * @param array $data
+ */
+ private function processArrays (&$data) {
+ if (isset ($this->jsonFields) && is_array ($this->jsonFields)) {
+ foreach ($this->jsonFields as $key)
+ $data[$key] = json_decode ($data[$key]);
+ }
+
+ if (isset ($this->arrayFields) && is_array($this->arrayFields)) {
+ foreach ($this->arrayFields as $key)
+ $data[$key] = explode ("|", $data[$key]);
+ }
+ }
+
+ /**
+ * @param array $data
+ */
+ private function validate ($data) {
+ foreach ($this->dbFields as $key => $desc) {
+ $type = null;
+ $required = false;
+ if (isset ($data[$key]))
+ $value = $data[$key];
+ else
+ $value = null;
+
+ if (is_array ($value))
+ continue;
+
+ if (isset ($desc[0]))
+ $type = $desc[0];
+ if (isset ($desc[1]) && ($desc[1] == 'required'))
+ $required = true;
+
+ if ($required && strlen ($value) == 0) {
+ $this->errors[] = Array ($this->dbTable . "." . $key => "is required");
+ continue;
+ }
+ if ($value == null)
+ continue;
+
+ switch ($type) {
+ case "text";
+ $regexp = null;
+ break;
+ case "int":
+ $regexp = "/^[0-9]*$/";
+ break;
+ case "bool":
+ $regexp = '/^[yes|no|0|1|true|false]$/i';
+ break;
+ case "datetime":
+ $regexp = "/^[0-9a-zA-Z -:]*$/";
+ break;
+ default:
+ $regexp = $type;
+ break;
+ }
+ if (!$regexp)
+ continue;
+
+ if (!preg_match ($regexp, $value)) {
+ $this->errors[] = Array ($this->dbTable . "." . $key => "$type validation failed");
+ continue;
+ }
+ }
+ return !count ($this->errors) > 0;
+ }
+
+ private function prepareData () {
+ $this->errors = Array ();
+ $sqlData = Array();
+ if (count ($this->data) == 0)
+ return Array();
+
+ if (method_exists ($this, "preLoad"))
+ $this->preLoad ($data);
+
+ foreach ($this->data as $key => &$value) {
+ if ($value instanceof dbObject && $value->isNew == true) {
+ $id = $value->save();
+ if ($id)
+ $value = $id;
+ else
+ $this->errors = array_merge ($this->errors, $value->errors);
+ }
+
+ if (!in_array ($key, array_keys ($this->dbFields)))
+ continue;
+
+ if (!is_array($value)) {
+ $sqlData[$key] = $value;
+ continue;
+ }
+
+ if (isset ($this->jsonFields) && in_array ($key, $this->jsonFields))
+ $sqlData[$key] = json_encode($value);
+ else if (isset ($this->arrayFields) && in_array ($key, $this->arrayFields))
+ $sqlData[$key] = implode ("|", $value);
+ else
+ $sqlData[$key] = $value;
+ }
+ return $sqlData;
+ }
+
+ private static function dbObjectAutoload ($classname) {
+ $filename = "models/". $classname .".php";
+ include ($filename);
+ }
+
+ /*
+ * Enable models autoload from a specified path
+ *
+ * Calling autoload() without path will set path to dbObjectPath/models/ directory
+ *
+ * @param string $path
+ */
+ public static function autoload ($path = null) {
+ if ($path)
+ static::$modelPath = $path . "/";
+ else
+ static::$modelPath = __DIR__ . "/models/";
+ spl_autoload_register ("dbObject::dbObjectAutoload");
+ }
+}
+?>
diff --git a/tests/dbObjectTests.php b/tests/dbObjectTests.php
new file mode 100644
index 00000000..fbf257cc
--- /dev/null
+++ b/tests/dbObjectTests.php
@@ -0,0 +1,258 @@
+
+error_reporting (E_ALL|E_STRICT);
+require_once ("../MysqliDb.php");
+require_once ("../dbObject.php");
+
+$db = new Mysqlidb('localhost', 'root', '', 'testdb');
+dbObject::autoload ("models");
+
+$tables = Array (
+ 'users' => Array (
+ 'login' => 'char(10) not null',
+ 'active' => 'bool default 0',
+ 'customerId' => 'int(10) not null',
+ 'firstName' => 'char(10) not null',
+ 'lastName' => 'char(10)',
+ 'password' => 'text not null',
+ 'createdAt' => 'datetime',
+ 'updatedAt' => 'datetime',
+ 'expires' => 'datetime',
+ 'loginCount' => 'int(10) default 0'
+ ),
+ 'products' => Array (
+ 'customerId' => 'int(10) not null',
+ 'userId' => 'int(10) not null',
+ 'productName' => 'char(50)'
+ )
+);
+
+$data = Array (
+ 'user' => Array (
+ Array ('login' => 'user1',
+ 'customerId' => 10,
+ 'firstName' => 'John',
+ 'lastName' => 'Doe',
+ 'password' => $db->func('SHA1(?)',Array ("secretpassword+salt")),
+ 'expires' => $db->now('+1Y'),
+ 'loginCount' => $db->inc()
+ ),
+ Array ('login' => 'user2',
+ 'customerId' => 10,
+ 'firstName' => 'Mike',
+ 'lastName' => NULL,
+ 'password' => $db->func('SHA1(?)',Array ("secretpassword2+salt")),
+ 'expires' => $db->now('+1Y'),
+ 'loginCount' => $db->inc(2)
+ ),
+ Array ('login' => 'user3',
+ 'active' => true,
+ 'customerId' => 11,
+ 'firstName' => 'Pete',
+ 'lastName' => 'D',
+ 'password' => $db->func('SHA1(?)',Array ("secretpassword2+salt")),
+ 'expires' => $db->now('+1Y'),
+ 'loginCount' => $db->inc(3)
+ )
+ ),
+ 'product' => Array (
+ Array ('customerId' => 1,
+ 'userId' => 1,
+ 'productName' => 'product1',
+ ),
+ Array ('customerId' => 1,
+ 'userId' => 1,
+ 'productName' => 'product2',
+ ),
+ Array ('customerId' => 1,
+ 'userId' => 1,
+ 'productName' => 'product3',
+ ),
+ Array ('customerId' => 1,
+ 'userId' => 2,
+ 'productName' => 'product4',
+ ),
+ Array ('customerId' => 1,
+ 'userId' => 2,
+ 'productName' => 'product5',
+ ),
+
+ )
+);
+function createTable ($name, $data) {
+ global $db;
+ //$q = "CREATE TABLE $name (id INT(9) UNSIGNED PRIMARY KEY NOT NULL";
+ $q = "CREATE TABLE $name (id INT(9) UNSIGNED PRIMARY KEY AUTO_INCREMENT";
+ foreach ($data as $k => $v) {
+ $q .= ", $k $v";
+ }
+ $q .= ")";
+ $db->rawQuery($q);
+}
+
+// rawQuery test
+foreach ($tables as $name => $fields) {
+ $db->rawQuery("DROP TABLE " . $name);
+ createTable ($name, $fields);
+}
+
+foreach ($data as $name => $datas) {
+ foreach ($data[$name] as $userData) {
+ $obj = new $name ($userData);
+ $id = $obj->save();
+ if ($obj->errors) {
+ echo "errors:";
+ print_r ($obj->errors);
+ exit;
+ }
+ }
+}
+
+$products = product::ArrayBuilder()->get(2);
+foreach ($products as $p) {
+ if (!is_array ($p)) {
+ echo "ArrayBuilder do not return an array\n";
+ exit;
+ }
+}
+
+$product = product::ArrayBuilder()->with('userId')->byId(5);
+if (!is_array ($product['userId'])) {
+ echo "Error in with processing in getOne";
+ exit;
+}
+
+$products = product::ArrayBuilder()->with('userId')->get(2);
+if (!is_array ($products[0]['userId'])) {
+ echo "Error in with processing in get";
+ exit;
+}
+
+$depts = product::join('user')->orderBy('products.id', 'desc')->get(5);
+foreach ($depts as $d) {
+ if (!is_object($d)) {
+ echo "Return should be an object\n";
+ exit;
+ }
+}
+
+$dept = product::join('user')->byId(5);
+if (count ($dept->data) != 13) {
+ echo "wrong props count " .count ($dept->data). "\n";
+ exit;
+}
+if ($db->count != 1) {
+ echo "wrong count after byId\n";
+ exit;
+}
+
+// hasOne
+$products = product::get ();
+$cnt = 0;
+foreach ($products as $p) {
+ if (get_class ($d) != 'product') {
+ echo "wrong class returned\n";
+ exit;
+ }
+
+ if (!($p->userId instanceof user)) {
+ echo "wrong return class of hasOne result\n";
+ exit;
+ }
+
+ $cnt++;
+}
+
+if (($cnt != $db->count) && ($cnt != 5)) {
+ echo "wrong count after get\n";
+ exit;
+}
+
+// hasMany
+$user = user::where('id',1)->getOne();
+if (!is_array ($user->products) || (count ($user->products) != 3)) {
+ echo "wrong count in hasMany\n";
+ exit;
+}
+
+foreach ($user->products as $p) {
+ if (!($p instanceof product)) {
+ echo "wrong return class of hasMany result\n";
+ exit;
+ }
+}
+
+// multi save
+$client = new user;
+$client->login = 'testuser';
+$client->firstName = 'john';
+$client->lastName = 'Doe Jr';
+
+$obj = new product;
+$obj->customerId = 2;
+$obj->userId = 2;
+$obj->productName = "product6";
+$obj->save();
+
+$obj->userId = 5;
+$obj->save();
+
+$obj->userId = $client;
+$obj->save();
+if ($client->errors) {
+ echo "errors:";
+ print_r ($client->errors);
+ exit;
+}
+
+$expected = '{"customerId":2,"userId":{"id":4,"login":"testuser","active":0,"customerId":0,"firstName":"john","lastName":"Doe Jr","password":"","createdAt":"' .$client->createdAt. '","updatedAt":null,"expires":null,"loginCount":0},"productName":"product6","id":6}';
+
+if ($obj->with('userId')->toJson() != $expected) {
+ echo "Multisave problem\n";
+ echo $obj->with('userId')->toJson();
+ exit;
+}
+
+$u= new user;
+$u->active='test';
+$u->customerId = 'test';
+$u->expires = 'test;';
+$u->firstName = 'test';
+
+$obj = new product;
+$obj->userId = $u;
+$obj->save();
+if ($obj->save()) {
+ echo "validation 1 failed\n";
+ exit;
+}
+if (count ($obj->errors) != 7) {
+ print_r ($obj->errors);
+ echo "validation 2 failed\n";
+ exit;
+}
+
+if (!user::byId(1) instanceof user)
+ echo "wrong return type1";
+
+if (!is_array (user::ArrayBuilder()->byId(1)))
+ echo "wrong return type2";
+
+if (!is_array (product::join('user')->orderBy('products.id', 'desc')->get(2)))
+ echo "wrong return type2";
+
+if (!is_array (product::orderBy('products.id', 'desc')->join('user')->get(2)))
+ echo "wrong return type2";
+
+$u = new user;
+if (!$u->byId(1) instanceof user)
+ echo "wrong return type2";
+
+$p = new product;
+if (!is_array ($p->join('user')->orderBy('products.id', 'desc')->get(2)))
+ echo "wrong return type2";
+
+if (!is_array ($p->orderBy('products.id', 'desc')->join('user')->get(2)))
+ echo "wrong return type2";
+
+echo "All done";
+?>
diff --git a/tests/models/product.php b/tests/models/product.php
new file mode 100644
index 00000000..7e0033c9
--- /dev/null
+++ b/tests/models/product.php
@@ -0,0 +1,29 @@
+ Array('int', 'required'),
+ 'customerId' => Array ('int', 'required'),
+ 'productName' => Array ('text','required')
+ );
+ protected $relations = Array (
+ 'userId' => Array ("hasOne", "user")
+ );
+
+ public function last () {
+ $this->where ("id" , 130, '>');
+ return $this;
+ }
+}
+
+
+?>
diff --git a/tests/models/user.php b/tests/models/user.php
new file mode 100644
index 00000000..6929e2fa
--- /dev/null
+++ b/tests/models/user.php
@@ -0,0 +1,40 @@
+ Array ('text', 'required'),
+ 'active' => Array ('bool'),
+ 'customerId' => Array ('int'),
+ 'firstName' => Array ('/[a-zA-Z0-9 ]+/'),
+ 'lastName' => Array ('text'),
+ 'password' => Array ('text'),
+ 'createdAt' => Array ('datetime'),
+ 'updatedAt' => Array ('datetime'),
+ 'expires' => Array ('datetime'),
+ 'loginCount' => Array ('int')
+ );
+
+ protected $timestamps = Array ('createdAt', 'updatedAt');
+ protected $relations = Array (
+ 'products' => Array ("hasMany", "product", 'userid')
+ );
+}
+
+
+?>
diff --git a/tests.php b/tests/mysqliDbTests.php
similarity index 99%
rename from tests.php
rename to tests/mysqliDbTests.php
index d6ba3c94..852877b2 100644
--- a/tests.php
+++ b/tests/mysqliDbTests.php
@@ -1,5 +1,5 @@
delete("products");
-echo "All done";
//print_r($db->rawQuery("CALL simpleproc(?)",Array("test")));
print_r ($db->trace);
+echo "All done";
?>