Location: PHPKode > projects > Dbscript > db/library/dbscript/model.php
<?php

  /** 
   * dbscript for PHP 4 & 5 - restful crud framework
   * @version 0.3.0 -- 10-Jun-2007
   * @author Brian Hendrickson <hide@address.com>
   * @link http://dbscript.net/
   * @copyright Copyright 2007 Brian Hendrickson
   * @package dbscript
   * @license http://www.opensource.org/licenses/mit-license.php MIT License
   */

  /**
   * Data Model
   * 
   * Describes a database table: fields, rules and relationships.
   *
   * Automatically composes simple JOIN queries for relationships
   * described in the models via has_many, has_one, etc.
   * 
   * Usage:
   * <code>
   *   // define a new table named photos
   * $model =& $db->model( 'photo' );
   *
   *   // define the fields in the table
   * $model->auto_field( 'id' );
   * $model->file_field( 'image' );
   * $model->char_field( 'title', 255 );
   * $model->text_field( 'caption' );
   *
   *   // create the table in the database
   * $model->save();
   *
   *   // deny access to everybody, unless they are an administrator
   * $model->let_access( 'all:never all:admin' );
   *
   *   // function to test whether current user is an administrator
   * function admin() {
   *   return true;
   * }
   * </code>
   * 
   * More info...
   * {@link http://dbscript.net/model}
   * 
   * @package dbscript
   * @author Brian Hendrickson <hide@address.com>
   * @access public
   * @version 0.3.0
   */

class Model {
  
  /**
   * name of the database table
   * @var string
   */
  var $table;
  
  /**
   * true if table exists in database
   * @var boolean
   */
  var $exists;
  
  /**
   * list of field names
   * @var string[]
   */
  var $field_array;
  
  /**
   * list of field attributes
   * @var string[]
   */
  var $field_attrs;
  
  /**
   * list of security access rules
   * @var string[]
   */
  var $access_list;
  
  /**
   * name of primary key field
   * @var string
   */
  var $primary_key;
    
  /**
   * name of collection key field
   * @var string
   */
  var $uri_key;
  
  /**
   * list of relationships to other tables
   * @var string[]
   */
  var $relations;
  
  /**
   * proper CamelCase name of data model object
   * @var string
   */
  var $custom_class;
  
  /**
   * list of public methods
   * @var string[]
   */
  var $allowed_methods;
  
  /**
   * when querying with find(), offset by x records
   * @var integer
   */
  var $offset;

  /**
   * limit query to x records
   * @var integer
   */
  var $limit;

  /**
   * order query by this column
   * @var string
   */
  var $orderby;

  /**
   * order query ASC or DESC
   * @var string
   */
  var $order;
  
  /**
   * list of Collection/Feed params for layout
   * @var string[]
   */
  var $params;
  
  /**
   * hide this table
   * @var boolean
   */
  var $hidden;
    
  function insert_from_post( &$req ) {
    $db =& db_object();
    $fields = $this->fields_from_request($req);
    foreach ($fields as $table=>$fieldlist) {
      
      // for each table in the submission do
      $pkfield = $db->models[$table]->primary_key;
      $rec = $db->models[$table]->base();
      foreach ( $fieldlist as $field=>$type )
        $rec->set_value( $field, $req->params[strtolower(classify($table))][$field] );
      $result = $rec->save_changes();
      if ( $result ) {
        $atomentry = $db->models['entries']->base();
        if ($atomentry) {
          $atomentry->set_value( 'etag', getEtag( $rec->$pkfield ) );
          $atomentry->set_value( 'resource', $table );
          $atomentry->set_value( 'record_id', $rec->$pkfield );
          $atomentry->set_value( 'content_type', 'text/html' );
          $atomentry->set_value( 'last_modified', timestamp() );
          $atomentry->set_value( 'person_id', get_cookie_user() );
          $aresult = $atomentry->save_changes();
          if ($aresult) {
            if ( array_key_exists( 'entry_id', $rec->attributes ))
              $rec->set_value( 'entry_id', $atomentry->id );
            if ( array_key_exists( 'person_id', $rec->attributes ))
              $rec->set_value( 'person_id', get_cookie_user() );
            $rec->save_changes();
          }
        }
        if (isset($rec->id)) {
          $req->set_param( 'id', $rec->id );
          $req->id = $rec->id;
        }
      } else {
        trigger_error( "The record could not be saved into the database.", E_USER_ERROR );
      }
    }
  }
  
