//Two manufactuers may use the same code for their product so the PK needs to be manufacturer id + product code
$product = $products[$manufacturerId][$productCode];
I’m intrigued by the concept of using array indexes to specify filters and possibly sorting, limits, grouping, etc. However, that is really the only thing here that hasn’t been done a thousand times before. Therefore, I much rather have the after-mentioned concept added to an existing, well known ORM or ActiveRecord than building a completely new one. Just seems like there would be more value in that than creating yet another one. This would work pretty well with Eloquent for example. I know Eloquent is an ActiveRecord and not a Mapper but the concept would add a another level elegance to the system.
Ex.
$users = User[‘status=1’][‘name=blah’]->find();
The main issue with that would be specifying bind parameters, and differentiating between field context when it comes to managing different clauses like sorting, limits, etc.
Interesting change-up of the standard API one the less but I would never use an unknown, untested solution over a well known one just because of this alone.
I have a question. Wouldn’t it be better, if the class instantiation for the object is made a lot simpler too? You are giving us very nice methods for filtering, but instantiating classes is quite mofugly.
$authors = new \Maphper\Maphper(new \Maphper\DataSource\Database($pdo, 'author'));
$blogs = new \Maphper\Maphper(new \Maphper\DataSource\Database($pdo, 'blog', 'id'));
$blogs->addRelation('author', new \Maphper\Relation\One($authors, 'authorId', 'id'));
Need I say, there is too much duplication?
To me, instantiating the map should be something as simple as
$blogs = new Maphper('blog'); //and that is it!
Because we had mapped the blog object with the author relationship once earlier at some point in time (and the core reason of having a mapping system to begin with, right?), then the code in the example in your docs could also be a bit reduced.
$blog = new stdClass;
$blog->title = 'My First Blog';
$blog->date = new \DateTime();
$blog->author->name = 'Tom Butler';
$blogs[] = $blog;
Ok, it is only one line, but it is simpler. I think the author relationship should be automatically instantiated “internally” through the mapping done earlier.
Does that make any sense?
Edit: I am also missing some sort of unit of work. I can’t imagine you’d want the give the dev the ability to create and fire off database queries to be one-to-one with the writing method you explained above. So you might need to offer something like…
$blogs[] = $blog;
$blogs[] = $blog->flush();
or
$blogs[] = $blog->persist();
where “flush” or “persist” is the final “ok, I am now ready to finally start working with the database” methods.
Actually the only thing that you can use the array indexes for is primary key lookup e.g. $users[$pk1][$pk2][$pk3] but those fields have to be designed as the primary key.
What it does do that hasn’t (AFAIK) been done before is generating database tables without any kind of developer supplied metadata. Consider a completely empty schema and running this code:
$users = new Maphper(new \Maphper\DataSource\Database($pdo, 'user', 'id', ['editmode' => true]));
$user = new stdclass;
$user->name = 'Tom';
$user->level = 1;
$user->registrationDate = new \DateTime
$users[] = $user;
Will create a table user with fields int id, varchar name, int level and datetime registrationDate
It will add indexes to relevant fields e.g. in this case ‘type ASC’ and ‘name DESC’
Obviously this is designed for development and has a performance overhead so should be turned off in production
With editmode turned on, Maphper will do an inversion of the usual control and remove the control from the database and give it to the application. It will reshape the database based on the supplied data so that the data can be saved. No more errors or loss of data when trying to write a 256 character string to a VARCHAR(255) column, it will just resize the column and then save the data.
Which will find any of the author’s books with sales greater than 50000 or a publication date before 1999.
I totally agree and actually have done something to fix that problem but the reason Maphper needs a complex instantiation is that it will support other data sources e.g.
new Maphper(new \Maphper\DataSource\CSV('../afile.csv'));
new Maphper(new \Maphper\DataSource\XML('./file.xml'));
new Maphper(new \Maphper\DataSource\TwitterFeed('username', $oauth);
etc.
I agree that the instantiation is a bit much but it’s the only way to keep the code flexible and extensible What I’ve actually done is created a factory that loads an XML file (for now) of all the mappers and their relationships in the system that then allows:
$loader = new \Maphper\Loader\Xml('maphperconfig.xml');
$blogs = $loader->getMapper('blogs');
Again, I’ve not convinced myself this is the best way yet so I haven’t released it.
I like this idea but the problem with this approach is that you cant use stdclass for the objects as when creating stdclass it obviously won’t have the author property set to being an object so the $blog->author->name = '' line will fail. Thinking about it, this should work already:
$blog = $blogs->createNew();
$blog->title = 'My First Blog';
$blog->date = new \DateTime();
$blog->author->name = 'Tom Butler';
$blogs[] = $blog;
Of course this makes the assumption that the mapper is available in the place the $blog object is constructed.
In the name of simplicity (Treating the mapper like an array), I can’t see any direct advantage of the $blog->persist() call. I’m happy to be shown otherwise but what is the practical difference between:
$blogs[] = $blog;
and
$blogs[] = $blog->persist();
If it’s a case of explicitly saying “I want to store the data in the database” then the first is surely saying “I want to store the data in the array”.
One other thing: Part of that is down to the namespaces, if you use the use keyword a lot of that verbosity is lost:
use Maphper\Maphper;
use Maphper\DataSource\Database;
use Maphper\Relation\One;
$authors = new Maphper(new Database($pdo, 'author'));
$blogs = new Maphper(new Database($pdo, 'blog', 'id'));
$blogs->addRelation('author', new One($authors, 'authorId', 'id'));
So it builds the persistent storage schema as you fetch and save data. I’m not to sure how I feel about that. Seems brittle and prone to breakage. Perhaps a nice thing for quickly prototyping though. You’re kinda taking agile to the extreme with this.
One of the first issues I see with this is updating an existing db for new features. If a db already exists you would have to run these commands at least once on the environment to sync up the environment schema. Which means that at least once the application would need to alter the schema of the persistent storage device in a production environment. You have essentially created a poor mans “db” update taking after the concept of a poor mans cron where the application request build the structure on the db as needed.
Not sure I agree with this. If anything it’s less brittle as it will always save data given to it. The upshot of this is that an insert query will never fail. The downside, of course, is that the database doesn’t enforce types. Think of it like json_encode() or csv/xml storage. There’s no type checking of the data it’s just stored so that it can be retrieved later.
Essentially I’m trying to reduce the need to describe the data in both the application and the database. If data passes the application’s validation rules, then the database is amended on the fly to run with it. If a new column is added in the application, it’s added in the database automatically rather than having to add the column in the database and then reference it in the code, referencing it in the code is enough.
Where it breaks, of course, is if other applications need to access the same database. However, for what I need 99% of the time this is not an issue (and if it is, it’s easy enough to turn off the DB modification feature).