While working with data we will find ourselves in need to map Classes to Database tables and vice versa. We map it, because we want to use higher level objects, not a simple scalar types like Database does.
Mapping (transformation), fetching and storing is not related to our business logic, that’s why we mostly hide it behind an Interface, likely DAO or Repository Pattern.
We move implementation of our Database related Interfaces into infrastructure or persistence layer, to explicitly separate it from the business logic. By that, we want to show that this is not what our business is about. Yet, even so we have hidden it in non-domain related layer, we are still in need to write and maintain this code. This means we still need to invest time and focus into things that we are trying to get rid off.
To have full focus on the business, we need to deal with low level code with as less effort as it’s possible. This comes from the mindset where we treat whole application as business oriented, not only the specific layer/module. And to make whole application oriented around the business, we’ve to use higher level abstractions that takes away need for implementing and maintaining low level code.
PHP way with Business Interfaces
Ecotone provides Business Interface, which aims for reducing low level and boilerplate code to minimum, so the focus can naturally shift to the more important parts of the system.
In my previous article, I was describing how Business Interfaces can be used in Message based Systems. Consider Business Interfaces as tool to help us define intention using an Interfaces, and Ecotone will take care of how and provide the implementation.
For working with databases, Ecotone provides special type of those Business Interfaces which take away the need to write transformation logic, parameter binding and SQL execution. This way we are hiding low level code behind abstraction, focusing on high level business functionality.
Modifying Database Data
To define Method that will insert/update/delete, we will be using DbalWrite Attribute:
We’ve have created an PersonService interface, which contains of register method. By marking it with DbalWrite attribute, we are telling Ecotone to execute given SQL whenever this method is invoked. The implementation of this Interface will be delivered by Ecotone and registered in your Dependency Container.
As we can see, we’ve bound two parameters name and surname, and we can find parameters with same name in method declaration.
Method’s parameters will be automatically bound to SQL ones.
Interfaces that we define are part of our business level code, they don’t come from the framework. To tell Ecotone what to do we use meta programming - based on Attributes. This way we get full ownership of the code, and full flexibility in defining it the way it’s needed in given context.
Return number of modified Records
In case of Update or Deletes we may want to know how many records were modified. For this we may add Integer return type for declared method.
As you can see we’ve used DateTimeImmutable for our parameter. Ecotone will use inbuilt conversion, to convert Date Time into to string before SQL will be executed.
Our Domain Model may use higher level classes than scalar types understood by the Database. And in most situations we will want our Interfaces to follow Business types instead of Database ones. Therefore we can use Conversion mechanism.
Inbuilt Class Conversion
We already saw default Date Time Conversion in play, yet Ecotone can provide conversion for any Class as long as it provide “__toString” method.
And now we can use it as part of our Interfaces without worrying about Conversion:
PersonId will be automatically converted to string, as it contains of __toString method.
Customized Parameter Conversion
We can write our own Converter to customize how we can want to Convert given Classes. Suppose we have class DayOfWeek which on PHP level is represented as a enum string, yet in database we want to store is as an integer.
Then we can set up Converter class for it:
Converter is a class that is registered in our Dependency Container. Ecotone will find all converters thanks to the attribute marker, and call it, when the conversion is needed. In our case it will be called when conversion from DayOfWeek to integer is needed.
After defining Converter, we can now use higher level class it in our Interface, being assured that in the Database it will be stored as integer.
Converter will be reused between all your Interfaces, therefore we write it once and we cover all cases.
Using Expression Language
For cases where we would like to customize our Parameter for given scenario we may use Expression Language. This way we can customize behaviour for specific action.
Suppose we have Person Name class and we would like to convert it to lower case before saving.
Then for storing PersonName as lower case, we can call this method before saving using Expression Language:
By providing DbalParameter Attribute we can define expression to be evaluated before given parameter is stored.
payload is special variable within the expression that refers to given paramter, in this scenario PersonName.
Non Argument, Database Parameters
We may have cases, where there is no need to pass parameter, as it can be evaluated dynamically. For this we can use DbalParameter as part of method’s attributes.
In this situation we’ve predefined “now” parameter using Dbal Parameter on the method level. We are using expression language to evaluate parameter value.
reference is a special variable within expression that points to your Dependency Container. This way we can fetch given Service and call method directly on it. In this situation we are fetching Clock Service and calling now() method.
For Method Level Dbal Parameters we can access all the arguments passed to the method by their names. In our case it would be “personId” or “name”.
JSON based Database Parameters
The database columns will not always contain of simple scalar types, it may actually contains of JSON. However in our Domain level code JSONs are mostly represented as more sophisticated classes or an array of Objects, therefore it requires conversion.
For our example, let’s suppose we want to store array of Person Roles in database. At the Domain level code, Person Role is represented as as PersonRole class.
And then our Interface level, we would define array of Roles:
By defining DbalParameter with convertToMediaType we are stating that we would like to convert given parameter to specific Media Type, in our case it will be JSON.
This will be enough to make the conversion from Collection of PersonRoles directly to JSON.
Querying Database Data
So far we’ve focused on storing data and parameter conversion, however we may use Ecotone’s abstraction for querying data too.
Querying multiple records
To fetch records we will be using DbalQuery attribute.
The result of the above will be array of array contains person_id and name.
We can pass Pagination as an object and use for multiple parameters to make the Interface more readable:
And then we can use this Class as part of our interface:
To make use of expression language inside SQL, we define it as follows:
So in our case, if we want to access pagination.limit, it will be
Converting Result Set
When working Domain level code, we will want to work with Classes instead of associative array returned by default by Dbal.
Lucky Ecotone is cable of to convert the result based on return type defined in Interface.
In the above method we state using fetchMode that we want to fetch single row from the result and then this single row will be converted to PersonDTO class.
To tell Ecotone how to do conversion we need to register Converter.
When fetching single row, we may find no result at all. For this cases we can use union return type:
Returning single value
For aggregation functions like SUM(), COUNT(), MIN(), we may want to return them directly instead of specific row. For this Ecotone provides fetch mode to return first column of first row:
Converting multiple records
When fetching multiple rows, we may want to use classes instead of array too. However we need to define what should we return and PHP does not support generics. To solve this missing functionality, Ecotone provides ability to read Docblocks in order to understand what do we want to convert to.
Fetching Large Result Sets
The default mode for fetch associative result, which means all the result will be loaded into memory. However we may use fetch mode to iterate over results, this way only single row will be loaded into memory at time:
Doctrine ORM Support
Suppose Person is our Doctrine ORM Entity, then we can define the Repository Interface like this:
Ecotone will find related Doctrine ORM Entity Manager and persist the class. The same will work for fetching, based on return type Ecotone will find related Entity and fetch it.
Eloquent Model Support
Suppose Person is our Eloquent Model, then we can define the Repository Interface like this:
Ecotone will use inbuilt methods like save to store the class. And for fetching it will use find method. This way we can hide persistence actions behind an interface.
Full Domain focus is a practice of building applications that aim to solve business problems, not technical ones. The aim is oriented on making the whole application business oriented, not a specific layer or module.
By writing only business related code, we create a space for everyone in the team to focus on what matters. Thanks to that, gradually everyone in the team will start to increase his business domain knowledge. The rule is simple, if there is no technical code to read and write, the only thing that we can focus on - is business.
Ecotone’s main tenet is to provide us with higher level abstractions, so we can enable shift to the business parts of system. Database abstractions discussed in this article are just one of the building blocks that Ecotone provides. Therefore I highly encourage you to take deeper look in the Ecotone’s documentation to find out more and experience that in real life project. And if you want to read more about Dbal based Business Methods, you can go to related section.