This exercise assumes that you have managed to configure your Divblox project and that you understand the basic concepts. If you are not sure that you do understand the basics, you can look at the common examples.
In this exercise we will go into a bit more detail about the main Divblox concepts that are used in every project. These concepts include:
- Data modeling
- The Divblox ORM (Object Relational Mapping)
- Interaction between a component's front-end and back-end
- Component and object security
- Exposing an API
In this training exercise, we will be creating a basic ticketing system that will allow users to create and manage "tickets". To allow users to interact with our tickets, we will generate CRUD (Create, Read, Update, Delete) components.
Additionally, we will create the following components:
- A page where full CRUD of tickets and categories is done
- A page where we reuse the CREATE component for a ticket to allow the user to create tickets in a simple way
We will also be building some custom functionality to demonstrate how to communicate between the front-end and back-end of a Divblox application.
Finally, we will also learn how to secure our components and data model entities, as well as how to expose our functionality via the Divblox API layer.
Step 1 - Data Model
We will be creating a data model with the following entities and attributes:
- Ticket: TicketName, TicketDescription, TicketDueDate, TicketUniqueId, TicketStatus
- Category: CategoryLabel
This can be represented as follows:
If you need a refresh on Divblox data modelling, click here. Below is a walk-through of how to add the necessary entities using Divblox's Data Modeller.
Step 2 - CRUD Components
Now that our data model is created and synchronized with our database,
let's generate some CRUD components (using the component builder) for
Category. Below is a walk-through of how to create full CRUD functionality for the
And now we will create the CRUD functionality for the
Ticketentity, which although more complex, is just as easy with Divblox.
When checking the
Validatecheckbox, Divblox automatically notifies the user that input is required. Further validations can be added at a later stage.
Notice that in both examples we did not tick the
Constrain To checkbox. If you constrain by a certain attribute, you are filtering to see only results that satisfy that criteria. An example would be to constrain Tickets by the current user account. This will display only tickets created by the current user. These constraints can only be done with entities that have a singular relationship. Singular relationships mean that an entity instance is linked to one, and only one, instance of another entity. E.g. Each ticket can only be linked to one account at any given time.
This logic also applies when using 'create' and 'update' functionality and using the
Constrain By checkbox. An example here would be to automatically link a ticket to the current user upon creation.
You may want to change the display of certain attributes, in all of the components they feature. In our example, let's say we have a set predetermined list of ticket statuses the user should be able to chose from. This can be manually done in the
entity_definitions.json files. A walk-through of this is shown in the below video.
Step 3 - Page Components
In order for us to be able to use our newly generated CRUD components, or any other component for that matter, we need to put them inside pages. Pages are also just components, but they can be navigated to by the user in the browser, while individual components can not.
!>A component is considered a page component when it is located in the "pages" grouping (the folder /project/components/pages/[component_name])
The pages we will build for this exercise are:
- An admin page where our full CRUD components can live
- A "New Ticket" page where users can create new tickets
To do this we will use a pre-made page template with a side navbar. As you will see, the navigation bar is pre-populated with links we will later override or delete to suite our needs.
Now we can create the 'Tickets' page where users can create tickets. Note that we are not creating any new functionality, just reusing the 'create' component previously generated and placing it on its own page.
Step 4 - Navigation bar
Ok, we now have components that allow us to create our data, as well as pages to view them on. We will now update the side navigation bar to function as we want it to. Notice how, in this video, we edit the component code in our IDE (any IDE/text editor of your choice). The preferred way is to use an IDE, but for quick fixes like changing the HTML layout of our page we can use Divblox's built-in code editor. The process followed here is as follows:
Divblox incorporates an easy to use seperation between the actual navigation bars and the menu items in them. The idea of this is to allow for reusabililty of the same menus and styiling throughout a project. You can access the list of menus available in your project from the
Navigation bar in the setup page.
You will see the following:
There are a few default menus, and we will create a basic-training-exercise-menu with the necessary items.
Here we add the HTML that we want to be displayed as our item, in our case we want an icon and page name. We give our two pages the following:
We also set the action we want to occur when clicking on the menu item to load up the corresponding page. The navigation bar component HTML looks like this:
Step 5 - Global Functions
For the purposes of this exercise, we want to assign a unique ID to every ticket. This will allow us later on to retrieve information about our ticket via an API. To generate this unique ID, we will make use of a global function call.
Global functions are defined for functionality that will be used multiple times, reducing code duplication
We will create the unique ID in the backend, as we need to verify whether or not it is indeed unique by checking our database.
Step 5.1 - Adding button
Add the button in our
ticket_crud_create component that will generate a unique ID and populate the input box. We can do this through the Divblox Component Builder or in the source code.
Below is a video running through step 1:
Step 5.2 - Create global function
Create the global php function that will generate the unique ID in
Step 5.3 - Call global function
Call the global function from
component.php, sending information to the front end.
Below is a video running through step 2 and 3:
Here is the code added into the class ProjectFunctions, in
And the code added into the
ticket_crud_create component.php file:
Below is a video of step 4:
The code added into the
initCustomFunctions function was:
Step 6 - Security
It is important to understand how Divblox user roles are used to control access to the application. Divblox has two forms of access:
- Component access allows the user to view the components
- Data Model access gives the user permissions to perform CRUD operations on specific entities defined in the data model.
By default, there are two user roles.
- Administrator - Has access to all components and full CRUD functionality.
- User - This is the user role allocated to anyone who registers on your app. The default access is only to your profile and account.
Any user that is not authenticated is treated as "Anonymous" - No access, gets redirected to the anonymous landing page.
Additional user roles can be defined in the data modeller.
It is important to define a user role hierarchy in the abstract class 'UserRoleHierarchy' (project/assets/php/user_role_hierarchy.class.php), so as to prevent duplicate writing of components and data model access for different user types. This allows for user roles to inherit access from lower user roles, so you only need to specify what access the user role has above and beyond previously defined access.
The Component default settings are as follows:
And the Data Model settings seen below. It is also important to note that by default users are able to
read data, even if not explicitly stated in the
For our exercise we created 2 pages (The 'admin' and 'new ticket' pages). Let's assume that we only want administrators to access the admin page.
You can access the register page by navigating to
[your_project_root]/?view=register. New users are registered with the user role "User" by default.
!> It is also good practice to test user role access in incognito/private mode, as you are typically logged in as a Divblox admin (dxAdmin) most of the time in your application and this may cause confusion.
As you can see, our new user is unable to view any of the pages we built. This is because he does not have component access to the components on those pages. We will change that in the
In the below video we will firstly give our user full access to any
Category components. This will allow us to see how the Data Model access works (we will observe this on our admin page). Once the Data Model access is configured, we will then give our user access only to the
create components of both
Category, allowing the user to view the New Ticket page, but not the admin page.
It is worth noting that this is a basic example to demonstrate how Divblox handles user access. As you may have seen above, there is no need to change the Data Model access of our user to be able to
delete as he will never be able to get to the admin page to do this.
Step 7 - Exposing an API
Now that we have all the groundwork completed, let's provide the world with an API endpoint that will allow us to do some custom functionality on our tickets. To do this, we will copy the provided
api_example endpoint and modify it for our use case. The API functionality we want to achieve is as follows:
- Allow a user to provide us with an array of unique ticket IDs as input
- Select only the ticket descriptions from the tickets
- Merge all of the ticket descriptions into the first ticket (initial unique ID)
- Delete the remaining tickets
- Return the new merged ticket as output
We will be using a program called 'PostMan' to test our API functionality. It comes pre-installed on the Divblox VM image.
Divblox automatically handles the routing for your API endpoint. API endpoints are available at [your_project_root]/api/[endpoint_name]
Below we will briefly explore the
api_example functionality, how to navigate the URL and what the expected output looks like.
To proceed, we will create a file
project/api and mimic the basic structure of an API endpoint like in
api_example.php. Firstly, we add an API operation with function name
mergeTickets(); function we code the following logic:
- check if the input is valid JSON
- check if the master unique ID exists
- check if the master unique ID is valid
- check if there are more than two IDs
- if there is more than two IDs:
- loop through the valid IDs
- perform a merging of the ticket descriptions
- delete each ticket after its description is merged
- save the results into the database
- present output to front end
The code added into our 'basic_training_exercise.php' endpoint (
/project/api/basic_training_exercise.php) is the following:
Once we have defined our endpoint, we can test to see if everything works. Note that this specific API operation updates and deletes data in our database, we need to update the
Data Model permissions so that 'any' users can 'update' and 'delete' (Recall that default permissions are only to 'create' and 'read'). Once this is done, our API operation should be set up and permissions for operations granted. We use Postman this time, as it makes it easier to input parameters and has a great user interface.
Step 8 - Further Examples
In the following examples, you will gain a deeper understanding of the Divblox PHP query language that is used to communicate with the database. We will also be building on your existing knowledge on how to communicate between the frontend and backend of your Divblox application.
In this section we will focus on the dxQuery language explained here.
You will build the following:
- A custom component that will serve as a container for some example functions. This component will manage the communication between frontend and backend
- Function 1: An example of how to generate dummy data
- Function 2: Basic dxQuery example using
- Function 3: A slightly more advanced dxQuery example using query conditions like
- Function 4: Even more advanced example that makes use of Divblox's wrappers for PHP's DateTime class
- Function 5: Example of building up a query result in a loop
- Function 6: More advanced example of building a query result in a loop
- Function 7: Optimization of function 6
- Lastly, we will optimize our code for extremely large datasets
Custom Component Setup
Create a custom component with two equally sized columns. In the left column we will house three elements, namely a drop down list of functions to select, an additional input box and a button to execute the chosen functionality. In the right column we will just create and empty div with ID = "ResultWrapper" so we can instruct
dxRequestInternal() where to display any output. Below is a video walk through of the process:
It is important to create a div in the right column so you can tell
dxInternalRequest() where to return any output.
We will again be using the
dxRequestInternal function for backend/frontend communication.
The code that replaces the default 3 second loading function for our button is:
We should now be set! Our custom component is ready, our input selection is set up and we have a div to display our output. What remains now is to define our 7 functions.
Generate data: This function will generate a bunch of categories & accounts, then it will generate a bunch of tickets, linked to a random category with a random status, account & due date Function 1
Here we use the built in PHP function
rand(), as well as a bit of Divblox functionality, including the
dxDateTime() class as well as the
generateTimeBasedString() functions. If you are not comfortable with these, you can refer to the class/function definitions.
Every time we run this function, we are generating 500 new tickets, 8 new categories and 50 new accounts. Note that the way we executed our loop, the tickets will not be generated with uniform distribution of categories or accounts, as the oldest generated accounts and categories will be sampled more often. But since we are just doing this to get some data, we are not worried about that.
Return all tickets in the category defined by the user in the additional input box. The default category should be 'Personal' if no input provided. Function 2
Return all tickets where the Account's first name is specified in the additional input box (Default value is 'John') and the ticket status is "In Progress" Function 3
Return all tickets that have a status of "Completed" for the current month Function 4
This function is slightly trickier as we are now trying to sort by date. There are many ways to try do this, but we will use built-in Divblox functions to make this easier. The 'trick' here would be to be familiar and comfortable with what
dxDateTime can offer you. All we do below is:
- Set the StartDate to today's year and month but manually change the day to 1 and time to
- Set the EndDate to StartDate, + 1 month, - 1 second (which is then just the last day of the current month)
Return a list of "Account" full names with a count of tickets that they each currently have "In Progress" Function 5
In this function we first request an array of all Account objects. We then loop through each of those objects, and fill a key-value pair array as [FullName => NrTicketsInProgress] which is our expected result.
Return a list of "Account" email addresses. For each account, show an array of categories. For each "Category" in the array, show the total count of all tickets for that category as well as the count for the specific account Function 6
You will end up with a nested array of objects, structured something like this:
For each account (defined by it's email address) we want to see an array of all the categories used in their tickets. For each of those account-used categories, we want to see both the category's total tickets, as well as the account's category total ticket count.
Optimize the query in Function 6 using the Select Clause and by reducing the number of iterations in the loop. You can use your browser's dev tools to monitor the time taken to execute the query. Function 7
- Select only the necessary attribute from Account entity, i.e. EmailAddress. This means that we are only returning a single column from the database. NOTE: Even though you only select one column, the Account primary key is still included for indexed searching.
- We only calculate the GrandTotal once for every Category, instead of re-checking and re-saving it every time we loop over an account, which has a ticket with said category.
The new code can be seen below:
Using the browser's network monitoring tool, the difference with just these minor changes can already be seen. Below is a screenshot of the times taken with the two methods. 5 observations were taken for a better average. There are much better and more in-depth ways to test your code efficiency, but for this example a rough visual difference is all we need. The time taken is dependent on the hardware of your machine, as well as how big your database is. So if your times are different, don't worry, as long as you can see a visible decrease in time taken.
In this last step, we will have to change our data model slightly. Let us include the
TicketCount attribute into the
Category entity, which will represent the total number of tickets in the category in question, and will be updated as tickets are saved and deleted. Using Divblox's data modeller, this process is simple and all the user needs to do is add the attribute and sync to the database, which updates the database and regenerates all the classes and necessary functionality code.
This optimization is intended for when our dataset becomes extremely large, and the cost of making even an optimized query in a loop becomes too high. In this optimization we are focusing on preparing our data for reporting purposes rather than aggregating after the fact.
We will need to write the functionality that calculates the
TicketCount into the
Delete() functions of the Ticket ORM class, meaning that any time a ticket is saved or deleted, the
TicketCount will be updated. We can do this by overriding their default behaviour defined in
divblox/config/database/data_model_orm/Ticket.class.php). We do this in accordance with the Divblox code-generation philosophy: never touch auto-generated files, but rather add functionality in classes that extend base Divblox functionality to prevent loss of work.
The code added to the class
Ticket which extends the class
This is great. Now every time we create or delete a ticket, our counter will automatically update. But what do we do about the tickets that already exist in our database? We will have to create a throwaway script to run once to account for all the already existing tickets in our database.
This script can be saved anywhere and run by going to
localhost/[your_project]/[path_to_script]/throwaway.php in your browser. You can confirm that it performed what it was supposed to by checking the category table in phpMyAdmin.
Now, the final step is to go back to our code and just reference the TicketCount attribute instead of calculating the TicketCount total every time. This shouldn't really affect our speed significantly until we have a very large data set, but was included for good practice.
In this exercise you learned about all the basic elements of a Divblox project. If you understand step 1 - 8 completely, you should have a fundamental understanding of the basics of any Divblox application.
If you would like to receive further hands-on training from the Divblox team, please reach out to us at firstname.lastname@example.org and we will arrange a consultation.