PHP: MySQL Abstraction and Wrappers
Happy new year to everyone, may this be the best year ever... (and every person that visit this site make a trillion euro this year)
Okay, now with all the clichés out of the way, I am going to start my blogging year out with a PHP post.(Originally I decided to abandon this post, but perhaps someone out there can find some value in this content.)
It is commonplace for PHP developers to use MySQL as their database engine; the database engine of choice for most.
PHP features a number of options/extensions in order to access MySQL, we've got the legacy mysql extension thats been around since PHP 4, the mysqli extension which was introduced with the advent of PHP 5 and the PDO extension.
Generally we need to avoid using the legacy mysql extension when we're faced with new development and rather use the new mysqli extension - since it supports the new functionality (e.g. Stored Procedures, charsets, prepared statements etc) added to MySQL over the years.
The PDO extension by contrast functions as a common abstraction that uses a driver based approach, which supports MySQL or any other db engine with a PDO driver (not going to go into too much detail).
Assuming that we decided on using the mysqli extension, returning rows of data from a query will look something like this:
<?php // Check if MySQLI Extension exists if (!function_exists('mysqli_connect')) { throw new Exception('MySQLI Extension not found!'); } // Connect to MySQL database $mysqli = @new mysqli("host", "user", "pass", "db"); if (mysqli_connect_errno()) { throw new Exception(mysqli_connect_error())); } // Sanitize values $id = (int)$_POST['id']; $title = $mysqli->real_escape_string($_POST['title']); $body = $mysqli->real_escape_string($_POST['body']); $query = "SELECT * FROM something WHERE title = '$title' AND body LIKE '%$body%' AND id = $id"; // Execute Query $result = $mysqli->query($query); // Throw exception if something goes wrong with the query // supposed to be default behavior in my opinion if ($mysqli->error) { throw new Exception($mysqli->error); } // Render rows if (is_object($result)) { echo '<table>'; while ($row = $result->fetch_assoc()) { echo '<tr>'. '<td>'.$row['id'].'</td>'. '<td>'.$row['title'].'</td>'. '<td>'.$row['body'].'</td>'. '</tr>'; } echo '</table>'; $result->close(); } // Close connection - apparently not necessary according to PHP documentation $mysqli->close(); ?>
The preceding snippet is a rather horrible snippet if we use it as is.
- from a reusability perspective
-
The preceding snippet represents a common task when working with mysql e.g.
returning rows of data - rather place this functionality in a common place, like
a class/function.
-
Connection string hardcoded, this needs to be in some kind of configuration file
somewhere.
- Entangled SQL/HTML/PHP can lead to some serious unmanageable code (spaghetti code).
- from a coupling perspective
-
Being solely dependent on the mysqli extension doesn't necessarily make sense, since there is a number of extensions available for Mysql.
- Dependent on Mysql - PHP supports quite a number of database engines, why restrict your script?
For this purpose (among others) I created a wrapper class that contains some of the basic functionality you'll need in order to access MySQL.
The following snippet is a rewrite of the previous snippet using the wrapper class, observe:
// Place within config file - Note this won't open an active MySQL Connection MySQL::Config('host', 'user', 'pass', 'db'); // Open MySQL Connection and Execute Query, Connection closed on object destruct // Perhaps move this to some kind of datalayer e.g. one for MySQL, one for PostgreSQL etc... $rows = MySQL::Create('SELECT * FROM something WHERE title = ?title AND body LIKE ?body AND id = ?id') ->Parameter('title', $_POST['title']) ->Parameter('body', '%'.$_POST['body'].'%') ->Parameter('id', $_POST['id'], 'integer') ->Query(); // Consider replacing with datagrid control/class if (isset($rows)) { echo '<table>'; foreach($rows as $row) { echo '<tr>'. '<td>'.$row['id'].'</td>'. '<td>'.$row['title'].'</td>'. '<td>'.$row['body'].'</td>'. '</tr>'; } echo '</table>'; }
This means less code and also makes us less dependent on the mysqli extension e.g. if mysqli gets deprecated tomorrow we simply change the underlying code within the MySQL wrapper.
Now this is where it gets a bit more interesting and the reason I initially sacked this post.
In the download that accompanies this post, you will notice a folder named "alternative", this folder contains an implementation of the strategy design pattern (won't go into too much detail in this post).
The basic idea is to create different "strategies" that represents the best possible way to access MySQL in context of what is available on the server that hosts the script.
In order to create a strategy, you need to extend the MySQLStrategy abstract class (like seen below) - which requires one to provide an implementation for the required methods.
<?php abstract class MySQLStrategy { protected $resource; protected $count; protected $host; public function __construct(MySQL $host) { $this->host = $host; } abstract public function AffectedRows(); abstract public function Count(); abstract public function Escape($value); abstract public function LastId(); abstract public function Open(); abstract public function Query(); abstract public function NonQuery(); abstract public function Scalar(); abstract public function Close(); } ?>
Within the 1.0/alternative/includes folder, you will find two implementations I already created, one represents the mysql legacy extension (MySQL_Strategy.php), the other the mysqli extension (MySQLI_Strategy.php), didn't create one for the MySQL PDO yet.
In the MySQL class constructor (MySQL.php) we determine the "context" in order of which extension is best and available, observe:
private function __construct(array $settings) { $this->settings = $settings; if (function_exists('mysqli_connect')) { $this->_strategy = new MySQLI_Strategy($this); } else if (function_exists('mysql_connect')) { $this->_strategy = new MySQL_Strategy($this); } else { throw new Exception("No compatible MySQL strategy found"); } }
I feel this approach (strategies) might be a bit of an overkill (comes with performance penalties), especially if we ask the question if we'll ever be in the position (when working with PHP 5) where the mysqli extension won't be available?
Even if it isn't available (when using a wrapper), we can easily migrate by changing the underlying code (like seen in the download) of the wrapper to another extension either way - without needing the additional overhead.
But hey, whats your opinion?
Additional Reading:
http://forge.mysql.com/wiki/Which_PHP_Driver_for_MySQL_should_I_use
MySQL Wrapper Class
Posted by - Christoff Truter
Date - 2011-01-17 22:56:58
Comments - 0
Date - 2011-01-17 22:56:58
Comments - 0
C# - IIS: List Websites
A while ago the company I work for started migrating some of their client websites, to a new (more robust) server.
What we needed (to save time etc) was the ability to retrieve/perform certain IIS actions programmatically using C#.
Now there is numerous ways to achieve this, one method we're going to look at involves ADSI (Active Directory Service Interfaces) - you will find a few seriously handy classes under the System.DirectoryServices namespace.
These classes provide us with a simple way to access Active Directory service providers e.g. LDAP, NDS, WinNT and IIS.
In this post (as the title suggests) the aim is to retrieve a list of websites on an IIS server, along with their physical paths and site status etc.
The idea is to retrieve the data displayed in the following rows (see image below) - visible in our IIS Management tool:
Firstly create some class for storing the retrieved details from IIS, like this:
public class Website { public Int32 Identity { get; set; } public String Name { get; set; } public String PhysicalPath { get; set; } public ServerState Status { get; set; } }
We're going to create an enum that lists possible states the websites can/might be in.
public enum ServerState { Starting = 1, Started = 2, Stopping = 3, Stopped = 4, Pausing = 5, Paused = 6, Continuing = 7 }
Next we need to connect to the IIS WWW publishing service (W3SVC) using the DirectoryEntry class.
public static void Main() { foreach (Website site in GetSites("IIS://localhost/W3SVC")) { Console.WriteLine(String.Concat ( site.Name, " , ", site.Identity, " , ", site.Status, " , ", site.PhysicalPath )); } } static IEnumerable<Website> GetSites(String Path) { DirectoryEntry IIsEntities = new DirectoryEntry(Path); foreach (DirectoryEntry IIsEntity in IIsEntities.Children) { if (IIsEntity.SchemaClassName == "IIsWebServer") { yield return new Website ( Convert.ToInt32(IIsEntity.Name), IIsEntity.Properties["ServerComment"].Value.ToString(), GetPath(IIsEntity), (ServerState)IIsEntity.Properties["ServerState"].Value ); } } } static String GetPath(DirectoryEntry IIsWebServer) { foreach (DirectoryEntry IIsEntity in IIsWebServer.Children) { if (IIsEntity.SchemaClassName == "IIsWebVirtualDir") return IIsEntity.Properties["Path"].Value.ToString(); } return null; } // Alternatively we can rewrite the preceding snippet using a linq statement /*static IEnumerable<Website> GetSites(String Path) { DirectoryEntry IIsEntities = new DirectoryEntry(Path); return (from s in IIsEntities.Children.OfType<DirectoryEntry>() where s.SchemaClassName == "IIsWebServer" select new Website { Identity = Convert.ToInt32(s.Name), Name = s.Properties["ServerComment"].Value.ToString(), PhysicalPath = (from p in s.Children.OfType<DirectoryEntry>() where p.SchemaClassName == "IIsWebVirtualDir" select p.Properties["Path"].Value.ToString()).Single(), Status = (ServerState)s.Properties["ServerState"].Value }); }*/
Once you run the snippet (if you've got the appropriate rights), you will notice something like the following window (if all goes according to plan)
Chances are (especially if you're using IIS 7) that you got an exception along the lines of "Unhandled Exception: System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)".
Which means its likely that you don't have the IIS 6 Management Compatibility installed (programs and features).
But surely there must be an IIS 7 alternative - why would we need to install compatibility tools etc for IIS 6 in order to manage IIS 7?
Microsoft did indeed provide an IIS 7 alternative, you'll need to include a reference to the Microsoft.Web.Administration assembly which is located within your inetsrv folder under system32.
Using the following snippet we're able to retrieve the websites via the ServerManager class.
ServerManager IIS = new ServerManager(); foreach (Site site in IIS.Sites) { Console.WriteLine(String.Concat ( site.Name, " , ", site.Id, " , ", site.State, " , ", site.Applications["/"].VirtualDirectories[0].PhysicalPath )); }
And thats it, this should give you a basic starting point, if you're looking at other alternatives WMI seems to be another option worth looking into - which also seems to be an IIS 6.0 based solution.
Posted by - Christoff Truter
Date - 2010-12-16 11:45:45
Comments - 0
Date - 2010-12-16 11:45:45
Comments - 0
First 6 7 8 9 10 11 12 13 14 15 Last / 62 Pages (124 Entries)