Commit baf4d2a1 authored by Alexander Butenko's avatar Alexander Butenko

Merge pull request #235 from avbdr/master

new dbObject class
parents fef36b1d a96ea382
......@@ -264,7 +264,7 @@ class MysqliDb
/**
*
* @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 @@ class MysqliDb
* 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 @@ class MysqliDb
* 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 @@ class MysqliDb
* 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 @@ class MysqliDb
/**
* 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 @@ class MysqliDb
/**
* 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))
......
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
<hr>
###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
This diff is collapsed.
<?
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";
?>
<?php
/**
* To make IDEs autocomplete happy
*
* @property int id
* @property int userid
* @property int customerId
* @property string productName
*/
class product extends dbObject {
protected $dbTable = "products";
protected $primaryKey = "id";
protected $dbFields = Array (
'userId' => 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;
}
}
?>
<?php
/**
* To make IDEs autocomplete happy
*
* @property int id
* @property string login
* @property bool active
* @property string customerId
* @property string firstName
* @property string lastName
* @property string password
* @property string createdAt
* @property string updatedAt
* @property string expires
* @property int loginCount
*/
class user extends dbObject {
protected $dbTable = "users";
protected $primaryKey = "id";
protected $dbFields = Array (
'login' => 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')
);
}
?>
<?php
require_once ("MysqliDb.php");
require_once ("../MysqliDb.php");
error_reporting(E_ALL);
$db = new Mysqlidb('localhost', 'root', '', 'testdb');
......@@ -358,9 +358,9 @@ if ($db->count != 0) {
}
$db->delete("products");
echo "All done";
//print_r($db->rawQuery("CALL simpleproc(?)",Array("test")));
print_r ($db->trace);
echo "All done";
?>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment