Model::where()
Model::read()
Model::update()
Model::delete()
Model::ofUser()
Model::of()
read()
, update()
and delete()
methods can be applied upon it. Assuming we have a model class User
as below:
<?php namespace spoova/mi/windows/Models; class User extends Model { function __construct(){ } }
where()
method from a route as shown below:
<?php namespace spoova/mi/windows/Routes; use spoova/mi/windows/Models; class SomeRoute{ function __construct() { User::where('id = ?', [1])->read()->User; // read user where id is one User::where('id = ?', [1])->delete(); // delete user where id is 1 User::where('id = ?', [1])->update(['firstname'=>'Felix']); // update user where id is 1, set firstname as "Felix" } }
where()
condition can be applied. The first argument contains list of fields and placeholders
for binded parameters while the second argument contains a list of binded parameter values. The assumed table name will be "users". The table name can be
modified by re-defining a tableName()
method on the model class which must return a string of the custom table name.
read()
method.
User::read()->User; // fetch all user data User::read(['username'])->User; // fetch only the username of every user User::read(['firstname', 'lastname'], [10])->User; // fetch firstname and lastname of 10 users
update()
method is used to update a selected record.
It takes a single array parameter. Which contains key(field name) and value(new) pairs. When a condition is not set upon it,
all records will be updated:
Posts::update(['date' => 2024-12-14 ]) //update all posts records, set all date rows as "2024-12-14"
delete()
method takes a single parameter which can either be a bool of true or an integer limit of number of data to be deleted. The limit may not
be applicable on multiple table. Calling this method without a condition can be dangerous as all records
belonging to the relative database table may get deleted. The bool argument of "true" ensures that a developer
is aware of the changes they are about to make (i.e deleting all records) before making them. If no argument is supplied, and no condition is set on the delete method,
this method will not delete any data. It is also advised to to keep the live server off if this method will be applied.
Posts::delete(true); //delete all posts
ofUser()
method is used to pull a data of the current user session account.
The database default structure format demands that any table owned by the current user must have a
user_id
foreign key column that is mapped to the id field on the user's table. When data
are obtained using models, ofUser()
makes it possible to pull only data related to the current user.
For example: If a database table "user"
contains an id (primary key) column, then a table (comments) must have a field
name with user_id
. The user_id is then used to pull data from the comments
table which belongs
to the current session user.
<?php namespace spoova\mi\core\class\Models; use Models; Posts extends Models { } public static function tableName() { return 'Posts'; } }
ofUser()
static method is applied on "Posts" (i.e Posts::ofUser()
), then spoova will try to find
the Posts related to the current user id by looking for a user_id
field in the
Posts
table. By default, this method uses the current user id to pull data, however, this can be modified.
The ofUser()
method takes its first argument as an integer. This integer is the id of the user
whose data must be pulled from the database. For example:
<?php namespace spoova\mi\windows\Routes; use Window use spoova\mi\windows\Models\Posts; Home extends UserFrame { function __construct() { $currentUserPosts = Posts::ofUser()->read()->Posts; $customUserPosts = Posts::ofUser(2)->read()->Posts; } } }
read()
method is used to read data from the database while the ->Posts
is the current Model name which stores the data obtained as a traversable object. When an error occurs, the last error
is saved and can be obtained using the read()->DBError
property. In the event that the foreignKey field name is
not user_id, then a second argument can be supplied into the ofUser()
method to define a new key name, In this
relationship, the user database table (i.e user) will be the owner while the Model (e.g Posts) table is being owned. We don't need to set the
user table since that has been done in the icore/init
file during installation.
of()
method is similar to the ofUser()
method. In this method, the table name can be
customized if the relationship is between a model and any other database table. The first argument
takes a new database table name while other arguments follows the ForeignId and ForeignKey structure respectively. By default,
if the owner database table for example is "admin"
, then a owned table "posts"
must have an "admin_id" foreign key field
while the owner table must have an "id" local key field which helps spoova
to naturally connect the two fields. For example:
Posts::of('admin', 3)->read()->Posts;
Posts::of('admin', 3, 'user_id')->read()->Posts;
user_id
field.
It is assumed that the custom foreign key supplied in this case is related to the admin table's local key "id".
This means that the admin table must have an "id" primary key field.
bind()
method is a method that can be called upon the model's of()
method.
The bind()
method is used to set up a connection between three database fields. In this
connection, the binded table is the highest table while the current model is the lowest table. The database table structures
resemble the format below:
comments -id -post_id posts -id -user_id user -id
Comments::of('posts', 3)->bind('users')->read()->Comments;
comments
table has a foreign key field of post_id
while the
posts
table has a foreign key field of user_id
. From the sample above, the
comment table will look within itself for where post_id foreign key is equivalent to posts table local key "id" 3.
then the posts table will bind to its own parent "users" through the parent foreign key field name "user_id".
This relationship can thus be defined as a complex relationship, one that is defined for a Child, Parent and GrandParent.
In the event that the foreign key of the post table on comments table above is not post_id
, this can be modified by supplying a third
argument of the foreign key field name on the of()
method.
Comments::of('posts', 3, 'foreignKey')->bind('users')->read()->Comments;
vdump()
helper function might serve a good purpose to view the type of data returned.
user -id -username admin -id -user_id
class Admin extends Model { }
class User extends Model { function __construct(){ } function admin(){ return matchOne(Admin::class); } }
"user_id"
foreign key
field within the admin table. The "user_id" is generated from the combination of the
current model's name and a default local key name "id". Once the relationship is defined,
we can obtain the data by calling the method as below:
User::admin()->read()->admin;
read()
method is a directive that tells the Model to obtain the defined relationship data between a model and
its respective child models. Also, unlike the of()
and ofUser()
methods that use
the Model's name as a property to obtain data, when accessing data under any of the known predefined relationships,
the first parameter of any relationship is used to obtain the collected data. For example, the admin
property was used to access the data because the first argument of matchOne()
is the Admin::class
.
In situations where the foreign key is not "user_id", we can supply the foreign key as the second parameter.
Also if the local key is not "id", we can supply the local key as the third argument just as below:
class User extends Model { function __construct(){ } function admin(){ return matchOne(Admin::class, 'foreign_key', 'local_key'); } }
tableName()
method which can be applied for any model. For example in the code below, the "PostItems" model will
assume that the database table name is post
rather than postitems
because the default behaviour was changed. class PostItems extends Model { function __construct(){ } function admin(){ return matchOne(Some::class); } static function tableName() : string{ return 'posts'; } }
student -id -name -book_id book -id -author_id author -id -user_id
matchOneFor
relationship as shown below:
class Student extends Model { static function bookAuthor(){ return matchOneFor(Author::class, Book::class); } }
student
is connected to the book
by the
book_id
foreign key and the book
is connected to the author
by the author_id
foreign key,
the matchOneFor
relationship will pull the author data by using the Book
model.
The matchOneFor(Author::class, Book::class)
simply translates as "match one Author::class for Book::class".
Once the relationship is defined we can obtain our data as below:
Student::bookAuthor()->read()->author;
author
and book
are both id
respectively.
We can however, overide this condition by supplying a custom Foreign and Local keys for
both the author
and the book
by supplying a third and fourth
argument respectively as an array of Foreign and local keys, where the first value of the array
is the foreign key while the second value is a local key just as shown below:
class Student extends Model { static function bookAuthor(){ return matchOneFor(Author::class, Book::class, ['authorForeignKey', 'authorLocalKey'], ['bookForeignKey', 'bookLocalKey']); } }
Similarly to "one-to-one" relationship, in "one-to-many" relationship, the child model class method is placed on its Parent Model. Within the child Model, the "matchMany" relationship is defined. Once this relationship is defined, then we can call the method.
Consider the database tables "posts" and "comments". Each post can have many comments. Using the table structure below, we can set up a one to many relationship:
posts -id -username comments -id -post_id
<?php use spoova\mi\core\classes\Model; Posts extends Model { static function comments(){ return self::matchMany(Comments::class); } }
Post::comments()->read()->comments;
Post
model owns many comments, then spoova assumes that we are trying to read
the comments which are more than one. We can also modify the foreign and local key of the parent model through a second and/or third argument.
as shown below:
<?php use spoova\mi\core\classes\Model; Posts extends Model { static function comments(){ return self::matchMany(Comments::class, 'foreign_key', 'local_key'); } }
matchedFor
sets an inverse relationship for these two relationship
(i.e matchOne and matchMany). In this case, the current model where this relationship is defined assumes that it is being owned by a parent model.
This relationship can be defined by placing a Parent model class method on its Child Model. Within the Child Model, the "matchedFor" relationship will be defined
This means that the first parameter of the matchedFor
must be a parent model. Once the relationship is defined, then we can call the method.
<?php use spoova\mi\core\classes\Model; Posts extends Model { static function user(){ return self::matchedFor(User::class); } }
Posts::user()->read()->posts;
Post
model and the User
model as one between
a child and a parent respectively. We can also modify the foreign and local key of the supplied class through a second and
third argument as shown below:
<?php use spoova\mi\core\classes\Model; Posts extends Model { static function user(){ return self::matchedFor(User::class, 'foreign_key', 'local_key'); } }
users -id -name roles -id -role user_role -id -user_id -role_id
users
and roles
table both have a binding
relationship through a third table role_user
which by default contains fields generated by the
singular form of its source tables along with a default "id" local key specific to both parent fields. Both the
singular form and the default local key are separated by and underscore "_". It is important to note that the
singular form of any table is genarated by stripping off the last "s" character of that table name. This means that
while a singular form of users
will be user
, the singular form of user
will
also be user
. Using the table structure above, we can set up our relationship as shown below:
<?php class User extends Model { static function roles(){ return maps(Roles::class); } }
roles
table by using the relationship it has with the user_role
table. If the linking table is not user_role
which is the default, then we can supply the custom table name as the second argument.
Also, in this relationship, the parent tables users
and roles
must have an "id" primary key. However, the foreign keys can be
modified. The supplied model's custom foreignkey and the current model's foreign key on the linking table can be supplied as the third and fourth argument
respectively. Hence we can have a format as below:
<?php class User extends Model { static function roles(){ return maps(Roles::class, 'bindingTable', 'rolesForeignKey', 'userForeignKey'); } }
Roles
class just as below
<?php class Roles extends Model { static function roles(){ return maps(User::class); } }
All properties obtained in any database relationship are reflections of the collection class. This means that
rather than calling the property itself, data returned can still be accessed using the collection()
method. For example:
User::posts()->read()->posts;
User::posts()->read()->collection(); //same as above
collection()
method will enable us to access the correct property
assigned to any relationship.
Posts::user()->read(['firstname'], 2)->posts;
read()
method is a directive that tells a model to process the relationship defined
in a way that the relative data is returned. For example, we can select unambiguos fields easily by
declaring the array list of fields we want to obtain as the first parameter of the read
method just as shown below:
User::posts()->read(['firstname','lastname','post'])->posts;
read()
method will process the data in a way that only the "firstname",
"lastname" and "post" of each posts is returned depending on the type of relationship defined. We can also set
the limit of data obtained by setting the limit as a second parameter.
User::posts()->read(['firstname','lastname','post'], [10])->posts;
read
method works exactly as the query builder crude operator "read()" does. This means
that we can also define the position at which the data is being obtained by defining two limits within the second argument.
where()
, withDefault()
, use()
, byRecent()
and order()
.
These methods can only be applied before the read()
method is called. Once the data is obtained through the
read()
method, then we can apply other methods which are error()
, pull()
, protect()
and shuffle()
.
User::where('id = ?', [1])->read()->User; // only user table where "id" field is 1 will be selected User::posts()->where('posts.id = ?', [1])->read()->posts; // only posts table where "id" field is 1 will be selected
where()
method can be applied on models or relationships. This method enables
us to be able to set certain conditions which our data must meet before it can be successfully obtained. The
where()
method takes the first argument as a raw string that defines a list of fields and their respective values (or binded parameter placeholders).
If the binded parameter sign (i.e "?") is used, then a second parameter that matches the number of expected binded values must be supplied
on the where()
method.
User::where('id=?', [1])->withDefault(['name' => 'Felix Russel'])->read()->User;
User::posts()->use([Postclass => ['id'=>'postid'], User::class => ['id' => 'userId']])->read()->posts;
use
method enables us to customize table names or even fetch a table that was overidden. The code sample is
above is a way by which we can pull hidden data. The use()
method is supplied an an array argument which contains
the model class (or table name) as index while the index's takes an array value of old field name and new custom field name of the
related index which in this case is a table or model class. In this way, we can fetch the hidden data without any problem. The index
(or model classes) supplied must be related tables, else, an error will be returned. The use()
method cannot be applied directly on a model.
It can either be binded on the where()
method or directly on dynamic relationship methods such as of()
, ofUser()
or dynamic relationship methods such as the example given above before the read()
method is called.
1. User::posts()->byRecent()->read()->posts; 2. User::posts()->byRecent('date')->read()->posts; 3. User::posts()->byRecent([Posts::class, 'post'])->read()->posts; 4. User::posts()->byRecent(['posts', 'post'])->read()->posts;
"byRecent"
condition allows us to select recent data. It cannot be used with the order()
method. By default,
a "one-to-one" relationship will use the id
of the parent model to select recent
data while a "one-to-many" will use the id
of the child model to select
recent data if a custom field is not defined. For example, assuming we are working with a "one-to-many" relationship, then:one-to-one
or many-to-many
relationship, data will be sorted based on the model calling the
relationship unless the sort table name (or model) is defined.
1. User::posts()->order('firstname')->read()->posts; 2. User::posts()->order('firstname', 'DESC')->read()->posts; 3. User::posts()->order([Posts::class, 'post'])->read()->posts; 4. User::posts()->order(['posts', 'firstname'])->read()->posts; // same as line 3 above if Posts::tablename() is not modfified
order()
condition allows us to select data by order. It overides the byRecent()
method hence, it cannot be used along with it.
The first argument is the field used to sort the data while the second argument defines the order in which the field is sorted. By default, the second argument
uses the ascending order "ASC" but can be changed to descending order "DESC". vdump(User::posts()->read()->pull(1));
read()
method returns a multidimentional transversible object,
the pull()
method allows us to pull data out of a list of collections
using the defined data access key. For example, the code above will pull out the first data obtained from a multidimentional
data. This method does not require the use of collection()
or dynamic property to access the
returned data. Hence, we can directly pull a data out of the object using the index key of that object as
shown above. The data returned is usually a Collectibles object.
User::where('id = ?', [1])->read()->protect(['password']);
protect()
method allows us to hide a specific data value.
Once an access key is protected, the value becomes ***Protected***.
The code above is an example local scope protection.
Data collections can also be protected at a global level. This means
that all models will remember to hide the value of some specific fields.
This can be done by calling the Collection::protect()
method.
Once the protection is set up, all models will ensure to protect
the data keys set on the global scope just as below:
User::where('id = ?', [1])->read()->collection(); //password value not protected Collection::protect(['password']); //password protected globally User::where('id = ?', [1])->read()->collection(); //password value protected from global protection
User::where('id = ?', [1])->read()->shuffle();
shuffle()
method is used to shuffle the data returned within the
collection object. It takes no argument.
Comments::of('posts')->read()->Comments
object(spoova\core\classes\Collection)[24] private 'data' => array (size=2) 0 => object(stdClass)[25] public 'id' => int 1 public 'user_id' => int 2 public 'post' => string 'This is a post' (length=14) public 'added_on' => string '2023-01-04 18:42:00' (length=19) public 'post_id' => int 2 public 'comment' => string 'This is a comment to a post' (length=27) 1 => object(stdClass)[26] public 'id' => int 2 public 'user_id' => int 2 public 'post' => string 'This is a post' (length=14) public 'added_on' => string '2023-01-12 21:22:32' (length=19) public 'post_id' => int 2 public 'comment' => string 'This is a second comment to post' (length=32) private 'datakeys' => array (size=2) 0 => int 0 1 => int 1 private 'iterator' => int 0 private 'callables' => array (size=0) empty public 'error' => string '' (length=0)
get()
method just as below:
Comments::of('posts')->read()->Comments->get(0);
object(stdClass)[25]
public 'id' => int 1
public 'user_id' => int 2
public 'post' => string 'This is a post' (length=14)
public 'added_on' => string '2023-01-04 18:42:00' (length=19)
public 'post_id' => int 2
public 'comment' => string 'This is a comment to a post' (length=27)
Comments::of('posts')->read()->Comments->get(0)->comment; // This is a comment to a post
get()
method rather than calling it
as a property just as below
Comments::of('posts')->read()->Comments->get(0, 'comment'); // This is a comment to a post
Comments::of('posts')->read()->Comments->get(0, []); // Get all data in index "0" as an array
Comments::of('posts')->read()->Comments->get(0, ['comment','user_id','lol']);
array (size=3) 'id' => int 1 'comment' => string 'This is a comment to a post' (length=27) 'lol' => boolean false
lol
does not exist as a property, hence, it is set as false rather than triggering an error.
get()
,
this can trigger error. The word "optimize" here means improving the efficiency of obtaining
data from collection object even if an error occurs in sql query and the collection class cannot return
any valid data. The class ModelOptimizer
is used to optimize model data in a way that even if an error
occurs in a collection object list, the error can still be silenced in a way that doesn't break the application.
Assuming we have a chained structure as below:
var_dump( Roles::user() );
object(spoova\core\classes\Collectibles)[18] public 'user' => object(spoova\core\classes\Collection)[16] private 'data' => array (size=0) empty private 'datakeys' => array (size=0) empty private 'iterator' => int 0 private 'callables' => array (size=0) empty public 'error' => string 'Sql Error: Unknown column 'user.role_id' in 'on clause''
get()
method directly to fetch an index, it may trigger an error.
Instead, we can first optimize the object before finally chaining the get()
method on it just as below:
var_dump( Optimize(Roles::user()->user)->get(0, ['firstname','lastname']) );
get()
method. Since no data index "0"
exists, the optimizer will ensure that the get()
method returns a false value by default. In case we
want to obtain the list of array we supplied (i.e firstname and lastname), we can set the Optimizer mode as false. This
will ensure that if array is supplied as sub indexes, then each of the array supplied will become an index assigned the false
value.
An example is shown below:
var_dump( Optimize(Roles::user()->user, false)->get(0, ['firstname','lastname']) ); //Rather than return false, the above returns: array (size=2) 'firstname' => boolean false 'lastname' => boolean false
error
property but can be obtained
through the error()
method. The "error" method can be placed upon the dynamic collection properties or
directly on the read()
method. A sample format is below:
User::posts()->read()->collection()->error(); //valid User::posts()->read()->posts->error(); //valid User::posts()->read()->error(); //valid
error()
method will return a false
value.