  function update_from_post( &$req ) {
    $db =& db_object();
    if (!(isset($req->params['entry']['etag'])))
      trigger_error( "Sorry, the etag was not submitted with the database entry", E_USER_ERROR );
    $fields = $this->fields_from_request($req);
    $atomentry = $db->models['entries']->find_by( 'etag', $req->params['entry']['etag'] );
    $recid = $atomentry->attributes['record_id'];
    $rec = $this->find( $recid );
    $Person =& $db->model('Person');
    $Group =& $db->model('Group');
    $p = $Person->find( get_cookie_user() );
    if (!($p->id == $atomentry->attributes['person_id']) && !$this->can_superuser($req->resource))
      trigger_error( "Sorry, your id does not match the owner of the database entry", E_USER_ERROR );
    $fieldsarr = $fields[$rec->table];
    foreach ($fieldsarr as $field=>$type)
      $rec->set_value( $field, $req->params[strtolower(classify($rec->table))][$field] );
    $result = $rec->save_changes();
    foreach ($fields as $table=>$fieldlist) {
      // for each table in the submission do
      if (!(in_array( $table, array('entries',$rec->table), true ))) {
        $rel = $rec->FirstChild( $table );
        foreach ($fieldlist as $field=>$type)
          $rel->set_value( $field, $req->params[strtolower(classify($table))][$field] );
        $rel->save_changes();
      }
    }
    if ($result) {
      $atomentry->set_value( 'last_modified', timestamp() );
      $atomentry->save_changes();
    } else {
      trigger_error( "The record could not be updated in the database.", E_USER_ERROR );
    }
  }
  
  function delete_from_post( &$req ) {
    $db =& db_object();
    if (!(isset($req->params['entry']['etag'])))
      trigger_error( "Sorry, the etag was not submitted with the database entry", E_USER_ERROR );
    $fields = $this->fields_from_request($req);
    $atomentry = $db->models['entries']->find_by( 'etag', $req->params['entry']['etag'] );
    $recid = $atomentry->attributes['record_id'];
    $rec = $this->find( $recid );
    $Person =& $db->model('Person');
    $Group =& $db->model('Group');
    $p = $Person->find( get_cookie_user() );
    if (!($p->id == $atomentry->attributes['person_id']) && !$this->can_superuser($req->resource))
      trigger_error( "Sorry, your id does not match the owner of the database entry", E_USER_ERROR );
    $result = $db->delete_record($rec);
  }
  
  function fields_from_request( &$req ) {
    $db =& db_object();
    $fields = array();
    $obj = strtolower(classify($this->table));
    foreach ($this->field_array as $fieldname=>$datatypename) {
      if (isset($req->params[$obj][$fieldname])) {
        $fields[$this->table][$fieldname] = $datatypename;
      }
    }
    foreach ($this->field_array as $fieldname=>$datatypename) {
      if (isset($_FILES[$obj]['name'][$fieldname])) {
        $fields[$this->table][$fieldname] = $datatypename;
        $req->params[$obj][$fieldname] = $_FILES[$obj]['tmp_name'][$fieldname];
      }
    }
    foreach ($this->relations as $table=>$vals) {
      if ( isset( $db->models[$table] )) {
        $obj = strtolower(classify($table));
        foreach ( $db->models[$table]->field_array as $fieldname=>$datatypename ) {
          if (!($table == 'entries') && isset($req->params[$obj][$fieldname]))
            $fields[$table][$fieldname] = $datatypename;
        }
        foreach ( $db->models[$table]->field_array as $fieldname=>$datatypename ) {
          if (!($table == 'entries') && isset($_FILES[$obj]['name'][$fieldname])){
            $fields[$table][$fieldname] = $datatypename;
            $req->params[$obj][$fieldname] = $_FILES[$obj]['tmp_name'][$fieldname];
          }
        }
      }
    }
    return $fields;
  }
  
