Skip to content

Instantly share code, notes, and snippets.

@jelmervdl
Created April 26, 2012 09:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jelmervdl/2498047 to your computer and use it in GitHub Desktop.
Save jelmervdl/2498047 to your computer and use it in GitHub Desktop.
Functional PHP classes
<?php
require_once __DIR__ . '/util.php';
class SQL_Table
{
protected $pdo;
protected $table_name;
private $fetch_mode;
public function __construct(PDO $pdo, $table_name)
{
$this->pdo = $pdo;
$this->table_name = $table_name;
}
public function setFetchMode($mode, $classname = null, array $ctorargs = null)
{
$this->fetch_mode = array($mode, $classname, $ctorargs);
}
public function select($fields = '*', array $conditions = array(), array $order_by = array())
{
$condition_clause = $this->generateAssignmentClause($conditions, 'co_');
$query = sprintf('SELECT %s FROM %s',
implode(', ', (array) $fields),
$this->table_name);
if ($condition_clause->first)
$query .= sprintf(' WHERE %s', implode(' AND ', $condition_clause->first));
if ($order_by)
{
$query .= ' ORDER BY';
foreach ($order_by as $column => $order)
$query .= sprintf(' %s %s', $column, $order);
}
$stmt = $this->pdo->prepare($query);
$stmt->execute($condition_clause->second);
if ($this->fetch_mode)
call_user_func_array(array($stmt, 'setFetchMode'), $this->fetch_mode);
return new FunctionalIterator(new PDOIterator($stmt));
}
public function update(array $values, array $conditions)
{
$assignment_clause = $this->generateAssignmentClause($values, 'as_');
$condition_clause = $this->generateAssignmentClause($conditions, 'co_');
$query = sprintf('UPDATE %s SET %s WHERE %s',
$this->table_name,
implode(', ', $assignment_clause->first),
implode(' AND ', $condition_clause->first)
);
$stmt = $this->pdo->prepare($query);
$stmt->execute(array_merge(
$assignment_clause->second,
$condition_clause->second
));
return $stmt->rowCount();
}
public function insert(array $values)
{
$query = $this->generateInsertQuery($values);
$stmt = $this->pdo->prepare($query);
$stmt->execute($values);
return $this->pdo->lastInsertId();
}
protected function generateInsertQuery(array $values)
{
$placeholders = array_map(curry('str_concat', ':'), array_keys($values));
return sprintf('INSERT INTO %s (%s) VALUES (%s)',
$this->table_name,
implode(', ', array_keys($values)),
implode(', ', $placeholders));
}
protected function generateAssignmentClause(array $assignments, $prefix = '')
{
$assignment_expressions = array();
$assignment_values = array();
foreach ($assignments as $key => $value)
{
$placeholder = $prefix . $key;
$assignment_expressions[] = sprintf('%s = :%s', $key, $placeholder);
$assignment_values[$placeholder] = $value;
}
return new Pair($assignment_expressions, $assignment_values);
}
}
<?php
/**
* Seriously?! PHP does not have a build-in string concat function? Only that dot
* operator which you can't pass as an argument to array_reduce? That sucks.
* @param string $string,... one or more strings to concatenate
* @return string
*/
function str_concat($string)
{
$output = '';
foreach (func_get_args() as $string)
$output .= $string;
return $output;
}
function str_remove($string, $substring)
{
for ($i = 0; $i < strlen($substring); ++$i)
if ($string{$i} != $substring{$i})
break;
return substr($string, $i);
}
/**
* Bind some arguments already to a function. The arguments which whom the function
* finally is called are appended after the already bound arguments.
* e.g. $a = curry('implode', ':') will return a function $a which then can be called
* with the remaining missing arguments (an array in this example) and then will call
* implode(':', supplied-array) eventually. Very handy in combination with array_map
* and array_filter.
*
* @param callable $function function
* @param mixed $arg,... one or more arguments
* @return callable
*/
function curry($function, $arg)
{
$bound_arguments = func_get_args();
array_shift($bound_arguments);
return function() use ($function, $bound_arguments) {
$call_arguments = func_get_args();
return call_user_func_array($function, array_merge($bound_arguments, $call_arguments));
};
}
function autoload_by_prefix($classname)
{
$parts = explode('_', $classname);
include dirname(__FILE__) . '/' . strtolower($parts[0]) . '.php';
}
function call_method($method, array $arguments, $object)
{
return call_user_func_array(array($object, $method), $arguments);
}
/**
* Like the C++ class. If only PHP had tuples...
*/
class Pair
{
public $first;
public $second;
public function __construct($first, $second)
{
$this->first = $first;
$this->second = $second;
}
}
abstract class Maybe
{
protected $value;
public function __construct($value = null)
{
$this->value = $value;
}
public function isSuccess()
{
return $this instanceof Success;
}
public function isFailure()
{
return $this instanceof Failure;
}
public function isNull()
{
return $this instanceof Nothing;
}
public function value()
{
return $this->value;
}
public function __toString()
{
return json_encode($this->value());
}
}
class Success extends Maybe
{}
class Failure extends Maybe
{}
class Nothing extends Maybe
{}
if (!class_exists('CallbackFilterIterator'))
{
class CallbackFilterIterator extends FilterIterator
{
private $callback;
public function __construct(Iterator $iterator, $callback)
{
parent::__construct($iterator);
$this->callback = $callback;
}
public function accept()
{
return call_user_func($this->callback,
$this->getInnerIterator()->current(),
$this->getInnerIterator()->key(),
$this->getInnerIterator());
}
public function __toString()
{
return sprintf('[%s calling %s on %s]',
get_class($this),
$this->callback,
$this->getInnerIterator());
}
}
}
if (!class_exists('CallbackIterator'))
{
class CallbackIterator extends IteratorIterator
{
private $callback;
public function __construct(Iterator $iterator, $callback)
{
parent::__construct($iterator);
$this->callback = $callback;
}
public function current()
{
return call_user_func($this->callback,
parent::current(),
parent::key(),
$this->getInnerIterator());
}
public function __toString()
{
return sprintf('[%s calling %s on %s]',
get_class($this),
$this->callback,
$this->getInnerIterator());
}
}
}
class FunctionalIterator extends IteratorIterator
{
public function filter($callback)
{
return new FunctionalIterator(new CallbackFilterIterator($this, $callback));
}
public function map($callback)
{
return new FunctionalIterator(new CallbackIterator($this, $callback));
}
public function first()
{
if (!$this->valid())
$this->rewind();
return $this->valid()
? $this->current()
: null;
}
public function __toString()
{
return '[FunctionalIterator ' . strval($this->getInnerIterator()) . ']';
}
}
class PDOIterator implements Iterator
{
private $stmt;
private $current;
private $index;
public function __construct(PDOStatement $stmt)
{
$this->stmt = $stmt;
$this->index = 0;
}
public function getPDOStatement()
{
return $this->stmt;
}
public function current()
{
return $this->current;
}
public function key()
{
return $this->index;
}
public function next()
{
$this->current = $this->stmt->fetch();
++$this->index;
}
public function rewind()
{
if ($this->index)
throw new RuntimeException("You cannot rewind a PDOIterator twice");
$this->current = $this->stmt->fetch();
}
public function valid()
{
return (bool) $this->current;
}
public function __toString()
{
return sprintf('[%s around query "%s"]',
get_class($this),
$this->stmt->queryString);
}
}
/**
* Dependency Injector. Can build your objects!
*
* Fancy stuff. Not really needed, but it is a nice way of doing it I think.
*/
class DependencyInjector
{
private $instances = array();
private $recipes = array();
private $default_factory = array(__CLASS__, 'defaultFactory');
public function __construct()
{
$this->instances[get_class($this)] = $this;
}
/**
* Add a recipe to the injector on how to build an instance of $classname.
* @param string $classname name of the class which the recipe builds
* @param callable $recipe callback which constructs an instance
*/
public function addRecipe($classname, $recipe)
{
$this->recipes[$classname] = $recipe;
}
/**
* Get an instance of $classname, whether it is already built or build one
* especially for me using the recipes.
* @param string $classname instance of this class
*/
public function get($classname)
{
assert(is_string($classname) || var_dump($classname));
return isset($this->instances[$classname])
? $this->instances[$classname]
: $this->instances[$classname] = $this->build($classname);
}
/**
* Really build an instance of $classname using the recipes.
* @param string $classname build an instance of this class
*/
public function build($classname, array $arguments = array())
{
$factory = isset($this->recipes[$classname])
? $this->recipes[$classname]
: $this->default_factory;
return call_user_func($factory, $this, $classname, $arguments);
}
public function alias($classname)
{
return new ProxyObject(curry(array($this, 'get'), $classname));
}
/**
* A default recipe for when we don't know how to build a class.
* It just looks at the constructor of a class and sees which
* parameters it needs based on their typehints, and tries to
* build them.
* @param DependencyInjector $injector injector which will be used to build the arguments
* @param string $classname build an instance of this class
* @return object
*/
static public function defaultFactory($injector, $classname, array $arguments)
{
$class = new ReflectionClass($classname);
if (empty($arguments) && $class->hasMethod('__construct'))
{
$constructor = $class->getMethod('__construct');
foreach ($constructor->getParameters() as $parameter)
{
if ($parameter->isOptional())
break;
if ($parameter->getClass() == null)
throw new Exception('No interface provided for argument '
. $parameter->getName() . ' of '
. $class->getName() . '\'s constructor');
$arguments[] = $injector->get($parameter->getClass()->getName());
}
}
return $class->newInstanceArgs($arguments);
}
}
class ProxyObject
{
private $callback;
public function __construct($callback)
{
$this->callback = $callback;
}
public function __get($key)
{
return $this->getInnerObject()->$key;
}
public function __set($key, $value)
{
return $this->getInnerObject()->$key = $value;
}
public function __isset($key)
{
return isset($this->getInnerObject()->$key);
}
public function __unset($key)
{
// return unset($this->getInnerObject()->$key);
}
public function __call($method, $arguments)
{
return call_user_func_array(array($this->getInnerObject(), $method), $arguments);
}
private function getInnerObject()
{
return call_user_func($this->callback);
}
}
function get_properties($object, array $properties)
{
$values = array();
foreach ($properties as $property)
$values[$property] = $object->$property;
return $values;
}
function unset_properties($object, array $properties)
{
foreach ($properties as $property)
unset($object->$property);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment