Permalink
Browse files

initial revision

  • Loading branch information...
0 parents commit 2ab9937ee5ec9bf2d34da1b9fa4cc26d22f0255e @jaz303 committed Apr 28, 2010
Showing with 1,201 additions and 0 deletions.
  1. +63 −0 DOCS
  2. +32 −0 TODO
  3. +13 −0 backend.php
  4. +150 −0 backend/Annotation.php
  5. +67 −0 backend/Attribute.php
  6. +127 −0 backend/Builder.php
  7. +52 −0 backend/Callback.php
  8. +203 −0 backend/Model.php
  9. +67 −0 backend/Property.php
  10. +17 −0 backend/Universe.php
  11. +29 −0 backend/Utilities.php
  12. +133 −0 backend/validation.php
  13. +239 −0 runtime/spitfire.php
  14. +9 −0 test.php
@@ -0,0 +1,63 @@
+
+
+Attributes
+----------
+
+A model class has exactly one attribute for each underlying database field, and this attribute is backed by an instance variable of the same name.
+
+The only exception are autonumber/serial fields, which are not regarded as attributes. This might change.
+
+Valid types for attributes:
+
+ - boolean
+ - date
+ - datetime
+ - float
+ - integer
+ - serialize
+ - string
+
+These types map directly to underlying database types, except serialize - which is first passed
+through PHP's serialize() function before being stored in the DB as a string. It is your responsbility to ensure that anything stored in serialized attributes is safe for serialization.
+
+During code generation, an attribute will generate a corresponding instance variable if none is defined.
+
+Properties
+----------
+
+Valid types for properties:
+
+ - boolean
+ - date
+ - datetime
+ - float
+ - integer
+ - serialize
+ - string
+ - ClassName
+
+During code generation, a property will generate a corresponding getter and setter for each property if none is defined.
+
+Scalar properties are always backed by an attribute of the same name.
+Because attributes are always type-safe, a corollary to this is that
+
+When a class used as a property, any number of attributes will be automatically generated based on the needs of the class. The class must implement a bunch of static methods:
+
+ Money::persistence_attributes()
+ -> array('i:cents' => 'cents',
+ 'i:currency' => 'currency')
+
+The keys define the types and names of the attributes required by this class, and the values define the getter methods on the instances.
+
+ Money::parse_from_model_assignment($base_name, $array) {
+
+ }
+
+ Money::awake_from_persistence($cents, $currency) {
+ return new Money($cents, $currency);
+ }
+
+Additionally, the definition of this class must be available at code generation time.
+
+Generated properties will also define $foo_before_typecast and get_foo_before_typecast()
+
@@ -0,0 +1,32 @@
+We have the following:
+
+[X] Attributes
+[X] Batch setting attributes
+[X] Attribute quoting
+[X] Properties
+[ ] Typecasting for properties
+[ ] Properties without explicit types
+[ ] Validations
+[ ] Get find_one working
+[ ] Primary keys
+[ ] Create/update
+[ ] Events/hooks/filters
+[ ] STI
+[ ] has_many/belongs_to
+[ ] acts_as_list
+[ ] acts_as_tree
+
+interesting problems:
+ - add serialize type for attribute
+ - properties/attributes still need a bit of thought
+ - need a way of creating new property types (or maybe something simple like capital letter for class)
+ - serialization contract for objects (e.g. money/date/address)
+ - belongs_to is going to need to be able to override a setter for its associated _id method
+
+
+Once we have this all done, let's go for a public release (that and BasePHP)
+
+---
+
+[ ] complex finders
+[ ] eager loading
@@ -0,0 +1,13 @@
+<?php
+require dirname(__FILE__) . '/runtime/spitfire.php';
+
+require dirname(__FILE__) . '/backend/Annotation.php';
+require dirname(__FILE__) . '/backend/Builder.php';
+require dirname(__FILE__) . '/backend/Universe.php';
+require dirname(__FILE__) . '/backend/Model.php';
+require dirname(__FILE__) . '/backend/Attribute.php';
+require dirname(__FILE__) . '/backend/Property.php';
+require dirname(__FILE__) . '/backend/validation.php';
+require dirname(__FILE__) . '/backend/Callback.php';
+require dirname(__FILE__) . '/backend/Utilities.php';
+?>
@@ -0,0 +1,150 @@
+<?php
+namespace spitfire;
+
+class Annotation
+{
+ const PROPERTY = 1;
+ const METHOD = 2;
+
+ /**
+ * Parse the annotations for a given Reflector.
+ * Annotations are derived from doc comments, and are similar to Java's.
+ *
+ * Annotation syntax is simple:
+ *
+ * :foo = expr
+ *
+ * Where 'expr' is a valid JSON expression containing no new lines.
+ * We also support single values, not nested in arrays/objects.
+ * You can't use any null expressions - this would be seen as a syntax
+ * error. You can, of course, create arrays/objects containing nulls.
+ *
+ * It's also valid to do:
+ *
+ * :foo
+ *
+ * Which is simply a shortcut for
+ *
+ * :foo = true
+ *
+ * The JSON is subject to whatever nuances affect PHP's json_decode().
+ * Particularly, string keys must always be enclosed in quotes, and
+ * all string quoting must be done with double quotes.
+ *
+ * Example usage:
+ *
+ * :requires_super_user = true
+ * :requires_privileges = { "foo": "crude" }
+ *
+ * You can build up arrays on separate lines for clarity:
+ *
+ * :extensions[] = { "name": "Extension1", "param": "foo" }
+ * :extensions[] = { "name": "Extension2", "param": "bar" }
+ *
+ * @todo this method should cache its results as the builder hammers it pretty hard
+ *
+ * @param $r <tt>Reflector</tt> for which to parse annotations
+ * @return associative array of annotations for <tt>$r</tt>
+ */
+ public static function parse(\Reflector $r) {
+
+ $comment = $r->getDocComment();
+ if (strlen($comment) == 0 || strpos($comment, ':') === false) {
+ return array();
+ }
+
+ $annotations = array();
+ preg_match_all('/\*\s+:(\w+)(\[\])?\s*(=\s*(.*))?$/m', $comment, $matches, PREG_SET_ORDER);
+ foreach ($matches as $m) {
+ if (!isset($m[4])) {
+ $decode = true;
+ } else {
+ $json = trim($m[4]);
+ if ($json[0] == '[' || $json[0] == '{') {
+ $decode = json_decode($json, true);
+ } else {
+ $decode = json_decode('[' . $json . ']', true);
+ if (is_array($decode)) {
+ $decode = $decode[0];
+ }
+ }
+ }
+ if ($decode === null) {
+ throw new Error_Syntax("Invalid JSON fragment: $json");
+ }
+ if ($m[2] == '[]') {
+ $annotations[$m[1]][] = $decode;
+ } else {
+ $annotations[$m[1]] = $decode;
+ }
+
+ }
+
+ return $annotations;
+
+ }
+
+ /**
+ * Returns the annotations for a given class.
+ *
+ * @param $class class name
+ * @return associative array of annotations for <tt>$class</tt>
+ */
+ public static function for_class($class) {
+ return self::parse_annotations(new ReflectionClass($class));
+ }
+
+ /**
+ * Returns the annotations for a given method.
+ *
+ * @param $class class name
+ * @param $method method name
+ * @return associative array of annotations for <tt>$class::$method</tt>
+ */
+ public static function for_method($class, $method) {
+ return self::parse_annotations(new ReflectionMethod($class, $method));
+ }
+
+ /**
+ * Returns an array of multiple annotations for a class.
+ *
+ * @param $class class name to select annotations from
+ * @param $include bitmask specifying search-space (properties and/or methods, default: both)
+ * @param $with optional annotation key which must be present for annotation to be present in output set
+ * @param $accessible set accessible flag?
+ * @return array of entries, each entry is array(Reflector, annotations)
+ */
+ public static function select($class, $include = null, $with = null, $accessible = false) {
+
+ if ($include === null) {
+ $include = self::PROPERTY | self::METHOD;
+ }
+
+ $reflector = new ReflectionClass($class);
+ $found = array();
+
+ if ($include & self::PROPERTY) {
+ foreach ($reflector->getProperties() as $property) {
+ $annotations = self::parse_annotations($property);
+ if (!count($annotations)) continue;
+ if ($with && !isset($annotations[$with])) continue;
+ $property->setAccessible($accessible);
+ $found[] = array($property, $annotations);
+ }
+ }
+
+ if ($include & self::METHOD) {
+ foreach ($reflector->getMethods() as $method) {
+ $annotations = self::parse_annotations($method);
+ if (!count($annotations)) continue;
+ if ($with && !isset($annotations[$with])) continue;
+ $method->setAccessible($accessible);
+ $found[] = array($method, $annotations);
+ }
+ }
+
+ return $found;
+
+ }
+}
+?>
@@ -0,0 +1,67 @@
+<?php
+namespace spitfire;
+
+/**
+ * An Attribute is a single, typed value that is persisted to the database.
+ * Every attribute is backed by a protected or public instance variable.
+ * At code generation time, a protected instance variable will be created if none exists.
+ *
+ * You won't often deal with attributes directly, in fact it's actively discouraged.
+ * Use a Property instead!
+ *
+ * Supported types: boolean, date, datetime, float, integer, serialize, string
+ * Types that might be supported someday: time, anything else that fits into a single SQL field
+ */
+class Attribute
+{
+ private $model;
+ private $name;
+ private $type;
+
+ public function __construct(Model $model, $name, $type) {
+ $this->model = $model;
+ $this->name = $name;
+ $this->type = $type;
+ }
+
+ public function name() { return $this->name; }
+ public function type() { return $this->type; }
+
+ public function render_class_body() {
+ if (!$this->model->get_reflection()->hasProperty($this->name)) {
+ return "protected \${$this->name};\n";
+ } else {
+ return "";
+ }
+ }
+
+ public function quoter($db) {
+ if ($this->type == 'serialize') {
+ return "\${$db}->quote_string(serialize(\$this->{$this->name}))";
+ } else {
+ return "\${$db}->{$this->quote_method()}(\$this->{$this->name})";
+ }
+ }
+
+ public function assignment_from_array($array_name) {
+ if ($this->type == 'serialize') {
+ return "\$this->{$this->name()} = unserialize({$array_name}['{$this->name()}']);";
+ } else {
+ return "\$this->{$this->name()} = {$array_name}['{$this->name()}'];";
+ }
+ }
+
+ public function quote_method() {
+ switch ($this->type) {
+ case 'boolean': return 'quote_boolean';
+ case 'date': return 'quote_date';
+ case 'datetime': return 'quote_datetime';
+ case 'float': return 'quote_float';
+ case 'integer': return 'quote_integer';
+ case 'serialize': throw new Exception("internal error - serialize shouldn't get here!");
+ case 'string': return 'quote_string';
+ default: throw new Exception("unknown attribute type - {$this->type}");
+ }
+ }
+}
+?>
Oops, something went wrong. Retry.

0 comments on commit 2ab9937

Please sign in to comment.