I'm realy not comfortable with having multiple transactions in the same session, as it _realy_ complicates some stuff if I wanna add automatic registerDirty() behavior.
| SitePoint Sponsor |
I'm realy not comfortable with having multiple transactions in the same session, as it _realy_ complicates some stuff if I wanna add automatic registerDirty() behavior.
Just had a quick look, looks good so far.
One thing that noticed was....
$sql = str_replace('`',$this->escapeCharacter,$sql);
in PDOProxy::query(), which means databases that are running as ANSI quote standard compliant (escapeCharacter would be ") means can never get a ` into the database.
Ren, actually - that's what i's for. It makes you able to write queries with ` and have them replaced with the escape char you want. And that only effects the query, not the values sent in.
Ah yes, unless the values are hardcoded in the SQL.Originally Posted by thr
As an aside, the way to escape names is..
$escapedName = $this->escapeCharacter.str_replace($this->escapeCharacter, $this->escapeCharacter.$this->escapeCharacter, $name).$this->escapeCharacter;
You can have a column named foo`bar, you'd have to be pretty insane todo it, but..
Did put a feature request in for PDO to have a quoteName function, but got rejected :/
Ok, thanks for the heads up there :], I'll lock into hacking a regexp to do the proper replacing,
Also if wanted to figure out the quote character automatically
SELECT CASE FIND_IN_SET(\'ANSI_QUOTES\', @@global.sql_mode) WHEN 0 \'`\' ELSE \'"\' END quoteChar
Would be the MySQL query.
Damn where did you learn all this sql magic? ;p nah, I'll leave that up to the user to supply the correct escape character, thanks tho.
Ok, I released my ORM now, all information is available at the trac: http://model.serverside.se/trac/wiki/WikiStart, small tutorial + download links + api documentation.

Thanks thr and well done!
Looking at it now...
The bleeding edge version is always available from the svn at @ http://model.serverside.se:83/repos/
Had a slight more indepth look, the API seems quite nice for doing simple operations, insert rows and what not.
Tried to add sqlite support [as I like sqlite for just hacking about in], using an assumption that the first column of the table is PK, and using 'SELECT * FROM `$table` WHERE 1 = 0', to get the column names and types.
But unfortunately if PDO returns no rows, you cant access the column metadata, which is pretty poor imo.
After the extreme hack of making sure the table(s) have atleast one row, using SELECT * FROM `table` LIMIT 1, did actually get the basic operations working.
PS also posted a ticket on trac for some undefined variables.PHP Code:protected function loadPDOTableInfo($type)
{
$type = (string)$type;
if(isset($this->tableInfo[$type]))
{
return $this->tableInfo[$type];
}
else
{
$table = call_user_func(array($type, '__getTable'));
$table = is_null($table) ? $type : (string)$table;
$this->tableInfo[$type] = array();
$this->tableInfo[$type]['id'] = null;
$this->tableInfo[$type]['keys'] = array();
$this->tableInfo[$type]['fields'] = array();
$this->tableInfo[$type]['table'] = $table;
$this->tableInfo[$type]['types'] = array();
$this->tableInfo[$type]['relations'] = call_user_func(array($type, '__getRelations'));
try
{
$stmt = $this->pdo->prepare("SELECT * FROM `$table` LIMIT 1");
$stmt->execute();
$n = $stmt->columnCount();
for($i = 0; $i < $n; ++$i)
{
$meta = $stmt->getColumnMeta($i);
$this->tableInfo[$type]['types'][$meta['name']] = $meta['native_type'];
if ($i === 0)
{
$this->tableInfo[$type]['id'] = $meta['name'];
$this->tableInfo[$type]['keys'][] = $meta['name'];
}
else
{
$this->tableInfo[$type]['fields'][] = $meta['name'];
}
}
if(count($this->tableInfo[$type]['keys']) == 0)
{
throw new RuntimeException('Could not load info for table "'.$table.'" no keys in table.');
}
elseif(count($this->tableInfo[$type]['keys']) == 1 && is_null($this->tableInfo[$type]['id']))
{
$this->tableInfo[$type]['id'] = $this->tableInfo[$type]['keys'][0];
}
return $this->tableInfo[$type];
}
catch(RuntimeException $e)
{
unset($this->tableInfo[$type]);
throw new RuntimeException("Could not load info for tabel '$table', caught exception of type: " . get_class($e) . " with message: '" . $e->getMessage() . "'");
}
}
}
Oh, thank you RenI'm doing some redesign in the internal stuff, etc. and alpha 0.1 is well... alpha 0.1 so.
I'm going to try to move to a more object-centric approach then the current sql-centric approach, how would you(and all others) that are familiar with orms feel about that? (not hiding sql in any way, just well... more of an "object"-feel and less of a "object-mixed-with-sql"-feel").
The current code (alpha 0.1) also holds some nasty bugs, etc - which I gotta sort out. Also trying to get a better "flow" of the codebase in the next release which will differ quite much from alpha 0.1 (inheritance should be in for once, more object-centric as I already mentioned).
To well, explain my more "object"-centric approach idea, here's an example:The above also works with set/get:ers and protected properties but for simplicty I used public properties herePHP Code:<?php
class Person
{
public $name;
public $email;
public $age;
public $groups;
}
?>
Then to save your object to the database - you have two choices:
1. Let Model try to auto-discover your datatypes (if you're lazy and only need float/integer/string)
2. Give model a "template" object which it will use to create the tables - such as:
So basicly, If you don't want to you have no need to create any SQL, Jointables, etc. at all - you create your objects, feed model a "template" object the first time you try to insert one object(these templates can be saved in %ModelAppDir%/templates/ and be autosensed, say Person.php for objects of type Person).PHP Code:<?php
$tpl= new Person;
$tpl->name = Model::string(25);
$tpl->email = Model::string(100);
$tpl->age = Model::int(3);
$tpl->groups = Model::collection("Group"); /* Yes N:M relations will be possible, altho I'll always advice against it */
$model = Model::getInstance("default");
$model->setTemplate($person);
?>
It will be able to map inheritance properly(I hope) and my "ObjectQuery"(which I've yet to get a better name) will be able to handle polymorphism.

I've also started playing with it - trying to get the tests to work into MSSQL
I've started hacking together a Metadata::loadMSSQLTableInfo() function. However, learning how to get MSSQL to tell me about the meta data is taking more reading of the online book than I expected! I've also got a set of unitTests/sql/mssql_XXX.txt files that seem to work ok.
Hmm.. from what you two are telling me you like the current approach I've got in Model Alpha 0.1 ? How would the people that tested the current Alpha 0.1 feel about moving into a more "object" approach in the one I described two posts above?
Think prefer the template object idea from above, removes some cruft (__getRelations() etc) from the domainobject classes.
Also I think I'd like the idea of using some sort of Container. (see picoContainer, phemto) for registering DomainObjects, and handing that to the model for it to instance them. But I just maybe going overboard with Containers atm, as introduced them into my Routes solution, and doing stuff like
PHP Code:// Just so we get the arguments described
class PDOEx extends PDO
{
function __construct($dsn, $user = '', $password = '')
{
parent::__construct($dsn, $user, $password);
}
}
$container = new Container();
/* Read application configuration, and setup container.... registerCached means this container will only create it once, and cache the object, sort of singleton with respect to this container.
*/
$container->registerCachedComponent('PDO', 'PDOEx', array('dsn' => 'sqlite:test.sql3',
'user' => '',
'password' => ''));
$container->registerCachedComponent('PDOHelper', 'PDOHelper', array('escapeCharacter' => '"'), 'Model/PDOHelper.php');
$container->registerCachedComponent('Model', 'Model', array('classpath' => ''), './Model.php');
// Allows for lazy creation so later in application can retrieve the Model, and it'll build the object tree (PDOEx, PDOHelper, & Model)
$model = $container->getInstance('Model');

I haven't played enough to have a preference. I quite like the idea about the templates though.Originally Posted by thr
Ok, currently something like this works (for the more object centric approach):
classes/Person.phptemplates/Person.php:PHP Code:<?php
class Person implements Persistable
{
public $name;
public $email;
public $age;
public $groups;
protected $parent;
protected $savings;
public function setParent($val) { $this->parent = $val; }
public function getParent() { return $this->parent; }
public function setSavings($savings) { $this->savings = (double)$savings; }
public function getSavings() { return $this->savings; }
public function getNameEmailLink() { return "<a href='mailto{$this->email}'>{$this->name}</a>"; }
}
?>test.php:PHP Code:<?php
$template = new ModelClassMap("Person");
$template->name = Model::stringField(10);
$template->email = Model::stringField(25);
$template->age = Model::integerField(3);
$template->groups = Model::collectionField("Group");
$template->setSavings(Model::doubleField(100));
$template->setParent(Model::objectField("Person"));
?>Say if you changed age to this:PHP Code:<?php
require 'classes/Person.php';
require 'templates/Person.php';
$person = new Person;
$person->name = "thr";
$person->email = "thrthr@gmail.com";
$person->age = 20;
$person->groups = array();
$person->setSavings(0.0);
$person->setParent(new Person);
$template->validateObject($person);
?>It would throw an Exception saying that age does not pass validation. It's also possible to build "custom" datatypes and use them instead, say you could build a custom datatype called Dollar, which would require a format such as '~^[\d\.]+\$$~' (expressed in regexp), extend it from the ModelStringField and changed the validate() method, voila. And it will ofc. be saved to the database (as string ofc, tho.)PHP Code:<?php
$person->age = "20";
?>
From these maps I'm also able(or will be when the code works ;p) to generate database tables, link tables, and such.
What about constraints that involve multiple fields? Perhaps have a collection of them on the template?
Also would need somewho to identify when a field can hold a NULL value. Think lastcraft posted an idea of using a fluent style interface for defining table structures, which might be good.
Hmm.. not too sure about that. I say I'm not sure because incoming data is always a string datatype.Originally Posted by thr
If the model knows that age should be an integer, then why not instead automatically cast it as an integer before validating?
I dunno, it just seems throwing an Exception like that because it is a string doesn't fit very well with PHP's type juggling. I think auto casting would better fit because then you could still keep strict guidelines for the properties, but not loose type juggling.
Hmm I think keep the base types as rigid as possible. Can always create a custom type todo more type juggling.
I'm not saying take that away.Originally Posted by Ren
You know what happends when you quote a string with PDO::quote() ? It get's " around it, I know MySQL can take strings as integers, but asfar as I can tell no other DB does that(and I for one think MySQL is wrong doing that).Originally Posted by dreamscape
Because if it is a string but should be a integer, it's invalid data.Originally Posted by dreamscape
For this example you can just as well do:And keep Person::$age protected/private. You won't ever get a conflict with the DB and the data is always correct.PHP Code:<?php
class Person
{
/* ... */
setAge($age){ $this->age = (int)$age; }
getAge() { return $this->age }
/* ... */
}
?>
Hmm... not following you here... how do you mean constrains on multiple fields?Originally Posted by Ren
Agreed, might add a second parameter in the constructor, something like:Originally Posted by Ren
PHP Code:<?php
class ModelClassField
{
/* ... */
public function __construct($type, $allownull = false){
/* ... */
}
?>Hmm.. interesting, where?Originally Posted by Ren
Edit: A thing I need advice on currently his how to do with the set/getMethods(), as currently the ModelClassReflector (wraps the ReflectionClass + ReflectionProperty + ReflectionMethod classes in a nicer interface just) looks for two methods that have the same name except the set/get prefix. Is this sufficent or should I look for a protected/private property named with the same name as the methods (w/o the prefix of set/get ofc.) ?
You've completely missed what I was saying.Originally Posted by thr
That's ridiculous. All user data is a string. You're saying that all user data that you want to be something other than a string is inherently invalid. It is inherently untrustworthy, but that doesn't make it inherently invalid just because PHP reads it in as a string and not an integer. That's absurd, and pretty much flies right in the face of how PHP deals with types.Originally Posted by thr
There's no logical reason that you shouldn't be able to do $person->age = '24'; and have it automatically mapped as an integer.
Let me put this another way... what exactly is the purpose of having datatypes in the model map templates, if you have to manually map the datatypes anyways?
http://www.sitepoint.com/forums/show...48&postcount=1Originally Posted by thr
Though I remembered that looking alot nicer.
Just the methods is probably good enough, another possibility with 5.1 is use Reflection*::getDocComment() and use a marker keyword in that.Originally Posted by thr
Bookmarks