I have to say that if you are familiar with CakePHP 2.x ORM, working with the new Model layer is either going to bring you a great deal of pain or pleasure… (or both in that order).
Indeed, there will be a bit of a learning curve to get used to the new and improved way of doing things, so although I am a bit sorry to say it, but if you’ve spent the last two years learning the intricacies of CakePHP 2.x ORM you are in for a bit of a surprise… although don’t despair, your knowledge will not go wasted.

In CakePHP 3 the ORM has been rebuilt from the ground-up and personally I find it a lot more flexible, better structured and “officially” speaking the separation of concerns principle is truly respected.

Back in the day we used to have a CakePHP Model and that was pretty much all there is to it (the Model layer). You could call a find() method (or some variation thereof) to retrieve your data and you’d get an array of data back.
For most purposes it worked very well, but as soon as you needed to do something more complicated (sub-queries anyone?)… things got rather nasty and in turn you could see plenty of poorly written CakePHP apps.

But let’s not dwell on the past, as today we have:

Table object, which provides a direct representation of your table.

Entity object, basically represents a record row (i.e. if you have a bunch of Products in a table named “products”, then a single product or item representation would be handled by the Entity object named “Product”, in this example.)… so to keep in mind: Table — collection of records, Entity — single row/record.

Query object, allows for construction and manipulation of queries. A little more on that below.

Although it looks like a lot of code, most of our application is contained right here. Let’s try to break it down.

First, I am attaching the Timestamp behavior to handle our “created” an “updated” fields. (Note I diverged slightly from the book where the default field is “modified”, and not “updated”… however the problem is readily fixed by providing a few extra settings as you see on lines 20 -22).

Next, comes our validationDefault() method. Although it looks a bit different from the way things were in the previous versions of CakePHP, the code within it should be pretty straight forward. I am checking to make sure that the “todo” field is not empty. During the “update” of the record, however, there is no need to attempt to validate this field.

The next method findRecent() is a custom finder method, which is quite powerful in the new ORM.
Notice, that this method does not return a result set. Traditionally you’d want this method to return a query object. What happens here is that the query object gets created and the actual SQL is ready to be executed at this point. However to trigger the execution of the SQL to actually “extract” your results requires another step (more about this when I talk about controllers).

There is a couple of other interesting things here…
I am using both formatResults() method and map() methods (thanks, Jose, for the hint) to modify certain things in my result-set. To understand the code a little better what we do here is modify each row value to our liking. For example I am protecting/sanitizing the “todo” field by using PHP’s htmlspcialchars() method. Additionally I am modifying the timestamp to be human-readable rather than Database date-time format, which looks rather ugly.

That’s all there is to our Table. This single method will allow us to get both finished and incomplete to-do’s by toggling the ‘status’ option.

Next, I must talk about unit testing. Actually as a somewhat decent developer I should start every application the TDD (test driven development) way. What that means I write my tests before I ever think about writing the production code.

For the sake of this example, please forgive me for not doing so from the get-go, but here comes our first test. After all, if our table has been setup the right way then the testing should be a no-brainer.

Although this test is lengthy in code, it is rather simple in what it actually accomplishes.

First we try testSaving()… this ensures that both the validation and the actual storage of the data to the database, works as expected.
(Based on the assertions you can see that we expect things to error-out with an empty “to-do” and save correctly, when a “to-do” has some value, on lines 42 and 51 respectively).

Additionally I test that an “evil” script cannot be inserted into our page by ensuring that the htmlspecialchars() method works its magic on a “to-do”. Take a look at line 59 in the table and the above method testSaveEvilScript().

Lastly, I am testing testFindTimeAgoInWords(), which ensures that a human-readable timestamp is returned, rather than something that looks like a posting on a personal ad for robots.

Of course, our testing would not be possible without a fixture (to better phrase it, having some fixture/seed data to work with, makes testing a lot easier):

public $fixtures = ['app.todos'];

I will provide you the sample fixture below.

For now suffice it to say, that I am done with the model layer and its testing. Although we have not looked at the entity object in detail, it is simply not required for our application… and CakePHP is smart enough to instantiate entity objects for you and hydrate them as necessary. More on that, in the CakePHP cookbook.

Finally, I will talk about the controller (and testing of it, of course, in the following post).

… and as promised here’s a sample fixture, which I am using for my tests:

Like this:

LikeLoading...

Related

Mark

That is a very nice beginner’s blog post regarding CakePHP3 first steps..

A few remarks, though:

The h() etc IMO should not be in the model (or controller) layer. It is something solely for representation and view output security, and as such you should use h() only in the templates – on demand so to speak.

Same goes for any output related manipulation, such as ->timeAgoInWords() etc.

All can be done quite easily where actually really needed/useful.

This also allows you to keep the data more flexible in its usage, as you could also work with it inside the model or controller without having to worry about how it might have been modified.

thanks Mark. please note, that the controller only responds with json… therefore it is best to adjust your data prior to dispatching it back (i.e. I don’t want to modify or add another view file just to “touch-up” data after it has been serialized by cake).
In most other cases I would agree with you… also it was kind of meant to show off the ability of the new ORM vs afterFind() as before, for example.

generally speaking I am very much against tampering with the data during the save() operations, but as far as massaging your data on the output that really depends on the implementation.

I’m a fan of sanitizing data on input – and storing it both sanitized and unsanitized in different fields – so that you can inspect both good and bad data.

What I’ve done is have entities override their toArray() such that the toArray() is the data that can be used publicly in APIs or otherwise – which helps when you are sending them to JsonView. You can also do the same for jsonSerialize.

Consulting

During the last twenty years I have helped many startups build amazing technology stacks. Once in a while I consult on a few high-profile projects for Fortune 50 companies.
Let's chat about your next project or
check out our consulting services.