  function db_field( $field, $alias ) {
    if (!(isset($this->$alias) && isset($this->data_array[$field])))
      $this->$alias =& $this->data_array[$field];
  }
  
  function exists() {
    return ( count( $this->field_array ) > 0 );
  }

  function base( $custom_class = NULL ) {
    $db =& db_object();
    $func_args = func_get_args();
    if (count($func_args) == 1 && $custom_class == NULL )
      $custom_class = NULL;
    elseif (isset($this->custom_class))
      $custom_class = $this->custom_class;
    elseif (class_exists(classify($this->table)))
      $custom_class = classify($this->table);

    if ($custom_class) {
      if (class_exists($custom_class)) {
        $this->custom_class = $custom_class;
        $obj = new $custom_class( $this );
        if (!$obj) trigger_error("error instantiating $custom_class", E_USER_ERROR );
        $obj->Record( $this->table, $db );
        return $obj;
      } else {
        trigger_error("error, class $custom_class not found", E_USER_ERROR ); 
      }
    }
    $this->custom_class = NULL;
    return new Record($this->table,$db);
  }
  
  function register( $custom_class ) {
    if (class_exists($custom_class)) {
      $this->custom_class = $custom_class;
      // apply the data model customizations from the custom_class
      $obj = new $custom_class( $this );
    }
  }
  
  function class_init() {
    if (class_exists($this->custom_class)) {
      $custom_class = $this->custom_class;
      // apply the data model customizations from the custom_class
      $obj = new $custom_class( $this );
      if (method_exists( $obj, 'init' ))
        $obj->init($this);
    }
  }
  
  function set_field( $field, $data_type, $arr=NULL ) {
    $this->field_array[$field] = $data_type;
    if (!($arr == NULL))
      $this->field_attributes( $field, $arr );
  }
  
  function set_uri_key( $field ) {
    $this->uri_key = $field;
  }
  
  function set_primary_key( $field ) {
    $this->primary_key = $field;
    if (!$this->uri_key)
      $this->uri_key = $field;
  }
  
  function field_attributes( $field, $arr ) {
    $this->set_attribute( $arr, $field );
  }
  
  function let_access( $fields ) {
    $this->let_read( $fields );
    $this->let_write( $fields );
    $this->let_create( $fields );
    $this->let_delete( $fields );
    $this->let_superuser( $fields );
  }
  
  function let_read( $fields ) {
    $args = explode( " ", $fields );
    if (!(count($args)>0)) trigger_error( "invalid data model access rule", E_USER_ERROR );
    foreach ( $args as $str) {
      $pair = split( ":", $str );
      if (!(count($pair)==2)) trigger_error( "invalid data model access rule", E_USER_ERROR );
      if ($pair[0] == 'all') {
        foreach ( $this->field_array as $field => $data_type ) {
          $this->access_list['read'][$field][] = $pair[1];
        }
      } else {
        $this->access_list['read'][$pair[0]][] = $pair[1];
      }
    }
  }
  
  function let_write( $fields ) {
    $args = explode( " ", $fields );
    if (!(count($args)>0)) trigger_error( "invalid data model access rule", E_USER_ERROR );
    foreach ( $args as $str) {
      $pair = split( ":", $str );
      if (!(count($pair)==2)) trigger_error( "invalid data model access rule", E_USER_ERROR );
      if ($pair[0] == 'all') {
        foreach ( $this->field_array as $field => $data_type ) {
          $this->access_list['write'][$field][] = $pair[1];
        }
      } else {
        $this->access_list['write'][$pair[0]][] = $pair[1];
      }
    }
  }
  
