Class TipyModel
M in MVC. TipyModel is ORM connecting objects to database
TipyModel:
- Represents row in a table
- Defines associations and hierarchies between models
- Validates models before they get persisted to the database
- Performs database operations in an object-oriented fashion
Conventions
TipyModel follows "Convention over Configuration" paradigm and tries to use as little configuration as possible. Of course you can configure model-database mapping in the way you wish but you will write your code much faster following TipyModel conventions:
- Class Name - singular, CamelCase, first letter in upper case. - BlogPost
- Table Name - plural, snake_case, all letters in lower case - blog_posts
- Model Properties - camelCase, first letter in lower case - createdAt
- Table Fields - snake_case, all letters in lower case - created_at
- Foreign Key - foreign table name + "_id" - author_id
- Primary Key - is always id
- If your table has created_at and updated_at fields they will be handled automatically
These conventions can be changed by overriding TipyModel methods:
TipyModel::classNameToTableName(),
TipyModel::fieldNameToAttrName(),
TipyModel::tableForeignKeyFieldName(),
TipyModel::classForeignKeyAttr(),
and constants:
TipyModel::CREATED_AT,
TipyModel::UPDATED_AT
Defining Models
Let's say you have the following table
create table users (
id int(11),
first_name varchar(255),
last_name varchar(255),
primary key (id)
);
To make TipyModel from this table you simply need to extend TipyModel class
class User extends TipyModel { }
This magically connect User class to users table (see Conventions section above) gives User class a lot of useful methods and magic properties to access table fields
// create new User object and save it to database $user = new User(); $user->firstName = 'John'; $user->lastName = 'Doe'; $user->save(); // or like this $user = User::create([ 'firstName' => 'John', 'lastName' => 'Doe' ]); $id = $user->id; $sameUser = User::load($id); echo $sameUser->firstName;
Validation
Model-level validation is the best way to ensure that only valid data is saved into database. It cannot be bypassed by end users and is convenient to test and maintain.
Validations are run autmatically before TipyModel::save() and TipyModel::update() send
SQL INSERT or UPDATE queries to the database.
To add validation to your model simply override TipyModel::validate() method.
class User extends TipyModel { public function validate() { if (!$this->firstName) throw new TipyValidtionException('First name should not be blank!'); if (!$this->lastName) throw new TipyValidtionException('Last name should not be blank!'); } }
The common way to fail validation is to throw TipyValidtionException and then to catch it in controller.
Hooks
TipyModel allows to define logic triggered before or after an alteration of the model state. To do this override the following methods in your model:
TipyModel::beforeCreate()TipyModel::afterCreate()TipyModel::beforeUpdate()TipyModel::afterUpdate()TipyModel::beforeDelete()TipyModel::afterDelete()
Associations
Association is a connection between two models. By declaring associations you define Primary Key-Foreign Key connection between instances of the two models, and you also get a number of utility methods added to your model. Tipy supports the following types of associations:
To define define model associations you need to assign values to these properties.
class User extends TipyModel { protected $hasMany = ['posts', 'comments']; }
Association class name is evaluated automatically by TipyInflector::classify() inflection.
If you wan't to specify class name different from association name you can pass association
options as arrays:
class User extends TipyModel { protected $hasMany = [ 'posts' => ['class' => 'BlogPost'], 'comments' => ['class' => 'BlogComment'] ]; }
hasMany
A hasMany association indicates a one-to-many connection with another model.
create table users ( create table blog_posts (
id int(11), <---------------+ id int(11),
first_name varchar(255), | title varchar(255),
last_name varchar(255), | body text,
primary key (id) +----- user_id int(11),
); primary key (id)
);
class User extends TipyModel { protected $hasMany = [ 'posts' => ['class' => 'BlogPost'] ); }
This gives User model magic property User::posts
$posts = $user->posts;
hasOne
A hasOne association indicates a one-to-one connection with another model.
create table users ( create table accounts (
id int(11), <---------------+ id int(11),
first_name varchar(255), | cc_number varchar(20),
last_name varchar(255), | cc_expire_date varchar(5),
primary key (id) +----- user_id int(11),
); primary key (id)
);
class User extends TipyModel { protected $hasOne = ['account']; }
This gives User model magic property User::account
$ccNumber = $user->account->ccNumber;
belongsTo
A belongsTo association is an opposite to hasMany and hasOne
create table users ( create table blog_posts (
id int(11), <---------------+ id int(11),
first_name varchar(255), | title varchar(255),
last_name varchar(255), | body text,
primary key (id) +----- user_id int(11),
); primary key (id)
);
class BlogPost extends TipyModel { protected $belongsTo = ['user']; }
This gives BlogPost model magic property BlogPost->user
$firstName = $post->user->firstName;
hasManyThrough
A hasManyThrough association indicates a many-to-many connection with another model through a third model.
create table users (
id int(11), <-------------+
first_name varchar(255), | create table memberships (
last_name varchar(255) +------ user_id int(11).
); id int(11),
+------ group_id int(11)
create table groups ( | );
id int(11), <-------------+
name varchar(255)
);
class User extends TipyModel { protected $hasManyThrough = [ 'groups' => ['class' => 'Group', 'through' => 'Membership'], ); }
This gives User model magic property User::groups
$groups = $user->groups;
Association Options
Association options are arrays with the following keys
- 'class' - associated model class
- 'dependent' - 'delete', 'nullify', or null - What to do with associated model rows when this model row is deleted
- 'conditions' - conditions to select associated records in addition to foreign_key.
- 'values' - values for conditions
- 'foreign_key' - custom associated record foreign key
- 'through_key' - custom second foreign key for $hasManyThrough
class User extends TipyModel { protected $hasMany = [ 'messages' => [ 'class' => 'Message', 'dependent' => 'delete' ], // 7 days old messages 'oldMessages' => [ 'class' => 'Message', 'conditions' => 'created_at < ?', 'values' => [strtotime('-1 week')] ] ]; }
Associations Cache
Associations are cached. So if you call
$post->comments
more than once only one query will be executed (first call) and then comments will always be taken from cache.
Downside of this approach: Cache doesn't know if comments were deleted or modified in the database. To reset associations cache use TipyModel::reload()
Associations with conditions are not cached. This means that
$post->comments(['order' => 'created_at desc'])
will allways execute query
In short always look for parethesis:
$post->comments; // is cached $post->comments(...); // is not cached
- TipyDAO
-
TipyModel
Todo: Accept conditions and values in one array 'conditions' => ['id = ?', $id]
Todo: Improve find() to accept arguments like Post::find('first', 'conditions' => ['created_at > ?', time()]);
Located at src/TipyModel.php
public
|
|
public
|
|
public
|
|
public
|
|
public
|
|
public
|
|
protected
|
|
public static
|
|
public
boolean
|
|
public
mysqli_result
|
|
public
|
|
public
|
|
public static
|
|
protected
mysqli_result
|
|
protected
mysqli_result
|
|
public
mysqli_result
|
|
public static
integer
|
|
public static
array
|
|
public static
|
|
public
|
|
protected static
|
#
instanceFromResult(
Fill model instance with data from mysqli_result |
protected
mixed
|
|
public
|
|
public
|
|
public
|
|
public
|
|
public
|
|
public
|
|
public
|
|
protected static
string
|
|
protected static
string
|
|
protected static
string
|
|
protected static
string
|
|
protected static
integer
|
affectedRows(),
fetchAllRows(),
fetchRow(),
isTransactionInProgress(),
lastInsertId(),
limitQuery(),
limitQueryAllRows(),
numRows(),
query(),
queryAllRows(),
queryRow(),
rollback(),
transaction()
|
string |
CREATED_AT
|
#
'created_at'
|
string |
UPDATED_AT
|
#
'updated_at'
|
protected static
array
|
$mysqlToPhpTypes
Rules to cast MySQL types to PHP types |
#
[
'char' => 'string',
'varchar' => 'string',
'binary' => 'string',
'varbinary' => 'string',
'blob' => 'string',
'text' => 'string',
'enum' => 'string',
'set' => 'string',
'integer' => 'integer',
'int' => 'integer',
'smallint' => 'integer',
'tinyint' => 'integer',
'mediumint' => 'integer',
'bigint' => 'integer',
'bit' => 'integer',
'boolean' => 'integer',
'decimal' => 'float',
'numeric' => 'float',
'double' => 'float',
'date' => 'datetime',
'datetime' => 'datetime',
'timestamp' => 'datetime'
]
|
protected static
array
|
$globalReflections
Global models<->tables reflections cache |
#
[]
|
public
string
|
$className
Model class name |
|
public
string
|
$table
Table name |
|
public
array
|
$attributes
Magic properties to access row columns |
|
public
array
|
$fields
Table column names list |
|
public
array
|
$fieldTypes
Table column types list |
|
public
array
|
$reflections
column => property reflections |
|
public
array
|
$data
Magic properties values |
|
public
boolean
|
$isDeletedRecord
True if table row represented by model is deleted |
|
public
array
|
$associationsCache
Associations cache |
|
protected
array
|
$hasMany
One-to-many associations |
|
protected
array
|
$hasOne
One-to-one associations |
|
protected
array
|
$belongsTo
Many-to-one associations |
|
protected
array
|
$hasManyThrough
Many-to-many associations through a third models |
$dbLink,
$logger
|