0

So I'm following a tutorial on Lynda.com regarding advanced PHP. They mention Late Static Bindings but I guess PHP 5.3 wasn't out when they made the tutorial so I'm trying to figure out how to do CRUD using a 'create' function in an extended class and pulling the attributes from the called class into the function. I feel like I'm missing something very simple, it insert a new row into my database, just no content. Heres the code, any suggestions would be useful.

PHP code in test.php file...

<?php
$user = new User();
$user->username = "johnsmith";
$user->password = "555";
$user->first_name = "john";
$user->last_name = "smith";
$user->create();
?>

User class that extends DatabaseObject class...

class User extends DatabaseObject {


    protected static $table_name="users";
    protected static $db_fields = array('id', 'username', 'password', 'first_name', 'last_name');
    public $id;
    public $username;
    public $password;
    public $first_name;
    public $last_name;
 }

DatabaseObject class with late static bindings...

class DatabaseObject {

protected static function attributes() {
 $attributes = array();
 foreach(static::$db_fields as $field) {

    if(property_exists(get_called_class(), $field)) {
    $attributes[$field] = get_called_class()->$field;
    }
      }
      return $attributes;
          }

protected static function sanitized_attributes() {
 global $database;
     $clean_attributes = array();
 foreach(self::attributes() as $key => $value) {
    $clean_attributes[$key] = $database->escape_value($value);
      }
      return $clean_attributes;
      }


public static function create() { 
 global $database;

  $attributes = self::sanitized_attributes();
  $sql = "INSERT INTO ".static::$table_name." (";
  $sql .= join(",", array_keys($attributes));
  $sql .=") VALUES ('";
  $sql .= join("', '", array_values($attributes));
  $sql .= "')";

  if($database->query($sql)) { 
   static::$id = $database->insert_id();// get last id saved in database and puts it in the id arguement
   return true;
  } else {
    return false; 
  }

}
1

2 Answers 2

1

The instance method can access its properies, the class method (static method) can not. So all the methods in DatabaseObject should be instance methods. For instance,

class DatabaseObject {
  protected function attributes() {
    $attributes = array();
    foreach(static::$db_fields as $field) {
      if(property_exists(get_called_class(), $field)) {
        $attributes[$field] = $this->$field;
      }
    }
    return $attributes;
  }
  protected function sanitized_attributes() {
    global $database;
    $clean_attributes = array();
    foreach(self::attributes() as $key => $value) {
      $clean_attributes[$key] = $database->escape_value($value);
    }
    return $clean_attributes;
  }
  public function create() {
    global $database;
    $attributes = self::sanitized_attributes();
    $sql = "INSERT INTO ".static::$table_name." (";
    $sql .= join(",", array_keys($attributes));
    $sql .=") VALUES ('";
    $sql .= join("', '", array_values($attributes));
    $sql .= "')";
    if($database->query($sql)) {
      $this->id = $database->insert_id();// get last id saved in database and puts it in the id arguement
      return true;
    } else {
      return false;
    }
  } 
}

Late static bindins are appropriate for only $table_name and $db_fields.

Sign up to request clarification or add additional context in comments.

Comments

1

What you should do is set the static field $db_fields to the DatabaseObject if you want to continue to use this structure. Otherwise, change the type of the DatabaseObject to abstract, have an empty array for the database fieldset and set it to protected. As part of the __construct function for the implementation of DatabaseObject (User), have setFields->array(....) to set the mapped fields. Alternatively, you can allow direct property call like $user->foo, and use __get && __set magic methods and pre-define all allowable fields, rather than use a mapping array.

Here's the DatabaseObject class with printing the SQL:

class DatabaseObject {
    protected static function attributes() {
        $attributes = array();
            foreach(static::$db_fields as $field) {
                if(property_exists(get_called_class(), $field)) {
                    $attributes[$field] = get_called_class()->$field;
                }
            }
        return $attributes;
    }

    protected static function sanitized_attributes() {
        $clean_attributes = array();
        foreach(self::attributes() as $key => $value) {
            $clean_attributes[$key] = $value;
        }
        return $clean_attributes;
    }

    public static function create() { 
        $attributes = self::sanitized_attributes();
        $sql = "INSERT INTO ".static::$table_name." (";
        $sql .= join(",", array_keys($attributes));
        $sql .=") VALUES ('";
        $sql .= join("', '", array_values($attributes));
        $sql .= "')";

        print $sql;
    }
}

Now if I run this as per your example I get the following output:

php > $user->create();
PHP Notice:  Trying to get property of non-object in php shell code on line 6
PHP Notice:  Trying to get property of non-object in php shell code on line 6
PHP Notice:  Trying to get property of non-object in php shell code on line 6
PHP Notice:  Trying to get property of non-object in php shell code on line 6
PHP Notice:  Trying to get property of non-object in php shell code on line 6
INSERT INTO users (id,username,password,first_name,last_name) VALUES ('', '', '', '', '')

This is because the code doesn't know about the static object because it hasn't been set yet. You should probably consider using dynamic binding rather than static binding for better memory management and generally better performance. Static binding is useful in factories and registries where you need to maintain state whilst providing direct access to instances without instantiating the registry class (eg. $r = new Registry();$t = $r->get('foo'));

See the wiki article http://en.wikipedia.org/wiki/Name_binding

The other problem that you have is that get_called_class() will return the name of the class rather than the instance. By changing DatabaseObject to abstract and User implements DatabaseObject, you can modify the access of your functions and change the static methods to non-static, as you should.

Ideally when you're using methods they should be non-static so that with standard OOP programming practices, A extends B, $b is B, $b->foo() calls A::foo() where foo() is a non-static method of A.

Is there any reason you're using static binding in database access?

Edit --

Also want to say, you shouldn't mix static with non-static code, it just doesn't work, not in PHP and certainly not in any OOP code. If you need to use static binding you need to either

  • Keep a registry of items and pass some sort of reference to the database object you're working with
  • Keep a hash map of key => value sets
  • Pass the actual non-static object to the processing methods A::process($b) so that A::process() calls $b->$field.

5 Comments

Well I'm following a Lynda.com tutorial. Basically this is the just the create part of the CRUD they're setting up. They have it so each object (User is for logging in, creating a new admin user/password, and there going to be another one Photograph, for uploading images to a directory) includes each one of these functions because when they made the tutorial PHP 5.3 hadn't come out yet. Basically I want to create a Create, Read, Update, and Delete method that I can easy us in various objects by just extending the object that the CRUD methods are in.
Then all I would have to do is create the assignments in the new object and assign them variable like above, then call the create() or read(), or update() method to interact with the database. I'm still trying to learn PHP and OOP so bear with me. Thanks for the response, very informative!!
The best example to see is what Zend/Symfony does, but if you're still learning CRUD, it's a bit advanced. Ideally with CRUD, you have a database layer with database objects that give you the basic CRUD commands, $obj->create(), $obj->read(), $obj->update(), $obj->delete(). You can either have User extend your CRUD object ($a = new User(); $a->update()), or inject your User object into a CRUD service (eg. $a = new Entity($user); $a->create();). Either implementation works, just depends on what you need.
Here's the service example, it's in c# but you should be able to follow: msdn.microsoft.com/en-us/library/gg328416.aspx
Gotcha, yeah the above #c example was a little too advanced for me but I appreciate the great feedback!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.