  function let_create( $fields ) {
    $args = explode( " ", $fields );
    if (!(count($args)>0)) trigger_error( "invalid data model access rule", E_USER_ERROR );
    foreach ( $args as $str) {
      $pair = split( ":", $str );
      if (!(count($pair)==2)) trigger_error( "invalid data model access rule", E_USER_ERROR );
      $this->access_list['create'][$this->table][] = $pair[1];
    }
  }

  function let_post( $fields ) {
    $this->let_create( $fields );
  }
  
  function let_modify( $fields ) {
    $this->let_write( $fields );
  }
  
  function let_put( $fields ) {
    $this->let_write( $fields );
  }
  
  function let_delete( $fields ) {
    $args = explode( " ", $fields );
    if (!(count($args)>0)) trigger_error( "invalid data model access rule", E_USER_ERROR );
    foreach ( $args as $str) {
      $pair = split( ":", $str );
      if (!(count($pair)==2)) trigger_error( "invalid data model access rule", E_USER_ERROR );
      $this->access_list['delete'][$this->table][] = $pair[1];
    }
  }

  function let_superuser( $fields ) {
    $args = explode( " ", $fields );
    if (!(count($args)>0)) trigger_error( "invalid data model access rule", E_USER_ERROR );
    foreach ( $args as $str) {
      $pair = split( ":", $str );
      if (!(count($pair)==2)) trigger_error( "invalid data model access rule", E_USER_ERROR );
        $this->access_list['superuser'][$this->table][] = $pair[1];
    }
  }

  function set_action( $method ) {
    $this->allowed_methods[] = $method;
  }
    
  function set_param( $param, $value ) {
    $this->params[$param] = $value;
  }
  
  function is_allowed( $method ) {
    if ( is_callable( array( $this->custom_class, $method ) ) ) {
      $obj = $this->base( $this->custom_class );
      if ($obj)
        return in_array( $method, $obj->allowed_methods, true );
    }
    return in_array( $method, $this->allowed_methods, true );
    return false;
  }

  function has_and_belongs_to_many( $relstring ) {
    $this->set_relation( $relstring, 'child-many' );
  }

  function belongs_to( $relstring ) {
    $this->set_relation( $relstring, 'child-one' );
  }
  
  function has_many( $relstring ) {
    $this->set_relation( $relstring, 'parent-many' );
  }
  
  function has_one( $relstring ) {
    $this->set_relation( $relstring, 'parent-one' );
  }
  
  function validates_presence_of( $field ) {
    $this->set_attribute( 'required', $field );
  }
  
  function validates_uniqueness_of( $field ) {
    $this->set_attribute( 'unique', $field );
  }
  
  function set_attribute( $attr, $field ) {
    if (is_array($attr))
      $this->field_attrs[$field]['values'] = $attr;
    else
      $this->field_attrs[$field][$attr] = true;
  }
  
  function set_hidden() {
    $this->hidden = true;
  }
  
  function foreign_key_for( $table ) {
    $db =& db_object();
    if (!(isset($db->models[$table]))) {
      $fields = $db->get_fields( $table );
      if (isset($fields[$table."_primary_key"]))
        $pk = $fields[$table."_primary_key"];
      else
        $pk = 'id';
    } else {
      $fields =& $db->models[$table]->field_array;
      $pk = $db->models[$table]->primary_key;
    }
    if (array_key_exists(
        strtolower(classify($table))."_".$pk, $this->field_array )) {
      return $pk; 
    } elseif ( array_key_exists(
        strtolower(classify($this->table))."_".$this->primary_key, $fields )) {
      return strtolower(classify($this->table))."_".$this->primary_key;
    } else {
      return $pk;
    }
  }
  
  function join_table_for( $t1, $t2 ) {
    if ($t1 < $t2)
      return $t1 . "_" . $t2;
    else
      return $t2 . "_" . $t1;
  }
  
