Created
April 26, 2012 09:26
-
-
Save jelmervdl/2498047 to your computer and use it in GitHub Desktop.
Functional PHP classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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