Models and Databases
Models and the Dorguzen ORM
Like any MVC framework, Dorguzen uses models to abstract the interaction with database tables. The parent model DGZ_DB_Adapter which all your models must extend, packs all kinds of methods to query your database and save data to it; all in a secure manner. You are free to use these methods however you wish. Reading from, writing to, and updating database tables could not be easier, thanks to the Dorguzen ORM which ensures that a models map effectively to their associated database tables. The model-again thanks to its parent Dorguzen model, knows all the fields of its associated table, and exposes methods to easily carry out all kinds of operations on that table.
How to create a model
A model is a representation of a database tables. You do not go to a database except via a model. With that said, you MUST give your model the same name as your database table with, except that the table starts with a lowercase. This is how it works out of the box and we recommend you leave it this way, but if for some reason you decide to name your database tables differently, you must go to the getTable() method of the DGZ_DB_Adapter method and change how it assumes the name of the current database table.
Just like when creating controller classes, a model is a class you create inside a file with the same name as the file that it contains. The model file has to be placed inside the 'models' folder, and if the class inside of it is for example Users, then the name of the file in the models folder needs to be named Users.php
A model should extend the Dorguzen base model called DGZ_DB_Adapter.
A model should have the following three members:
- The protected property called $_columns which is an array. What is great about this member is that it is going to contain the name and data types of all the fields of the database table associated with this model. This will be done by the system for you. You just have to declare this property.
- The private property called $_hasParent which is an array. You pass to it
a stringed associative array of all models that are the parent model of this model-where the keys are the names of the parent table and
the values are the parent table column names that this model (table) has foreign keys of. Remember that when we talk of models, we are talking
about the tables that models represent. Here's an example of declaring the $hasParent property:
This is not very crucial but it is recommended practice to specify relationships between models using these fields. If not for anything else, it will help make your code readable for both you and your fellow developers. Leave it blank if this model has no parent table.private $_hasParent = [ 'News' => 'news_id', 'Images' => 'images_id', ]; - The private property called $_hasChild which is an array. You pass to it
a stringed associative array of all models that are the children models of this model. The keys of this associative array are the names of the
children tables and the values are the names of the foreign key columns on those children tables. This is the direct opposite of $_hasParent,
so children models refer to all tables that have a foreign key of the table of this model. Here's an example of declaring the $hasChild property:
This is not too crucial, but it is recommended to specify the relationship between models like this as you create the, as this information will be used by Dorguzen behind the scenes to enforce foreign key constraints on your tables during database operations. For example; when you delete a record of a table (model) using the method deleteWhere(), it uses the $_hasChild property to detect and delete any foreign key records on child tables dependent on the deleted record. If you did not define model relationships like this, then you will have to handle foreign key constraints on your own. Leave these $_hasChild property blank if this model has no child table.private $_hasChild = [ 'news2img' => 'news_id', ]; - It must have a constructor that does two things:
- calls the parent (DGZ_DB_Adapter) constructor like so: parent::__construct();. This configures all the settings needed by the model do do its work, including establishing the DB access credentials
- calls the parent (DGZ_DB_Adapter)'s loadORM() method like so: $this->loadORM($this);. This fetches the database table associated with the model, retrieves data about all its columns and their data types and stores the details in the $_columns property of the model ready for use by Dorguzen to read and write data to and from the table.
Table Naming Conventions
- We will talk about all the conventions used in Dorguzen under the topic Dorguzen Conventions , but here we will mention the conventions surrounding models and the naming of database tables.
- Every table should have a name field named with the table name, an underscore and 'name', for example the users table should have the following field: users_name. This is not a restriction, because you do not have to do this. But doing so grants the system another way to better control the data in your application. You do not need it, but if it does not hurt to do so, we recommend you create one.
- The columns or fields of the table must start with the name of the table followed by an underscore and then the field name. For example the names of columns on a products table are as follows: products_id, products_name, products_description etc. In the case of a foreign key field, the field name should be the name of the current table followed by an underscore, and then the name of the column name of the parent table being referenced. For example the column names of an orders table using a foreign key of the 'products_id' column of the 'products' table will look like this: orders_id, orders_name, orders_products_id etc.
Model conventions
- You MUST give your model the same name as your database table, except that the table starts with a lowercase while the model class name starts with an uppercase. Without this your models will not work.
- If you have a table to store the users of your application, then you need to name the table 'users' and password field (if there is one) should be named users_pass. This is because Dorguzen's DGZ_DB_Adapter looks for the users_pass field in your 'users' table.
- If you do not want to, or cannot name your users' table users, or name the password field 'users_pass' then that is okay. You would just have to create your own model instead of the Users.php that comes with Dorguzen already written for you, and you need to write your own methods in it to write to, and read from that users' table. This includes ability to encrypt and decrypt the user passwords in which ever way you decide to do so.
Dorguzen Conventions
- Development with Dorguzen is rapid as a result of its capitalizing on the principle of 'convention over configuration'. Dorguzen therefore depends heavily on conventions, and this meant to make your life as a developer easy. Following these conventions is the best way to make dorguzen do the heavy-lifting for you. The following is an attempt at listing all the conventions that are used in Dorguzen.
- Have a model named Users.php and a table named users
- A model represents and has the same name as a database table, except that the table starts in lowercase while the model starts in uppercase.
- For every table there must be one model
- Table columns are named starting with the table name followed by an underscore and the field name
- Table columns should have a field name that starts with the table name, an underscore and the text 'name'
- Foreign key fields are named by the current table name, then an underscore,followed by the field name exactly as it is in the parent table. For example, the 'products_id' field of the 'products' table as a foreign key in the orders table looks like this: orders_products_id
- View file classes are spelled beginning with a lowercase letter to distinguish them from controllers and models. However this is just a recommendation and you can do as you please with no effect. Whatever you name your classes however, to be on the safe side, no two classes in your application should have the same name; not even the same name with different case letters.
Selecting Records
Select all
- Call the method getAll() on a model like so:
This will get all users in your 'users' table. You can also write and run your own raw queries. You would have to call the query() method which is there for that purpose. Here is how:$users = new Users; $users->getAll();$users = new Users; $sql = "SELECT * FROM users"; $users->query($sql);
Select...WHERE
- Call the grabWhere() or the selectWhere() method
on a model. These two methods both do the exact same thing and accept the same arguments. They take two array arguments;
the first one $columns contains the fields to select, and the second one $criteria contains the conditions that the
values in the selected columns must meet.
This will get the two columns users_name and users_type in your 'users' table where users_id equals 5.$userId = 5; $users = new Users; $columns = ['users_name', 'users_type']; $where = ['users_id', $userId]; $users->selectWhere($columns, $where);
Inserting Records
insert()
- Call the insert() method
on a model. It accepts an associative array where the keys are the columns to write to, while the values are the values
to insert. Here is an example:
This is how straight forward it is. It inserts the $data values into the columns with names matching the $data keys on a blog table.$blog = new Blog(); $data = [ 'blog_title' => $_POST['title'], 'blog_comment' => $_POST['comment'], 'blog_user_id' => $_POST['user_id'], ]; $blog->insert($data);
save()
- This an even more expressive write query that depicts the power of the ORM feature. It allows you to visually type
out a model's table fields as if it was an objec, then you simply assign values to the fields like you would to a
variable, and call the save() method on it when you are done.
Seeing is believing, so here is an example:
What's more, it returns the last inserted id. So, $subscribed will contain the ID of the lastly inserted record in the subscribers table. This is very handy, and can be used after an insertion, to perhaps, do another insertion of that ID as a reference into another table.$subs = new Subscribers(); $subs->subscribers_name = $name; $subs->subscribers_email = $email; $subs->subscribers_date_created = date("Y-m-d H:i:s"); $subscribed = $subs->save();
Updating Records
update()
- Call the update() method
on a model. It accepts two arrays; $data, the array with what to insert into the database table, and another array $where
containing field-value pairs representing the column and value that the table columns being written to must match.
If that sounds confusing, don't worry, i would be confused as well if i didn't see an example. So here is an example:
$updated will be true if the update was successful, or false if it failed.$blog = new Blog(); $data = [ 'blog_title' => $_POST['title'], 'blog_article' => $_POST['article'], ]; $where = ['blog_id' => $_POST['blog_id']]; $updated = $blog->update($data, $where);
updateObject()
- This is very similar to the save() method in its expressiveness. The only difference is that while save() does an insert,
updateObject() does an update. Like save(), it allows you visually type
out the model table fields as if it was an object, then assign values to the fields like you do to variables and call the
updateObject() at the end to save the data. However, before you call updateObject()
you should pass it one argument; an associative array that is going to be used to build the 'WHERE' clause of the update
query. This array contains the criteria or condition upon which to base the update. The array keys will be column names of
the target table, while the values will be the values that must be matched on the columns of a record for it to be eligible for
updating. Here is an example:
$updated will be true if the update was successful, or false if it failed.$newsletter = new Newsletter(); $newsletter->newsletter_name = $nl_name; $newsletter->newsletter_subject = $nl_subject; $newsletter->newsletter_heading = $nl_heading; $newsletter->newsletter_message = $nl_message; //build the where clause $where = ['newsletter_id' => $nl_id]; $updated = $newsletter->updateObject($where);
Deleting Records
deleteWhere()
- Call the deleteWhere() method
on a model. It accepts an array; $where, the array containing field-value pairs representing the columns and values
that the columns to be deleted must match.
Here is an example:
$deleted will be true if the deletion was successful, or false if not.$user = new Users(); $userId = $_GET['userId']; $whereClause = ['users_id' => $userId]; $deleted = $user->deleteWhere($whereClause);
SQL Joins
Joins
- Beyond the awesome methods to write to and retrieve data from tables, Dorguzen does not offer any other methods for doing more complex queries like join operations between two or more tables. This would mean trying to make you learn a new syntax that other solutions like Doctrine already provide. Besides, teaching you a new ORM syntax may defeat the idea of giving you a rapid development tool to get up and running with quickly. Having said that, we are working on adding new methods to the model for performing the most common operations and we will go for the most expressive syntax possible.
-
Dorguzen offers you the database connection that you can connect to from any model just by calling connect() like
so: $this->connect();. You have the ability to access your mysql database using
one of two drivers; mysqli or PDO which you
set as the connectionType in the Settings class, and you are good to go.
You can then write your own more complex and secure mysqli/PDO queries. For quick queries that you don't think
need any sanitizing via prepared statements, you can just use the provided query()
method. Here is how you would use the query() method:
$sql = "SELECT * FROM product_categories pc LEFT JOIN products p ON p.products_cat_id = pc.product_categories_id"; return $this->query($sql);
Forms
The DGZ_Form class
The DGZ_Form class is a form wrapper class with methods that build form fields for you. We recommend you use it to create all your forms. There is no reason not to use it because it only has advantages. Here they are:
- It is very flexible in how it allows you create any type of form using any type of attributes you wish to pass it, with no restrictions. These attributes can be later styled according to your preferred look and feel for the form
- You can combine its form fields with other fields created without it in the same form.
- It is secure. It comes with a built-in method to generate a CSRF token to be sent to your server with the form when it is submitted. It also has another method to validate the csrf token on your server once the form has been submitted.
- If a submitted form has to be redisplayed for some reason, like, when errors are found in the values entered by the user, if the developer posted back the values using the postBack() method available to all controllers, these previously-entered values will automatically be redisplayed in the fields. The developer will not have to write code to redisplay these on the re-displayed form.
Here is how you will create a form with DGZ_Form:
<?php
$form = new DGZ_Form();
?>
<?=$form::open('subscribe', $this->controller->settings->getFileRootPath().'controller/handleform', 'post')?>
<?=$form::label('name', 'Name')?>
<?=$form::input('name', 'text', ['class' => 'form-control'])?>
<?=$form::label('email', 'Email')?>
<?=$form::input('email', 'email', ['class' => 'form-control'])?>
<?=$form::label('follow_up_contact', 'Follow up contact')?>
<?=$form::checkbox('subscribe', 'yes', false)?>
<?=$form::checkbox('request_call_back', 'yes', false)?>
<?=$form::checkbox('wish_to_learn_more', 'yes', false)?>
<?=$form::label('preference', 'Preference')?>
<?=$form::radio('preference', 'indoor')?>
<?=$form::radio('preference', 'outdoor')?>
<?=$form::label('comment', 'Your Comment')?>
<?=$form::input('comment', 'textarea', ['class' => 'form-control'])?>
<?=$form::label('favDrink', 'Your Favourite drink-fanta pre-selected (multiple select)')?>
<?=$form::select('favDrink', ['coke' => 'coke', 'fanta' => 'fanta', 'beer' => 'beer'], 'fanta', true,['class' => 'form-control'])?>
<?=$form::label('favSports', 'Your Favourite sport-boxing pre-selected (single select)')?>
<?=$form::select('favSports', ['football' => 'football', 'tennis' => 'tennis', 'boxing' => 'boxing'], 'boxing', false, ['class' => 'form-control'])?>
<?=$form::getCsrfToken('home.php')?>
<?=$form::submit('submit', 'Submit', ['class' => 'btn btn-primary btn-sm'])?>
<?=$form::close()?>
The way it works is pretty self-explanatory. You would instantiate DGZ_Form which is a static class, then start building the form from the top down by calling on:
open() which takes the name of the form (for the name attribute)- the form handler (for the action attribute)
- optional string for the method that will be used in the submission (post/get)
- finally attributes you want to be passed to the opening form tag in the form of an associative array like so: [ 'class' => 'form-control', 'enctype' => 'muiltipart/form-data', ]
- a string that will match the ID of the target form field.
- The text that will be displayed for the user to see
- the name of the input field, and this will be the ID of the field too
- The field type (text/email/number/textarea etc)
- An array of attributes that will be built into the input tag
- the field name
- the value as a string
- a number, for multiple check boxes to be created in one go, or false
- an optional array of attributes to pass to the checkbox form element
- the field name
- the value as a string
- an optional array of attributes to pass to the checkbox form element
- the field name
- an array of the data to display in the option elements in an associative format. This array will be looped through and the data injected into the option fields; where the keys will be the values and the values will represent the text displayed for the user to see as the option hint.
- a string to represent the value of the group of options that you want to be preselected.
- an optional array of attributes to pass to the checkbox form element
To secure your forms against cross-site request forgeries, make sure to call the getCsrfToken() method passing it an optional unique string to be used to create the encrypted characters for the form validation, which should help add another layer of difficulty for it to be guessed. This will place a hidden input field in your form with a name of csrf, and the encrypted key as its value.
Finally, call the submit() method to create the submit button of your choice. It takes:
- the input type, which could be button or submit.
- The value to display on the button as a 'call to action' for the user to see.
- As usual, an array of attributes to be built into the button.
To validate the csrf in the submitted form, just call the validateToken() method on the DGZ_Form class, passing it the csrf value coming from the submitted form. Here is an example form validation code:
if ($_POST)
{
if (DGZ_Form::validateToken($_POST['csrf']))
{
$this->addSuccess('Submission was successful', 'Great!');
$this->redirect('home');
}
else
{
$this->addErrors('Validation failed', 'Error')
$this->postBack($_POST);
$this->redirect('controller', 'showForm');
}
}