  function set_relation( $relstring, $type ) {
    $db =& db_object();
    $f = split(":",$relstring);
    if (count($f)==2) {
      $k = $f[0];
      $fk = $f[1];
    } else {
      if ( array_key_exists( $relstring.'_id', $this->field_array ))
        $k = $relstring.'_id';
      else
        $k = $this->primary_key;
      $fk = $relstring;
    }
    $fo = split("\.",$fk);
    if (count($fo)==2) {
      $table = tableize($fo[0]);
      $fkk = $fo[1];
    } else {
      $table = tableize($fk);
      $fk = $table.".".$this->foreign_key_for( $table );
    }
    if ($type == 'child-many') {
      if (!$db->table_exists($this->join_table_for($table, $this->table))) {
        $join =& $db->get_table($this->join_table_for($table, $this->table));
        if (!($join->exists)) {
          $join->int_field( strtolower(classify($this->table))."_".$k );
          $join->int_field( strtolower(classify($table))."_".$this->foreign_key_for( $table) );
          $join->save();
        }
      }
    }
    $this->relations[$table]['type'] = $type;
    $this->relations[$table]['col'] = $k;
    $this->relations[$table]['fkey'] = $fk;
    $this->relations[$table]['tab'] = $table;
  }
  
  function can_write_fields( $fields ) {
    $return = false;
    foreach( $fields as $key=>$val ) {
      if ( $this->can_write( $key ) ) {
        $return = true;
      } else {
        return false;
      }
    }
    return $return;
  }
  
  function can_read_fields( $fields ) {
    $return = false;
    foreach( $fields as $key=>$val ) {
      if ( $this->can_read( $key ) ) {
        $return = true;
      } else {
        return false;
      }
    }
    return $return;
  }
  
  function can_read( $resource ) {
    if (!(isset($this->access_list['read'][$resource]))) return false;
    foreach ( $this->access_list['read'][$resource] as $callback ) {
      if ( function_exists( $callback ) ) {
        if ($callback())
          return true;
      } else {
        if ( member_of( $callback ))
          return true;
      }
    }
    return false;
  }
  
  function can_write( $resource ) {
    if (!(isset($this->access_list['write'][$resource]))) return false;
    foreach ( $this->access_list['write'][$resource] as $callback ) {
      if ( function_exists( $callback ) ) {
        if ($callback())
          return true;
      } else {
        if ( member_of( $callback ))
          return true;
      }
    }
    return false;
  }

  function can_create( $resource ) {
    if (!(isset($this->access_list['create'][$resource]))) return false;
    foreach ( $this->access_list['create'][$resource] as $callback ) {
      if ( function_exists( $callback ) ) {
        if ($callback())
          return true;
      } else {
        if ( member_of( $callback ))
          return true;
      }
    }
    return false;
  }

  function can_delete( $resource ) {
    if (!(isset($this->access_list['delete'][$resource]))) return false;
    foreach ( $this->access_list['delete'][$resource] as $callback ) {
      if ( function_exists( $callback ) ) {
        if ($callback())
          return true;
      } else {
        if ( member_of( $callback ))
          return true;
      }
    }
    return false;
  }

  function can_superuser( $resource ) {
    if (!(isset($this->access_list['superuser'][$resource]))) return false;
    foreach ( $this->access_list['superuser'][$resource] as $callback ) {
      if ( function_exists( $callback ) ) {
        if ($callback())
          return true;
      } else {
        if ( member_of( $callback ))
          return true;
      }
    }
    return false;
  }

  function rewind() {
    $this->MoveFirst();
  }

  function set_routes($table) {
    $req =& request_object();
    if (!(isset($req->activeroute)))
      return;
    $req->connect(
      $table,
      ':resource',
      array(
        'requirements' => array ( '[a-z]+' ),
        'resource' => $table
      )
    );
    $req->connect(
      classify($table),
      ':resource/:id',
      array(
        'requirements' => array ( '[a-z]+', '[0-9]+' ),
        'resource' => $table,
        'id' => $req->id
      )
    );
  }
  
