{"id":346,"date":"2010-08-01T14:02:39","date_gmt":"2010-08-01T14:02:39","guid":{"rendered":"http:\/\/www.nickbennett.co.uk\/?p=346"},"modified":"2025-06-11T16:05:34","modified_gmt":"2025-06-11T16:05:34","slug":"ror-active-records-in-php","status":"publish","type":"post","link":"https:\/\/blog.nickbennett.co.uk\/index.php\/2010\/08\/01\/ror-active-records-in-php\/","title":{"rendered":"RoR Active Records in PHP"},"content":{"rendered":"<p>As a developer, you can become a little complacent with the framework and language you&#8217;re using. You&#8217;ve invested a lot of time in getting yourself up and running so why look at other solutions to problems you&#8217;ve already solved? Well, there is more than 1 way to skin a cat, and the other methods out there may mean you can skin 10 cats for every 1 of yours. OK, I&#8217;m going to stop with this horrible analogy. This post has come about because I was inspired by the Active Record methodology involved in Ruby on Rails and I wanted to create a PHP version.<\/p>\n<p>Let&#8217;s start with a few examples of <a title=\"See Ruby on Rails API for more details\" href=\"http:\/\/api.rubyonrails.org\/classes\/ActiveRecord\/Base.html\" target=\"_blank\" rel=\"noopener\">active records<\/a> in <a title=\"Ruby on Rails\" href=\"http:\/\/rubyonrails.org\/\" target=\"_blank\" rel=\"noopener\">RoR<\/a>.<\/p>\n<pre>\n<code>\nuser = User.find_user_by_email('myemail@gmail.com');\n\nuser.name; # Me\nuser.age; # Thirty (Just)\n\nuser.new(:name =&gt; \"David\", :surname =&gt; \"Hasselhoff\" : occupation =&gt; \"Life Guard\");\n<\/code>\n<\/pre>\n<p>If you&#8217;re not familiar with RoR you will probably be impressed with how easy it is to create new records and generate the setters and getters. If you are familiar with RoR please excuse any syntax errors as I don&#8217;t actually write in Ruby.<\/p>\n<p>My current framework is a custom setup very similar to the <a title=\"CodeIgniter\" href=\"http:\/\/codeigniter.com\/\" target=\"_blank\" rel=\"noopener\">CodeIgniter<\/a> framework. All the data access is done via 2 files, the <strong>data access object,<\/strong> and the <strong>access object<\/strong>. I have a script which is an automatic code generator that can create both of these files using the database and table names. The <strong>data access object<\/strong> is the parent of the <strong>access object<\/strong>, this allows any custom queries to be managed by the <strong>access object<\/strong> and direct table access to be carried out by the <strong>data access object<\/strong>. Therefore for each table in the database, we need both of these files, this can start to add up fairly quickly. Just to highlight this setup&#8230;<\/p>\n<p><strong>Data Access Object<\/strong><\/p>\n<pre>\n<code>\n&lt;?php\n\nclass DAO_User {\n\n \/\/ Private properties\n protected $db;\n protected $db_name\u00a0= \"my_database\";\n protected $table_name = \"user\";\n\n \/\/ Table specific\n protected $first_name;\n protected $last_name;\n\n \/*\n * Constructor\n *\/\n public function __construct () {\n     $this-&gt;db = new Database();\n }\n\n \/*\n * Getters\n *\/\n public function getFirstName(){\n    return $this-&gt;first_name;\n }\n\n public function getLastName(){\n    return $this-&gt;last_name;\n }\n\n \/*\n * Setters\n *\/\n public function setFirstName($val=''){\n    $this-&gt;first_name = $val;\n }\n public function setLastName($val=''){\n    $this-&gt;last_name = $val;\n }\n\n \/*\n * Processes\n *\/\n public function createRow () {\n\n # Insert code\n\n }\n\n public function updateRow () {\n\n # Update code\n\n }\n\n public function deleteRow() {\n\n # Delete code\n\n }\n\n}\n\n?&gt;\n<\/code>\n<\/pre>\n<p><strong>Access Object<\/strong><\/p>\n<pre>\n<code>\n&lt;?php\n\nrequire_once('class.DAO.my_database.user.php');\n\nclass AO_User extends DAO_User {\n\n \/*\n * Return a list of values\n *\/\n public function findUserByName ($name=\"dave\") {\n\n # Query code here\n\n }\n\n}\n\n?&gt;\n<\/code>\n<\/pre>\n<p>These classes are used by the <strong>Models<\/strong> for the framework to interact directly with the database. This is a very clean way of dealing with access to the database but the number of files can get quite large and the code in the model is quite clunky. Issues can also arise when the database is changed or when variables aren&#8217;t passed for certain fields (<em>Do we set it to blank or keep the existing value for this field?<\/em>). I decided to try and create a new PHP class based on the RoR active records approach.<\/p>\n<p>A strange approach to some maybe, but I decided to write the code to use the class first and then tried to get the class functioning around it. Here is the code&#8230;<\/p>\n<pre>\n<code>\n&lt;?php\n\nclass data_access_test {\n\n public function index() {\n  $ao = new DataAccess('my_database','user');\n  print $ao-&gt;find('1')-&gt;first_name(); # Expected output dave\n }\n\n}\n\n?&gt;\n<\/code>\n<\/pre>\n<p>OK, so in case you are not clear on what we are saying here. We start by initialising the class with 2 variables. The first is the name of the database and the second is the table we wish to access. We then call the <strong>find<\/strong> method and pass a specific id of a row in the table. By chaining the methods we are also able to call the <strong>first_name<\/strong> method which without a variable assumes we wish to get (if we passed a variable it would assume we wish to set). OK, so now it&#8217;s time to get started on the class itself.<\/p>\n<pre>\n<code>\nprivate $_dbl; # Your database class to actually talk to the DB\nprivate $_database; # User defined var for the DB we need to look at\nprivate $_table; # User defined var for the table we need to look at\nprivate $_internal_errors = array(); # Store any errors in setting up object\nprivate $_primary_key_fields = array(); # Primary key holder for $database.$table\nprivate $_set_primary_key = false; # False by default, if 2 fields create key then we set to true\nprivate $_fields; # Holder of the available fields in our table\nprivate $_type; # As above, see desc query on your table for example of values\nprivate $_null; # See Above\nprivate $_key; # See Above\nprivate $_default; # See Above\nprivate $_extra; # See Above\nprivate $_row_data; # Holder of all of this row's data\n<\/code>\n<\/pre>\n<p>To start with we define all of the variables that our class will need to function correctly. Read the comments for more detail on what each variable will be used for. Now, for the nitty-gritty of the object which will happen at the point of initialisation.<\/p>\n<pre>\n<code>\n\/*\n * Set globals to define host user and password\n *\/\npublic function __construct($database = '', $table = '') {\n  $this->dbl = new Database;\n\n  if ($database != '' && $table != '') {\n    $this->_database = $database;\n    $this->_table = $table;\n\n    if (!$this->setTableData()) {\n      $this->_internal_errors[] = 'Database and table not recognised';\n    }\n  } else {\n    $this->_internal_errors[] = 'Require database and table at initialisation';\n  }\n}\n\n\/*\n * General\n *\/\nprivate function setTableData() {\n  $sql = \"SHOW COLUMNS FROM \" . $this->dbl->mysqlEscape($this->_database) . \".\" . $this->dbl->mysqlEscape($this->_table);\n\n  list(\n    $this->_fields,\n    $this->_type,\n    $this->_null,\n    $this->_key,\n    $this->_default,\n    $this->_extra\n  ) = $this->dbl->returnRows($sql);\n\n  if ($this->_fields) {\n    $primary_count = 0;\n\n    \/\/ Set the primary keys\n    foreach ($this->_fields as $key => $value) {\n      if ($this->_key[$key] == 'PRI') {\n        $this->_primary_key_fields[] = $value;\n        $primary_count++;\n      }\n    }\n\n    if ($primary_count > 2 || !in_array('auto_increment', $this->_extra)) {\n      $this->_set_primary_key = true;\n    }\n\n    return 1;\n  }\n}\n<\/code>\n<\/pre>\n<p>The construct of the class sets up our database object so that we can talk to the database. We check that the user has passed the 2 required variables, if they haven&#8217;t we assign an error message to our internal errors variable. I find it useful to have an internal errors variable that way if there are any issues you can simply check this variable to diagnose the problem. If we have both the <strong>database<\/strong> and <strong>table<\/strong> we can go about setting up our object, this is all done in the <strong>setTableData<\/strong> method.<\/p>\n<p>This method begins by doing a DESC query on the table. We use the return variables to set our <strong>fields<\/strong>, <strong>type<\/strong>, <strong>null<\/strong>, <strong>key<\/strong>, <strong>default,<\/strong> and <strong>extra<\/strong> properties. The final thing this method does is check the primary keys. I had a number of problems with my original DAO files when I tried to generate the files for a table with multiple primary keys. By setting our <strong>primary_key_fields<\/strong> variable we can make a decision as to whether to allow the user to set and get this variable. The decision I&#8217;ve made is you cannot set the primary field if there is only 1 primary key. If there are more primary keys then I set my <strong>set_primary_key<\/strong> flag to true to indicate that we can allow this field setting.<\/p>\n<p>So how do we set and get variables on the fly when we don&#8217;t have the methods already set? Well, we can do this by using the <strong>__call<\/strong> method.<\/p>\n<pre>\n<code>\n\/*\n * Use name to check what field we are setting or getting\n *\/\npublic function __call($name = '', $arguments = '') {\n  \/\/ Only proceed if there are no internal errors\n  if (count($this->_internal_errors) == 0) {\n    \/\/ Check that this is a valid field\n    if (in_array($name, $this->_fields)) {\n      \/\/ We have an argument so set, otherwise get\n      if (!empty($arguments[0])) {\n        if (!in_array($name, $this->_primary_key_fields) || $this->_set_primary_key === true) {\n          if ($this->checkFieldDateType($name, $arguments[0]) == 1) {\n            $this->setData($name, $arguments[0]);\n          } else {\n            $this->_internal_errors[] = $name . ' cannot be set as it fails the type check';\n          }\n        } else {\n          $this->_internal_errors[] = $name . ' cannot be set as it\\'s a primary key';\n        }\n      } else {\n        return $this->getData($name);\n      }\n    } else {\n      $this->_internal_errors[] = $name . ' is not a recognised field in this table';\n    }\n  }\n}\n<\/code>\n<\/pre>\n<p>I&#8217;ll now talk you through this method and then show you the <strong>get<\/strong> and <strong>set<\/strong> methods. The first thing this method does is to check the <strong>internal_errors<\/strong> property, we don&#8217;t want to try and set something if we&#8217;ve had a problem setting up the object. We then check to see if an argument has been passed, if it has then we can assume the user wants to set a variable. We check whether the method name is a primary key and whether we can set it. If allowed we finally check the argument against the data type set for this field (I won&#8217;t go into detail about this method but it&#8217;s a good way to ensure data integrity by ensuring it matches the type of data acceptable for that field) and then pass to our <strong>set<\/strong> method. If there are no arguments then we simply send it to the <strong>get<\/strong> method.<\/p>\n<pre>\n<code>\nprivate function getData($key = '') {\n  return $this->_row_data[$key];\n}\n\n\/*\n * Setters\n *\/\nprivate function setData($key = '', $value = '') {\n  $this->_row_data[$key] = $value;\n  return $this;\n}\n<\/code>\n<\/pre>\n<p>Fairly straightforward forward huh? Our <strong>set<\/strong> method simply uses the <strong>name<\/strong> passed to the <strong>__call<\/strong> method (aka table field) as the key for our internal variable <strong>_row_data<\/strong>. The return <strong>$this<\/strong> allows us to chain methods in this object. The <strong>get<\/strong> method is even more straightforward and just returns the value currently assigned to this key (aka table field). Ok, so if we look back at our original code we still need to define our <strong>find<\/strong> method.<\/p>\n<pre>\n<code>\n\/*\n * Find record using unique id\n *\/\npublic function find($record_id = '') {\n  if ($record_id != '' && count($this->_primary_key_fields) == 1) {\n    $sql = \"SELECT * \n        FROM \" . $this->dbl->mysqlEscape($this->_database) . \".\" . $this->dbl->mysqlEscape($this->_table) . \" \n        WHERE \" . $this->_primary_key_fields[0] . \" = '\" . $record_id . \"'\";\n\n    $results = $this->dbl->returnRows($sql, 'non_list');\n\n    foreach ($results[0] as $key => $value) {\n      if (!is_numeric($key)) {\n        $this->_row_data[$key] = $value;\n      }\n    }\n  }\n  return $this;\n}\n<\/code>\n<\/pre>\n<p>Our <strong>find<\/strong> method checks that the record id is available and that there is only 1 primary key. A query is then generated to return all of the information in that table row. The columns of the rows are used as keys in our internal <strong>_row_data<\/strong> variable and the value is the cell data. When we now call a <strong>get<\/strong> on any of the columns we will get the data we require&#8230;bingo!<\/p>\n<p>My class also has methods for creating new rows and updating rows. If you&#8217;ve come this far I&#8217;m sure you can figure these out for yourself. This class is far from perfect but it&#8217;s a good start. One of the problems you may have already noted is what if we want to set an empty value? Also what if we want to return multiple rows? If you have any ideas on it please share them with me \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As a developer, you can become a little complacent with the framework and language you&#8217;re using. You&#8217;ve invested a lot of time in getting yourself up and running so why look at other solutions to problems you&#8217;ve already solved? Well, there is more than 1 way to skin a cat, and the other methods out [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":373,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[25,27,98,109,124,309,313,326,350,353,372],"class_list":["post-346","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-php","tag-access","tag-active","tag-classes","tag-conversion","tag-data","tag-objects","tag-on","tag-php","tag-rails","tag-record","tag-ruby"],"_links":{"self":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/346","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/comments?post=346"}],"version-history":[{"count":4,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/346\/revisions"}],"predecessor-version":[{"id":1453,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/346\/revisions\/1453"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/"}],"wp:attachment":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/media?parent=346"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/categories?post=346"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/tags?post=346"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}