From the Terminal
I "Rewrote" My ORM Again. And Ended Up Benchmarking Every PHP ORM in the Process.
You may or may not have read my other blog posts about how
How I gamified unit testing my PHP framework and went from 0% unit test coverage to 93% in 30 days
(7 years ago!) or about how
PHP attributes are so awesome I just had to add attribute based field mapping to my ORM
I've been writing ORMs by hand for 20+ years. This isn't even the only one I've worked on. So with ML I've been able to basically get to where I want to be.
In Divergence v3 I basically refactored everything. It's an ActiveRecord in name only at this point. The factory doesn't even have the Getters and Setters. They are registered in the constructor and that lets me keep the factory lean. ActiveRecord just reads the registered Getters and attached them on itself as well. So you don't even need to use the Factory. It happens in the background. But you can if you want to.
Should Models Even Have Getters and Setters to Get Records Of Themselves From the Storage Engine?
For the longest time I've preferred `Model::getByID` but it's kinda a problem for writing unit tests when all your code is set up that way. Let's just say for reasons it's better to have a Factory class. But then I just kept Model::getByID and it bubbles up to an actual factory with a series of stubs.
So you can still do it that way but now you can write tests that do things. Yay wonderful tests.
Other Abstractions
I really wanted to add sqlite and postgres so that's why that happened.
I guess I sorta "finished" basically the vision which is full attribute driven definitions on models and setup appropriate abstraction layers for different connection engines.
<?php
namespace technexus\Models;
use Divergence\Models\Mapping\Column;
use Divergence\Models\Mapping\Relation;
class BlogPost extends \Divergence\Models\Model
{
use \Divergence\Models\Relations;
public static $tableName = 'blog_posts';
private string $Title;
private string $Permalink;
private string $MainContent;
#[Column(type:"timestamp",notnull:false)]
private $Edited;
private string $Status;
#[Relation(
type: 'one-one',
class: User::class,
local: 'CreatorID',
foreign: 'ID'
)]
protected $Creator;
#[Relation(
type: 'one-many',
class: PostTags::class,
local: 'ID',
foreign: 'BlogPostID'
)]
protected $Tags;
New Hydration Method
Okay well you may be asking why all this abstraction? Well it's just easier to build your objects with Reflections during hydration than doing it through the normal PHP life cycle. Late static binding and instantiating ourselves in the model the classic way had it's run but now we're instead just using reflections to generate a "fake" object based on the Model's class and hydrate it again with reflections. That's pretty much the "magic trick" that allows full DTO mapping in PHP land.
Anyway here's version 3 of Divergence.
It's running this site too.
Here are my benchmarks right now
In the process of doing all this I really wanted a good profiler and test case so I ended up building a new PHP Benchmarking Suite which I call "The PHP Bench". The screenshot above is the results command doing it's thing on the CLI. But then I made a website for it where results can be viewed online.
I'm particularly proud of the architecture of this benchmark. I hand designed the Models to simulate several types of data. Canary in particular handles every type of field my framework supports. But then I also have fixed ints, variable strings, and fixed strings, among other benchmarks you can run. Every type shows a clear and distinct performance profile among the various frameworks.
Each framework gets their own composer.json and isolated vendor path that only gets required for their individual test so none of the frameworks can really interact with each other or cause conflicts. The findings were completely unexpected to me but I'm proud to say my self made ORM is among the world's best.
Now that I have a profiler and benchmark I can use my next step is to scaffold some first to save() and some first to query profilers against some of these and see if I can shave another 0.02-0.05 ms per operation. I never really thought I'd get to hit this milestone so soon. So that's nice.
I honestly feel like a dog that finally caught the truck. Now what.
Haha just kidding. There's so much I can do with this now! With agents I can just throw the docs at it. I even got a minimal context size one written up for MVPs. I've already got this blog working of course but what's next? You can't see it but I already got my admin page slightly tricked out.
I didn't even use react. I told it to use classic jquery ajax calls so it's dead simple.
So what else is there to do? I've been wanting to tackle some federated things and see how far I can get. In particular I think it would be fun to turn this blog into a federated node and implement the ActivityPub protocol.