  function save() {
    
    $db =& db_object();
    trigger_before( 'save', $this, $db );
    
    if (!(isset($db->tables)))
      $db->tables = $db->get_tables();

    if ( !( in_array( $this->table, $db->tables ) ) ) {
      if (count($this->field_array)>0) {
        if (!(isset($this->primary_key)))
          $this->auto_field( 'id' );
        $db->add_table( $this->table, $this->field_array );
        $this->class_init();
      } else {
        return NULL;
      }
    }
    
    // schema sync
    
    #$fields = $db->get_fields( $this->table );
    #foreach ( $this->field_array as $field => $data_type ) {
    #  if ( !( array_key_exists( $field, $fields ) ) ) {
    #    $db->add_field( $this->table, $field, $data_type );
    #  }
    #}
    #if ( !( isset( $this->primary_key ))) {
    # if (isset($fields[$this->table."_primary_key"]))
    #    $this->set_primary_key( $fields[$this->table."_primary_key"] );
    #}
    #foreach ( $fields as $field => $type ) {
    #  if ( !( array_key_exists( $field, $this->field_array ) ) ) {
    #    if ( !( $this->table."_primary_key" == $field ) ) {
    #      $this->set_field( $field, $type );
    #    }
    #  }
    #}

    if ( !( isset( $this->primary_key )) && $this->table != 'db_sessions')
      trigger_error("The ".$this->table." table must have a primary key. Example: ".$this->table."->set_primary_key('field')"hide@address.com($this->conn), E_USER_ERROR );
    $this->exists = true;
    $this->set_routes( $this->table );
    trigger_after( 'save', $this, $db );
  }
  
  function is_blob($field) {
    $db =& db_object();
    return ( $db->get_mapped_datatype( $this->field_array[$field] ) === 'blob' );
  }
  
  function find( $id=NULL, $find_by=NULL ) {
    $db =& db_object();
    trigger_before( 'find', $this, $db );
    $db->recordsets[$this->table] = $db->get_recordset($this->get_query($id, $find_by));
    $rs =& $db->recordsets[$this->table];
    if (!$rs) return false;
    if ( $id != NULL && $rs->rowcount > 0 )
      if ( $find_by != NULL )
        return $rs->Load( $this->table, 0 );
      else
        return $rs->Load( $this->table, $rs->rowmap[$this->table][$id] );
    trigger_after( 'find', $this, $db );
    return false;
  }
  
  function find_by( $col, $val ) {
    return $this->find( $val, $col );
  }
  
  function MoveFirst() {
    $db =& db_object();
    if (!(isset($db->recordsets[$this->table])))
      $this->find();
    $rs =& $db->recordsets[$this->table];
    if (!$rs) return false;
    return $rs->MoveFirst( $this->table );
  }
  
  function MoveNext() {
    $db =& db_object();
    if (!(isset($db->recordsets[$this->table])))
      $this->find();
    $rs =& $db->recordsets[$this->table];
    if (!$rs) return false;
    return $rs->MoveNext( $this->table );
  }
  
  function session_exists() {
    if (isset($_SESSION[$this->table."_submission"]))
      return true;
    return false;
  }
  
  function set_limit( $limit ) {
    if ( $limit > 0 ) $this->limit = $limit;
  }
  
  function set_offset( $offset ) {
    if ( $offset > 0 ) $this->offset = $offset;
  }
  
  function set_orderby( $col ) {
    if ( strlen( $col ) > 0 ) $this->orderby = $this->table . "." . $col;
  }
  
  function set_order( $order ) {
    if ( strlen( $order ) > 0 ) $this->order = $order;
  }
  
  function rowcount() {
    $db =& db_object();
    $rs =& $db->recordsets[$this->table];
    if (!$rs) return 0;
    return $rs->num_rows( $this->table );
  }
  
}

?>
Return current item: Dbscript