Refactored structure

This commit is contained in:
Dmitry Khomutov 2016-04-17 12:34:12 +06:00
commit e5164ae1dd
329 changed files with 277 additions and 457 deletions

145
src/B8Framework/Application.php Executable file
View file

@ -0,0 +1,145 @@
<?php
namespace b8;
use b8\Config;
use b8\Exception\HttpException\NotFoundException;
use b8\Http;
use b8\View;
use b8\Controller;
use b8\Http\Response;
use b8\Http\Request;
class Application
{
/**
* @var array
*/
protected $route;
/**
* @var Controller
*/
protected $controller;
/**
* @var Request
*/
protected $request;
/**
* @var Response
*/
protected $response;
/**
* @var Config
*/
protected $config;
public function __construct(Config $config, Http\Request $request = null)
{
$this->config = $config;
$this->response = new Http\Response();
if (!is_null($request)) {
$this->request = $request;
} else {
$this->request = new Http\Request();
}
$this->router = new Http\Router($this, $this->request, $this->config);
if (method_exists($this, 'init')) {
$this->init();
}
}
public function handleRequest()
{
$this->route = $this->router->dispatch();
if (!empty($this->route['callback'])) {
$callback = $this->route['callback'];
if (!$callback($this->route, $this->response)) {
return $this->response;
}
}
if (!$this->controllerExists($this->route)) {
throw new NotFoundException('Controller ' . $this->toPhpName($this->route['controller']) . ' does not exist!');
}
$action = lcfirst($this->toPhpName($this->route['action']));
if (!$this->getController()->hasAction($action)) {
throw new NotFoundException('Controller ' . $this->toPhpName($this->route['controller']) . ' does not have action ' . $action . '!');
}
return $this->getController()->handleAction($action, $this->route['args']);
}
/**
* @return Controller
*/
public function getController()
{
if (empty($this->controller)) {
$controllerClass = $this->getControllerClass($this->route);
$this->controller = $this->loadController($controllerClass);
}
return $this->controller;
}
/**
* @param string $class
*
* @return Controller
*/
protected function loadController($class)
{
$controller = new $class($this->config, $this->request, $this->response);
$controller->init();
return $controller;
}
/**
* @param array $route
*
* @return bool
*/
protected function controllerExists($route)
{
return class_exists($this->getControllerClass($route));
}
/**
* @param array $route
*
* @return string
*/
protected function getControllerClass($route)
{
$namespace = $this->toPhpName($route['namespace']);
$controller = $this->toPhpName($route['controller']);
return $this->config->get('b8.app.namespace') . '\\' . $namespace . '\\' . $controller . 'Controller';
}
public function isValidRoute($route)
{
if ($this->controllerExists($route)) {
return true;
}
return false;
}
protected function toPhpName($string)
{
$string = str_replace('-', ' ', $string);
$string = ucwords($string);
$string = str_replace(' ', '', $string);
return $string;
}
}

29
src/B8Framework/Cache.php Executable file
View file

@ -0,0 +1,29 @@
<?php
namespace b8;
/**
* @package b8
* @subpackage Cache
*/
class Cache
{
const TYPE_APC = 'ApcCache';
const TYPE_REQUEST = 'RequestCache';
protected static $instance = array();
/**
* Get a cache object of a specified type.
*/
public static function getCache($type = self::TYPE_REQUEST)
{
if (!isset(self::$instance[$type])) {
$class = '\\b8\\Cache\\' . $type;
self::$instance[$type] = new $class();
}
return self::$instance[$type];
}
}

View file

@ -0,0 +1,149 @@
<?php
namespace b8\Cache;
use b8\Type;
class ApcCache implements Type\Cache
{
/**
* Check if caching is enabled.
*
* @return boolean
*/
public function isEnabled()
{
$rtn = false;
$apcCli = ini_get('apc.enable_cli');
if (function_exists('apc_fetch') && (php_sapi_name() != 'cli' || in_array($apcCli, array('1', 1, true, 'On')))) {
$rtn = true;
}
return $rtn;
}
/**
* Get item from the cache:
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function get($key, $default = null)
{
if (!$this->isEnabled()) {
return $default;
}
$success = false;
$rtn = apc_fetch($key, $success);
if (!$success) {
$rtn = $default;
}
return $rtn;
}
/**
* Add an item to the cache:
*
* @param string $key
* @param mixed $value
* @param integer $ttl
*
* @return array|bool
*/
public function set($key, $value = null, $ttl = 0)
{
if (!$this->isEnabled()) {
return false;
}
return apc_store($key, $value, $ttl);
}
/**
* Remove an item from the cache:
*
* @param string $key
*
* @return bool|string[]
*/
public function delete($key)
{
if (!$this->isEnabled()) {
return false;
}
return apc_delete($key);
}
/**
* Check if an item is in the cache:
*
* @param string $key
*
* @return bool|string[]
*/
public function contains($key)
{
if (!$this->isEnabled()) {
return false;
}
return apc_exists($key);
}
/**
* Short-hand syntax for get()
*
* @see Config::get()
*
* @param string $key
*
* @return mixed
*/
public function __get($key)
{
return $this->get($key, null);
}
/**
* Short-hand syntax for set()
*
* @see Config::set()
*
* @param string $key
* @param mixed $value
*
* @return array|bool
*/
public function __set($key, $value = null)
{
return $this->set($key, $value);
}
/**
* Is set
*
* @param string $key
*
* @return bool|string[]
*/
public function __isset($key)
{
return $this->contains($key);
}
/**
* Unset
*
* @param string $key
*/
public function __unset($key)
{
$this->delete($key);
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace b8\Cache;
use b8\Type;
class RequestCache implements Type\Cache
{
protected $data = array();
/**
* Check if caching is enabled.
*/
public function isEnabled()
{
return true;
}
/**
* Get item from the cache:
*/
public function get($key, $default = null)
{
return $this->contains($key) ? $this->data[$key] : $default;
}
/**
* Add an item to the cache:
*/
public function set($key, $value = null, $ttl = 0)
{
$this->data[$key] = $value;
return $this;
}
/**
* Remove an item from the cache:
*/
public function delete($key)
{
if ($this->contains($key)) {
unset($this->data[$key]);
}
return $this;
}
/**
* Check if an item is in the cache:
*/
public function contains($key)
{
return array_key_exists($key, $this->data);
}
/**
* Short-hand syntax for get()
* @see Config::get()
*/
public function __get($key)
{
return $this->get($key, null);
}
/**
* Short-hand syntax for set()
* @see Config::set()
*/
public function __set($key, $value = null)
{
return $this->set($key, $value);
}
/**
* Is set
*/
public function __isset($key)
{
return $this->contains($key);
}
/**
* Unset
*/
public function __unset($key)
{
$this->delete($key);
}
}

176
src/B8Framework/Config.php Executable file
View file

@ -0,0 +1,176 @@
<?php
namespace b8;
use Symfony\Component\Yaml\Parser as YamlParser;
if (!defined('B8_PATH')) {
define('B8_PATH', dirname(__FILE__) . '/');
}
class Config
{
protected static $instance;
public static function getInstance()
{
return self::$instance;
}
/**
* @var array
*/
protected $config = array();
public function __construct($settings = null)
{
self::$instance = $this;
if (empty($settings)) {
return;
} elseif (is_array($settings)) {
// Array of setting data.
$this->setArray($settings);
} elseif (is_string($settings) && file_exists($settings)) {
$this->loadYaml($settings);
}
}
public function loadYaml($yamlFile)
{
// Path to a YAML file.
$parser = new YamlParser();
$yaml = file_get_contents($yamlFile);
$config = (array)$parser->parse($yaml);
if (empty($config)) {
return;
}
$this->setArray($config);
}
/**
* Get a configuration value by key, returning a default value if not set.
* @param $key string
* @param $default mixed
* @return mixed
*/
public function get($key, $default = null)
{
$keyParts = explode('.', $key);
$selected = $this->config;
$i = -1;
$last_part = count($keyParts) - 1;
while ($part = array_shift($keyParts)) {
$i++;
if (!array_key_exists($part, $selected)) {
return $default;
}
if ($i === $last_part) {
return $selected[$part];
} else {
$selected = $selected[$part];
}
}
return $default;
}
/**
* Set a value by key.
* @param $key string
* @param $value mixed
*/
public function set($key, $value = null)
{
$this->config[$key] = $value;
}
/**
* Set an array of values.
*/
public function setArray($array)
{
self::deepMerge($this->config, $array);
}
/**
* Short-hand syntax for get()
* @see Config::get()
*/
public function __get($key)
{
return $this->get($key);
}
/**
* Short-hand syntax for set()
* @see Config::set()
*/
public function __set($key, $value = null)
{
return $this->set($key, $value);
}
/**
* Is set
*/
public function __isset($key)
{
return isset($this->config[$key]);
}
/**
* Unset
*/
public function __unset($key)
{
unset($this->config[$key]);
}
/**
* Deeply merge the $target array onto the $source array.
* The $source array will be modified!
* @param array $source
* @param array $target
*/
public static function deepMerge(&$source, $target)
{
if (count($source) === 0) {
$source = $target;
return;
}
foreach ($target as $target_key => $target_value) {
if (isset($source[$target_key])) {
if (!is_array($source[$target_key]) && !is_array($target_value)) {
// Neither value is an array, overwrite
$source[$target_key] = $target_value;
} elseif (is_array($source[$target_key]) && is_array($target_value)) {
// Both are arrays, deep merge them
self::deepMerge($source[$target_key], $target_value);
} elseif (is_array($source[$target_key])) {
// Source is the array, push target value
$source[$target_key][] = $target_value;
} else {
// Target is the array, push source value and copy back
$target_value[] = $source[$target_key];
$source[$target_key] = $target_value;
}
} else {
// No merge required, just set the value
$source[$target_key] = $target_value;
}
}
}
public function getArray()
{
return $this->config;
}
}

116
src/B8Framework/Controller.php Executable file
View file

@ -0,0 +1,116 @@
<?php
namespace b8;
use b8\Config;
use b8\Http\Request;
use b8\Http\Response;
use b8\View;
/**
* b8 Abstract Controller class
* @package b8
*/
abstract class Controller
{
/**
* @var Request
*/
protected $request;
/**
* @var Response
*/
protected $response;
/**
* @var Config
*/
protected $config;
/**
* @var View
*/
protected $controllerView;
/**
* @var View
*/
protected $view;
public function __construct(Config $config, Request $request, Response $response)
{
$this->config = $config;
$this->request = $request;
$this->response = $response;
}
public function hasAction($name)
{
if (method_exists($this, $name)) {
return true;
}
if (method_exists($this, '__call')) {
return true;
}
return false;
}
/**
* Handles an action on this controller and returns a Response object.
* @return Response
*/
public function handleAction($action, $actionParams)
{
return call_user_func_array(array($this, $action), $actionParams);
}
/**
* Initialise the controller.
*/
abstract public function init();
/**
* Get a hash of incoming request parameters ($_GET, $_POST)
*
* @return array
*/
public function getParams()
{
return $this->request->getParams();
}
/**
* Get a specific incoming request parameter.
*
* @param $key
* @param mixed $default Default return value (if key does not exist)
*
* @return mixed
*/
public function getParam($key, $default = null)
{
return $this->request->getParam($key, $default);
}
/**
* Change the value of an incoming request parameter.
* @param $key
* @param $value
*/
public function setParam($key, $value)
{
return $this->request->setParam($key, $value);
}
/**
* Remove an incoming request parameter.
* @param $key
*/
public function unsetParam($key)
{
return $this->request->unsetParam($key);
}
}

View file

@ -0,0 +1,246 @@
<?php
namespace b8\Controller;
use b8\Controller,
b8\Type\RestUser,
b8\Store\Factory,
b8\Exception\HttpException;
class RestController extends Controller
{
const SEARCHTYPE_AND = 'AND';
const SEARCHTYPE_OR = 'OR';
public $requiresAuthentication = true;
public $updateLastAction = true;
/**
* @var \b8\Type\RestUser
*/
protected $activeUser;
protected $where = array();
protected $limit = null;
protected $offset = null;
protected $joins = array();
protected $arrayDepth = 2;
protected $params = null;
protected $order = array();
protected $group = null;
protected $manualJoins = array();
protected $manualWheres = array();
protected $searchType = self::SEARCHTYPE_AND;
protected $_resourceName = null;
protected $_modelName = null;
protected $_tableName = null;
protected $_modelClass = null;
public function init()
{
}
protected function setControllerView()
{
}
protected function setView()
{
}
public function handleAction($action, $actionParams)
{
$response = call_user_func_array(array($this, $action), $actionParams);
$this->response->setContent($response);
return $this->response;
}
public function setActiveUser(RestUser $user)
{
$this->activeUser = $user;
}
public function getActiveUser()
{
return $this->activeUser;
}
public function index()
{
if(!$this->activeUser->checkPermission('canRead', $this->_resourceName))
{
throw new HttpException\ForbiddenException('You do not have permission do this.');
}
$this->where = $this->_parseWhere();
$this->limit = is_null($this->limit) ? $this->getParam('limit', 25) : $this->limit;
$this->offset = is_null($this->offset) ? $this->getParam('offset', 0) : $this->offset;
$this->order = is_null($this->order) || !count($this->order) ? $this->getParam('order', array()) : $this->order;
$this->group = is_null($this->group) || !count($this->group) ? $this->getParam('group', null) : $this->group;
$this->searchType = $this->getParam('searchType', self::SEARCHTYPE_AND);
$store = Factory::getStore($this->_modelName);
$data = $store->getWhere($this->where, $this->limit, $this->offset, $this->joins, $this->order, $this->manualJoins, $this->group, $this->manualWheres, $this->searchType);
$rtn = array(
'debug' => array(
'where' => $this->where,
'searchType' => $this->searchType,
),
'limit' => $this->limit,
'offset' => $this->offset,
'total' => $data['count'],
'items' => array()
);
foreach($data['items'] as $item)
{
$rtn['items'][] = $item->toArray($this->arrayDepth);
}
return $rtn;
}
/**
*
*/
protected function _parseWhere()
{
$clauses = array(
'fuzzy' => 'like',
'gt' => '>',
'gte' => '>=',
'lt' => '<',
'lte' => '<=',
'neq' => '!=',
'between' => 'between'
);
$where = $this->getParam('where', array());
$where = array_merge($where, $this->where);
if(count($where))
{
foreach($where as &$value)
{
if(!is_array($value) || !isset($value['operator']))
{
if(is_array($value) && count($value) == 1)
{
$value = array_shift($value);
}
$value = array(
'operator' => '=',
'value' => $value
);
}
}
foreach($clauses as $clause => $operator)
{
$fields = $this->getParam($clause, array());
if(count($clause))
{
if(!is_array($fields))
{
$fields = array($fields);
}
foreach($fields as $field)
{
if(isset($where[$field]))
{
$where[$field]['operator'] = $operator;
if($operator == 'like')
{
$where[$field]['value'] = str_replace(' ', '%', $where[$field]['value']);
}
}
}
}
}
}
return $where;
}
public function get($key)
{
if(!$this->activeUser->checkPermission('canRead', $this->_resourceName))
{
throw new HttpException\ForbiddenException('You do not have permission do this.');
}
$rtn = Factory::getStore($this->_modelName)->getByPrimaryKey($key);
if(is_object($rtn) && method_exists($rtn, 'toArray'))
{
$rtn = $rtn->toArray($this->arrayDepth);
}
return array(strtolower($this->_modelName) => $rtn);
}
public function put($key)
{
if(!$this->activeUser->checkPermission('canEdit', $this->_resourceName))
{
throw new HttpException\ForbiddenException('You do not have permission do this.');
}
$store = Factory::getStore($this->_modelName);
if($obj = $store->getByPrimaryKey($key))
{
$obj->setValues($this->getParams());
$rtn = $store->save($obj);
return array(strtolower($this->_modelName) => $rtn->toArray($this->arrayDepth));
}
else
{
return null;
}
}
public function post()
{
if(!$this->activeUser->checkPermission('canCreate', $this->_resourceName))
{
throw new HttpException\ForbiddenException('You do not have permission do this.');
}
$store = Factory::getStore($this->_modelName);
$modelClass = $this->_modelClass;
$obj = new $modelClass();
$obj->setValues($this->getParams());
$rtn = $store->save($obj);
return array(strtolower($this->_modelName) => $rtn->toArray($this->arrayDepth));
}
public function delete($key)
{
if(!$this->activeUser->checkPermission('canDelete', $this->_resourceName))
{
throw new HttpException\ForbiddenException('You do not have permission do this.');
}
$store = Factory::getStore($this->_modelName);
try
{
if($obj = $store->getByPrimaryKey($key))
{
$store->delete($obj);
return array('deleted' => true);
}
}
catch(\Exception $ex)
{
}
return array('deleted' => false);
}
}

151
src/B8Framework/Database.php Executable file
View file

@ -0,0 +1,151 @@
<?php
namespace b8;
class Database extends \PDO
{
protected static $initialised = false;
protected static $servers = array('read' => array(), 'write' => array());
protected static $connections = array('read' => null, 'write' => null);
protected static $details = array();
protected static $lastUsed = array('read' => null, 'write' => null);
/**
* @deprecated
*/
public static function setReadServers($read)
{
$config = Config::getInstance();
$settings = $config->get('b8.database', array());
$settings['servers']['read'] = $read;
$config->set('b8.database', $settings);
}
/**
* @deprecated
*/
public static function setWriteServers($write)
{
$config = Config::getInstance();
$settings = $config->get('b8.database', array());
$settings['servers']['write'] = $write;
$config->set('b8.database', $settings);
}
/**
* @deprecated
*/
public static function setDetails($database, $username, $password)
{
$config = Config::getInstance();
$settings = $config->get('b8.database', array());
$settings['name'] = $database;
$settings['username'] = $username;
$settings['password'] = $password;
$config->set('b8.database', $settings);
}
protected static function init()
{
$config = Config::getInstance();
$settings = $config->get('b8.database', array());
self::$servers['read'] = $settings['servers']['read'];
self::$servers['write'] = $settings['servers']['write'];
self::$details['db'] = $settings['name'];
self::$details['user'] = $settings['username'];
self::$details['pass'] = $settings['password'];
self::$initialised = true;
}
/**
* @param string $type
*
* @return \b8\Database
* @throws \Exception
*/
public static function getConnection($type = 'read')
{
if (!self::$initialised) {
self::init();
}
// If the connection hasn't been used for 5 minutes, force a reconnection:
if (!is_null(self::$lastUsed[$type]) && (time() - self::$lastUsed[$type]) > 300) {
self::$connections[$type] = null;
}
if(is_null(self::$connections[$type])) {
if (is_array(self::$servers[$type])) {
// Shuffle, so we pick a random server:
$servers = self::$servers[$type];
shuffle($servers);
} else {
// Only one server was specified
$servers = array(self::$servers[$type]);
}
$connection = null;
// Loop until we get a working connection:
while(count($servers))
{
// Pull the next server:
$server = array_shift($servers);
if (stristr($server, ':')) {
list($host, $port) = explode(':', $server);
$server = $host . ';port=' . $port;
}
// Try to connect:
try
{
$connection = new self('mysql:host=' . $server . ';dbname=' . self::$details['db'],
self::$details['user'],
self::$details['pass'],
array(
\PDO::ATTR_PERSISTENT => false,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_TIMEOUT => 2,
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
));
}
catch(\PDOException $ex)
{
$connection = false;
}
// Opened a connection? Break the loop:
if($connection)
{
break;
}
}
// No connection? Oh dear.
if(!$connection && $type == 'read')
{
throw new \Exception('Could not connect to any ' . $type . ' servers.');
}
self::$connections[$type] = $connection;
}
self::$lastUsed[$type] = time();
return self::$connections[$type];
}
public function getDetails()
{
return self::$details;
}
public static function reset()
{
self::$connections = array('read' => null, 'write' => null);
self::$lastUsed = array('read' => null, 'write' => null);
self::$initialised = false;
}
}

View file

@ -0,0 +1,153 @@
<?php
namespace b8\Database;
use b8\Database,
b8\Database\Map,
b8\View\Template;
class CodeGenerator
{
protected $_db = null;
protected $_map = null;
protected $_tables = null;
protected $_ns = null;
protected $_path = null;
/**
* @param Database $db
* @param array $namespaces
* @param string $path
* @param bool $includeCountQueries
*/
public function __construct(Database $db, array $namespaces, $path, $includeCountQueries = true)
{
$this->_db = $db;
$this->_ns = $namespaces;
$this->_path = $path;
$this->_map = new Map($this->_db);
$this->_tables = $this->_map->generate();
$this->_counts = $includeCountQueries;
}
protected function getNamespace($modelName)
{
return array_key_exists($modelName, $this->_ns) ? $this->_ns[$modelName] : $this->_ns['default'];
}
public function getPath($namespace)
{
return array_key_exists($namespace, $this->_path) ? $this->_path[$namespace] : $this->_path['default'];
}
public function generateModels()
{
print PHP_EOL . 'GENERATING MODELS' . PHP_EOL . PHP_EOL;
foreach($this->_tables as $tableName => $table)
{
$namespace = $this->getNamespace($table['php_name']);
$modelPath = $this->getPath($namespace) . str_replace('\\', '/', $namespace) . '/Model/';
$basePath = $modelPath . 'Base/';
$modelFile = $modelPath . $table['php_name'] . '.php';
$baseFile = $basePath . $table['php_name'] . 'Base.php';
if (!is_dir($basePath)) {
@mkdir($basePath, 0777, true);
}
$model = $this->_processTemplate($tableName, $table, 'ModelTemplate');
$base = $this->_processTemplate($tableName, $table, 'BaseModelTemplate');
print '-- ' . $table['php_name'] . PHP_EOL;
if(!is_file($modelFile))
{
print '-- -- Writing new Model' . PHP_EOL;
file_put_contents($modelFile, $model);
}
print '-- -- Writing base Model' . PHP_EOL;
file_put_contents($baseFile, $base);
}
}
public function generateStores()
{
print PHP_EOL . 'GENERATING STORES' . PHP_EOL . PHP_EOL;
foreach($this->_tables as $tableName => $table)
{
$namespace = $this->getNamespace($table['php_name']);
$storePath = $this->getPath($namespace) . str_replace('\\', '/', $namespace) . '/Store/';
$basePath = $storePath . 'Base/';
$storeFile = $storePath . $table['php_name'] . 'Store.php';
$baseFile = $basePath . $table['php_name'] . 'StoreBase.php';
if (!is_dir($basePath)) {
@mkdir($basePath, 0777, true);
}
$model = $this->_processTemplate($tableName, $table, 'StoreTemplate');
$base = $this->_processTemplate($tableName, $table, 'BaseStoreTemplate');
print '-- ' . $table['php_name'] . PHP_EOL;
if(!is_file($storeFile))
{
print '-- -- Writing new Store' . PHP_EOL;
file_put_contents($storeFile, $model);
}
print '-- -- Writing base Store' . PHP_EOL;
file_put_contents($baseFile, $base);
}
}
public function generateControllers()
{
print PHP_EOL . 'GENERATING CONTROLLERS' . PHP_EOL . PHP_EOL;
@mkdir($this->_path . 'Controller/Base/', 0777, true);
foreach($this->_tables as $tableName => $table)
{
$namespace = $this->getNamespace($table['php_name']);
$controllerPath = $this->getPath($namespace) . str_replace('\\', '/', $namespace) . '/Controller/';
$basePath = $controllerPath . 'Base/';
$controllerFile = $controllerPath . $table['php_name'] . 'Controller.php';
$baseFile = $basePath . $table['php_name'] . 'ControllerBase.php';
if (!is_dir($basePath)) {
@mkdir($basePath, 0777, true);
}
$model = $this->_processTemplate($tableName, $table, 'ControllerTemplate');
$base = $this->_processTemplate($tableName, $table, 'BaseControllerTemplate');
print '-- ' . $table['php_name'] . PHP_EOL;
if(!is_file($controllerFile))
{
print '-- -- Writing new Controller' . PHP_EOL;
file_put_contents($controllerFile, $model);
}
print '-- -- Writing base Controller' . PHP_EOL;
file_put_contents($baseFile, $base);
}
}
protected function _processTemplate($tableName, $table, $template)
{
$tpl = Template::createFromFile($template, B8_PATH . 'Database/CodeGenerator/');
$tpl->appNamespace = $this->getNamespace($table['php_name']);
$tpl->name = $tableName;
$tpl->table = $table;
$tpl->counts = $this->_counts;
$tpl->addFunction('get_namespace', function($args, $view) {
return $this->getNamespace($view->getVariable($args['model']));
});
return $tpl->render();
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* {@table.php_name} base controller for table: {@name}
*/
namespace {@appNamespace}\Controller\Base;
use b8\Controller\RestController;
/**
* {@table.php_name} base Controller
* @see {@appNamespace}\Controller\{@table.php_name}
* @uses {@appNamespace}\Store\{@table.php_name}Store
* @uses {@appNamespace}\Model\{@table.php_name}
*/
class {@table.php_name}ControllerBase extends RestController
{
protected $_modelName = '{@table.php_name}';
protected $_resourceName = '{@table.php_name.toLowerCase}s';
protected $_modelClass = '\{@appNamespace}\Model\{@table.php_name}';
}

View file

@ -0,0 +1,268 @@
<?php
/**
* {@table.php_name} base model for table: {@name}
*/
namespace {@appNamespace}\Model\Base;
use {@appNamespace}\Model;
use b8\Store\Factory;
/**
* {@table.php_name} Base Model
*/
class {@table.php_name}Base extends Model
{
/**
* @var array
*/
public static $sleepable = array();
/**
* @var string
*/
protected $tableName = '{@name}';
/**
* @var string
*/
protected $modelName = '{@table.php_name}';
/**
* @var array
*/
protected $data = array(
{loop table.columns}
'{@item.name}' => null,
{/loop}
);
/**
* @var array
*/
protected $getters = array(
// Direct property getters:
{loop table.columns}
'{@item.name}' => 'get{@item.php_name}',
{/loop}
// Foreign key getters:
{loop table.relationships.toOne}
'{@item.php_name}' => 'get{@item.php_name}',
{/loop}
);
/**
* @var array
*/
protected $setters = array(
// Direct property setters:
{loop table.columns}
'{@item.name}' => 'set{@item.php_name}',
{/loop}
// Foreign key setters:
{loop table.relationships.toOne}
'{@item.php_name}' => 'set{@item.php_name}',
{/loop}
);
/**
* @var array
*/
public $columns = array(
{loop table.columns}
'{@item.name}' => array(
'type' => '{@item.type}',
{if item.length}
'length' => {@item.length},
{/if}
{if item.null}
'nullable' => true,
{/if}
{if item.is_primary_key}
'primary_key' => true,
{/if}
{if item.auto}
'auto_increment' => true,
{/if}
{if item.default_is_null}
'default' => null,
{/if}
{ifnot item.default_is_null}
{if item.default}
'default' => {if item.default.isNumeric}{@item.default}{/if}{ifnot item.default.isNumeric}'{@item.default}'{/ifnot},
{/if}
{/ifnot}
),
{/loop}
);
/**
* @var array
*/
public $indexes = array(
{loop table.indexes}
'{@item.name}' => array({if item.unique}'unique' => true, {/if}'columns' => '{@item.columns}'),
{/loop}
);
/**
* @var array
*/
public $foreignKeys = array(
{loop table.relationships.toOne}
'{@item.fk_name}' => array(
'local_col' => '{@item.from_col}',
'update' => '{@item.fk_update}',
'delete' => '{@item.fk_delete}',
'table' => '{@item.table}',
'col' => '{@item.col}'
),
{/loop}
);
{loop table.columns}
/**
* Get the value of {@item.php_name} / {@item.name}.
*
{if item.validate_int}
* @return int
{/if}{if item.validate_string}
* @return string
{/if}{if item.validate_float}
* @return float
{/if}{if item.validate_date}
* @return \DateTime
{/if}
*/
public function get{@item.php_name}()
{
$rtn = $this->data['{@item.name}'];
{if item.validate_date}
if (!empty($rtn)) {
$rtn = new \DateTime($rtn);
}
{/if}
return $rtn;
}
{/loop}
{loop table.columns}
/**
* Set the value of {@item.php_name} / {@item.name}.
*
{if item.validate_null}
* Must not be null.
{/if}{if item.validate_int}
* @param $value int
{/if}{if item.validate_string}
* @param $value string
{/if}{if item.validate_float}
* @param $value float
{/if}{if item.validate_date}
* @param $value \DateTime
{/if}
*/
public function set{@item.php_name}($value)
{
{if item.validate_null}
$this->_validateNotNull('{@item.php_name}', $value);
{/if}
{if item.validate_int}
$this->_validateInt('{@item.php_name}', $value);
{/if}
{if item.validate_string}
$this->_validateString('{@item.php_name}', $value);
{/if}
{if item.validate_float}
$this->_validateFloat('{@item.php_name}', $value);
{/if}
{if item.validate_date}
$this->_validateDate('{@item.php_name}', $value);
{/if}
if ($this->data['{@item.name}'] === $value) {
return;
}
$this->data['{@item.name}'] = $value;
$this->_setModified('{@item.name}');
}
{/loop}{loop table.relationships.toOne}
/**
* Get the {@item.table_php_name} model for this {@parent.table.php_name} by {@item.col_php}.
*
* @uses \{@parent.appNamespace}\Store\{@item.table_php_name}Store::getBy{@item.col_php}()
* @uses \{@parent.appNamespace}\Model\{@item.table_php_name}
* @return \{@parent.appNamespace}\Model\{@item.table_php_name}
*/
public function get{@item.php_name}()
{
$key = $this->get{@item.from_col_php}();
if (empty($key)) {
return null;
}
$cacheKey = 'Cache.{@item.table_php_name}.' . $key;
$rtn = $this->cache->get($cacheKey, null);
if (empty($rtn)) {
$rtn = Factory::getStore('{@item.table_php_name}', '{get_namespace model: item.table_php_name}')->getBy{@item.col_php}($key);
$this->cache->set($cacheKey, $rtn);
}
return $rtn;
}
/**
* Set {@item.php_name} - Accepts an ID, an array representing a {@item.table_php_name} or a {@item.table_php_name} model.
*
* @param $value mixed
*/
public function set{@item.php_name}($value)
{
// Is this an instance of {@item.table_php_name}?
if ($value instanceof \{@parent.appNamespace}\Model\{@item.table_php_name}) {
return $this->set{@item.php_name}Object($value);
}
// Is this an array representing a {@item.table_php_name} item?
if (is_array($value) && !empty($value['{@item.col}'])) {
return $this->set{@item.from_col_php}($value['{@item.col}']);
}
// Is this a scalar value representing the ID of this foreign key?
return $this->set{@item.from_col_php}($value);
}
/**
* Set {@item.php_name} - Accepts a {@item.table_php_name} model.
*
* @param $value \{@parent.appNamespace}\Model\{@item.table_php_name}
*/
public function set{@item.php_name}Object(\{@parent.appNamespace}\Model\{@item.table_php_name} $value)
{
return $this->set{@item.from_col_php}($value->get{@item.col_php}());
}
{/loop}{loop table.relationships.toMany}
/**
* Get {@item.table_php} models by {@item.from_col_php} for this {@parent.table.php_name}.
*
* @uses \{@parent.appNamespace}\Store\{@item.table_php}Store::getBy{@item.from_col_php}()
* @uses \{@parent.appNamespace}\Model\{@item.table_php}
* @return \{@parent.appNamespace}\Model\{@item.table_php}[]
*/
public function get{@item.php_name}()
{
return Factory::getStore('{@item.table_php}', '{get_namespace model: item.table_php_name}')->getBy{@item.from_col_php}($this->get{@item.col_php}());
}
{/loop}}

View file

@ -0,0 +1,101 @@
<?php
/**
* {@table.php_name} base store for table: {@name}
*/
namespace {@appNamespace}\Store\Base;
use b8\Database;
use b8\Exception\HttpException;
use {@appNamespace}\Store;
use {@appNamespace}\Model\{@table.php_name};
/**
* {@table.php_name} Base Store
*/
class {@table.php_name}StoreBase extends Store
{
protected $tableName = '{@name}';
protected $modelName = '\{@appNamespace}\Model\{@table.php_name}';
{if table.primary_key}
protected $primaryKey = '{@table.primary_key.column}';
public function getByPrimaryKey($value, $useConnection = 'read')
{
return $this->getBy{@table.primary_key.php_name}($value, $useConnection);
}
{/if}
{ifnot table.primary_key}
public function getByPrimaryKey($value, $useConnection = 'read')
{
throw new \Exception('getByPrimaryKey is not implemented for this store, as the table has no primary key.');
}
{/ifnot}
{loop table.columns}
{if item.unique_indexed}
public function getBy{@item.php_name}($value, $useConnection = 'read')
{
if (is_null($value)) {
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$query = 'SELECT * FROM `{@parent.name}` WHERE `{@item.name}` = :{@item.name} LIMIT 1';
$stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':{@item.name}', $value);
if ($stmt->execute()) {
if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return new {@parent.table.php_name}($data);
}
}
return null;
}
{/if}
{if item.many_indexed}
public function getBy{@item.php_name}($value, $limit = 1000, $useConnection = 'read')
{
if (is_null($value)) {
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
{if counts}
$query = 'SELECT COUNT(*) AS cnt FROM `{@parent.name}` WHERE `{@item.name}` = :{@item.name}';
$stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':{@item.name}', $value);
if ($stmt->execute()) {
$res = $stmt->fetch(\PDO::FETCH_ASSOC);
$count = (int)$res['cnt'];
} else {
$count = 0;
}
{/if}
$query = 'SELECT * FROM `{@parent.name}` WHERE `{@item.name}` = :{@item.name} LIMIT :limit';
$stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':{@item.name}', $value);
$stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT);
if ($stmt->execute()) {
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$map = function ($item) {
return new {@parent.table.php_name}($item);
};
$rtn = array_map($map, $res);
{ifnot counts}$count = count($rtn);{/ifnot}
return array('items' => $rtn, 'count' => $count);
} else {
return array('items' => array(), 'count' => 0);
}
}
{/if}
{/loop}
}

View file

@ -0,0 +1,20 @@
<?php
/**
* {@table.php_name} controller for table: {@name}
*/
namespace {@appNamespace}\Controller;
use {@appNamespace}\Controller\Base\{@table.php_name}ControllerBase;
/**
* {@table.php_name} Controller
* @uses {@appNamespace}\Controller\Base\{@table.php_name}Base
* @uses {@appNamespace}\Store\{@table.php_name}Store
* @uses {@appNamespace}\Model\{@table.php_name}
*/
class {@table.php_name}Controller extends {@table.php_name}ControllerBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
}

View file

@ -0,0 +1,18 @@
<?php
/**
* {@table.php_name} model for table: {@name}
*/
namespace {@appNamespace}\Model;
use {@appNamespace}\Model\Base\{@table.php_name}Base;
/**
* {@table.php_name} Model
* @uses {@appNamespace}\Model\Base\{@table.php_name}Base
*/
class {@table.php_name} extends {@table.php_name}Base
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
}

View file

@ -0,0 +1,18 @@
<?php
/**
* {@table.php_name} store for table: {@name}
*/
namespace {@appNamespace}\Store;
use {@appNamespace}\Store\Base\{@table.php_name}StoreBase;
/**
* {@table.php_name} Store
* @uses {@appNamespace}\Store\Base\{@table.php_name}StoreBase
*/
class {@table.php_name}Store extends {@table.php_name}StoreBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
}

View file

@ -0,0 +1,428 @@
<?php
/**
* Database generator updates a database to match a set of Models.
*/
namespace b8\Database;
use b8\Database;
class Generator
{
protected $_db = null;
protected $_map = null;
protected $_tables = null;
protected $_ns = null;
protected $_path = null;
public function __construct(Database $db, $namespace, $path)
{
$this->_db = $db;
$this->_ns = $namespace;
$this->_path = $path;
$this->_map = new Map($this->_db);
$this->_tables = $this->_map->generate();
}
public function generate()
{
error_reporting(E_ERROR & E_WARNING);
$di = new \DirectoryIterator($this->_path);
$this->_todo = array(
'drop_fk' => array(),
'drop_index'=> array(),
'create' => array(),
'alter' => array(),
'add_index' => array(),
'add_fk' => array(),
);
foreach($di as $file)
{
if($file->isDot())
{
continue;
}
$fileName = explode('.', $file->getBasename());
if ($fileName[count($fileName)-1] != 'php')
{
continue;
}
$modelName = '\\' . $this->_ns . '\\Model\\Base\\' . str_replace('.php', '', $file->getFilename());
require_once($this->_path . $file->getFilename());
$model = new $modelName();
$columns = $model->columns;
$indexes = $model->indexes;
$foreignKeys = $model->foreignKeys;
$tableName = $model->getTableName();
if(!array_key_exists($tableName, $this->_tables))
{
$this->_createTable($tableName, $columns, $indexes, $foreignKeys);
continue;
}
else
{
$table = $this->_tables[$tableName];
$this->_updateColumns($tableName, $table, $columns);
$this->_updateRelationships($tableName, $table, $foreignKeys);
$this->_updateIndexes($tableName, $table, $indexes);
}
}
print 'DROP FK: ' . count($this->_todo['drop_fk']) . PHP_EOL;
print 'DROP INDEX: ' . count($this->_todo['drop_index']) . PHP_EOL;
print 'CREATE TABLE: ' . count($this->_todo['create']) . PHP_EOL;
print 'ALTER TABLE: ' . count($this->_todo['alter']) . PHP_EOL;
print 'ADD INDEX: ' . count($this->_todo['add_index']) . PHP_EOL;
print 'ADD FK: ' . count($this->_todo['add_fk']) . PHP_EOL;
$order = array_keys($this->_todo);
while($group = array_shift($order))
{
if(!isset($this->_todo[$group]) || !is_array($this->_todo[$group]) || !count($this->_todo[$group]))
{
continue;
}
foreach($this->_todo[$group] as $query)
{
try
{
//print $query . PHP_EOL;
$this->_db->query($query);
}
catch(\Exception $ex)
{
print 'FAILED TO EXECUTE: ' . $query . PHP_EOL;
print $ex->getMessage().PHP_EOL.PHP_EOL;
}
}
}
}
protected function _createTable($tbl, $cols, $idxs, $fks)
{
$defs = array();
$pks = array();
foreach($cols as $colName => $def)
{
$add = '`' . $colName . '` ' . $def['type'];
switch($def['type'])
{
case 'text':
case 'longtext':
case 'mediumtext':
case 'date':
case 'datetime':
case 'float':
$add .= '';
break;
default:
$add .= !empty($def['length']) ? '(' . $def['length'] . ')' : '';
break;
}
if(empty($def['nullable']) || !$def['nullable'])
{
$add .= ' NOT NULL ';
}
if(!empty($def['default']))
{
$add .= ' DEFAULT ' . (is_numeric($def['default']) ? $def['default'] : '\'' . $def['default'] . '\'');
}
if(!empty($def['auto_increment']) && $def['auto_increment'])
{
$add .= ' AUTO_INCREMENT ';
}
if(!empty($def['primary_key']) && $def['primary_key'])
{
$pks[] = '`' . $colName . '`';
}
$defs[] = $add;
}
if(count($pks))
{
$defs[] = 'PRIMARY KEY (' . implode(', ', $pks) . ')';
}
$stmt = 'CREATE TABLE `' . $tbl . '` (' . PHP_EOL;
$stmt .= implode(", \n", $defs);
$stmt .= PHP_EOL . ') ENGINE=InnoDB DEFAULT CHARSET=utf8';
$stmt .= PHP_EOL;
$this->_todo['create'][] = $stmt;
foreach($idxs as $name => $idx)
{
$this->_addIndex($tbl, $name, $idx);
}
foreach($fks as $name => $fk)
{
$this->_addFk($tbl, $name, $fk);
}
}
protected function _updateColumns($tableName, $table, $columns)
{
$currentColumns = $table['columns'];
while($column = array_shift($currentColumns))
{
if(!array_key_exists($column['name'], $columns))
{
$this->_todo['alter'][$tableName.'.'.$column['name']] = 'ALTER TABLE `' . $tableName . '` DROP COLUMN `' . $column['name'] . '`';
}
else
{
$model = $columns[$column['name']];
$model['nullable'] = !isset($model['nullable']) ? false : $model['nullable'];
$model['default'] = !isset($model['default']) ? false : $model['default'];
$model['auto_increment'] = !isset($model['auto_increment']) ? false : $model['auto_increment'];
$model['primary_key'] = !isset($model['primary_key']) ? false : $model['primary_key'];
$column['is_primary_key'] = !isset($column['is_primary_key']) ? false : $column['is_primary_key'];
if( $column['type'] != $model['type'] ||
($column['length'] != $model['length'] && !in_array($model['type'], array('text', 'longtext', 'mediumtext', 'date', 'datetime', 'float'))) ||
$column['null'] != $model['nullable'] ||
$column['default'] != $model['default'] ||
$column['auto'] != $model['auto_increment'])
{
$this->_updateColumn($tableName, $column['name'], $column['name'], $model);
}
}
unset($columns[$column['name']]);
}
if(count($columns))
{
foreach($columns as $name => $model)
{
// Check if we're renaming a column:
if(isset($model['rename']))
{
unset($this->_todo['alter'][$tableName.'.'.$model['rename']]);
$this->_updateColumn($tableName, $model['rename'], $name, $model);
continue;
}
// New column
$add = '`' . $name . '` ' . $model['type'];;
switch($model['type'])
{
case 'text':
case 'longtext':
case 'mediumtext':
case 'date':
case 'datetime':
case 'float':
$add .= '';
break;
default:
$add .= !empty($model['length']) ? '(' . $model['length'] . ')' : '';
break;
}
if(empty($model['nullable']) || !$model['nullable'])
{
$add .= ' NOT NULL ';
}
if(!empty($model['default']))
{
$add .= ' DEFAULT ' . (is_numeric($model['default']) ? $model['default'] : '\'' . $model['default'] . '\'');
}
if(!empty($model['auto_increment']) && $model['auto_increment'])
{
$add .= ' AUTO_INCREMENT ';
}
if(!empty($model['primary_key']) && $model['primary_key'] && !isset($table['indexes']['PRIMARY']))
{
$add .= ' PRIMARY KEY ';
}
$this->_todo['alter'][] = 'ALTER TABLE `' . $tableName . '` ADD COLUMN ' . $add;
}
}
}
protected function _updateColumn($tableName, $prevName, $newName, $model)
{
$add = '`' . $newName . '` ' . $model['type'];;
switch($model['type'])
{
case 'text':
case 'longtext':
case 'mediumtext':
case 'date':
case 'datetime':
case 'float':
$add .= '';
break;
default:
$add .= !empty($model['length']) ? '(' . $model['length'] . ')' : '';
break;
}
if(empty($model['nullable']) || !$model['nullable'])
{
$add .= ' NOT NULL ';
}
if(!empty($model['default']))
{
$add .= ' DEFAULT ' . (is_numeric($model['default']) ? $model['default'] : '\'' . $model['default'] . '\'');
}
if(!empty($model['auto_increment']) && $model['auto_increment'])
{
$add .= ' AUTO_INCREMENT ';
}
$this->_todo['alter'][] = 'ALTER TABLE `' . $tableName . '` CHANGE COLUMN `' . $prevName . '` ' . $add;
}
protected function _updateRelationships($tableName, $table, $foreignKeys)
{
$current = $table['relationships']['toOne'];
while($foreignKey = array_shift($current))
{
if(!array_key_exists($foreignKey['fk_name'], $foreignKeys))
{
$this->_dropFk($tableName, $foreignKey['fk_name']);
}
elseif( $foreignKey['from_col'] != $foreignKeys[$foreignKey['fk_name']]['local_col'] ||
$foreignKey['table'] != $foreignKeys[$foreignKey['fk_name']]['table'] ||
$foreignKey['col'] != $foreignKeys[$foreignKey['fk_name']]['col'] ||
$foreignKey['fk_update'] != $foreignKeys[$foreignKey['fk_name']]['update'] ||
$foreignKey['fk_delete'] != $foreignKeys[$foreignKey['fk_name']]['delete'])
{
$this->_alterFk($tableName, $foreignKey['fk_name'], $foreignKeys[$foreignKey['fk_name']]);
}
unset($foreignKeys[$foreignKey['fk_name']]);
}
if(count($foreignKeys))
{
foreach($foreignKeys as $name => $foreignKey)
{
// New column
$this->_addFk($tableName, $name, $foreignKey);
}
}
}
protected function _updateIndexes($tableName, $table, $indexes)
{
$current = $table['indexes'];
while($index = array_shift($current))
{
if(!array_key_exists($index['name'], $indexes))
{
$this->_dropIndex($tableName, $index['name']);
}
elseif( $index['unique'] != $indexes[$index['name']]['unique'] ||
$index['columns'] != $indexes[$index['name']]['columns'])
{
$this->_alterIndex($tableName, $index['name'], $index);
}
unset($indexes[$index['name']]);
}
if(count($indexes))
{
foreach($indexes as $name => $index)
{
if($name == 'PRIMARY')
{
continue;
}
// New index
$this->_addIndex($tableName, $name, $index);
}
}
}
protected function _addIndex($table, $name, $idx, $stage = 'add_index')
{
if($name == 'PRIMARY')
{
return;
}
$q = 'CREATE ' . (isset($idx['unique']) && $idx['unique'] ? 'UNIQUE' : '') . ' INDEX `' . $name . '` ON `' . $table . '` (' . $idx['columns'] . ')';
$this->_todo[$stage][] = $q;
}
protected function _alterIndex($table, $name, $idx, $stage = 'index')
{
$this->_dropIndex($table, $name, $stage);
$this->_addIndex($table, $name, $idx, $stage);
}
protected function _dropIndex($table, $idxName, $stage = 'drop_index')
{
if($idxName == 'PRIMARY')
{
return;
}
$q = 'DROP INDEX `' . $idxName . '` ON `' . $table . '`';
$this->_todo[$stage][] = $q;
}
protected function _addFk($table, $name, $fk)
{
$q = 'ALTER TABLE `' . $table . '` ADD CONSTRAINT `' . $name . '` FOREIGN KEY (`' . $fk['local_col'] . '`) REFERENCES `'.$fk['table'].'` (`'.$fk['col'].'`)';
if(!empty($fk['delete']))
{
$q .= ' ON DELETE ' . $fk['delete'] . ' ';
}
if(!empty($fk['update']))
{
$q .= ' ON UPDATE ' . $fk['update'] . ' ';
}
$this->_todo['add_fk'][] = $q;
}
protected function _alterFk($table, $name, $fk)
{
$this->_dropFk($table, $name);
$this->_addFk($table, $name, $fk);
}
protected function _dropFk($table, $name)
{
$q = 'ALTER TABLE `'.$table.'` DROP FOREIGN KEY `' . $name . '`';
$this->_todo['drop_fk'][] = $q;
}
}

258
src/B8Framework/Database/Map.php Executable file
View file

@ -0,0 +1,258 @@
<?php
namespace b8\Database;
use b8\Database;
class Map
{
protected $_db = null;
protected $_tables = array();
public function __construct(Database $db)
{
$this->_db = $db;
}
public function generate()
{
$tables = $this->_getTables();
foreach($tables as $table)
{
$this->_tables[$table] = array();
$this->_tables[$table]['php_name'] = $this->_generatePhpName($table);
}
$this->_getRelationships();
$this->_getColumns();
$this->_getIndexes();
return $this->_tables;
}
protected function _getTables()
{
$details = $this->_db->getDetails();
$rtn = array();
foreach($this->_db->query('SHOW TABLES')->fetchAll(\PDO::FETCH_ASSOC) as $tbl)
{
$rtn[] = $tbl['Tables_in_' . $details['db']];
}
return $rtn;
}
protected function _getRelationships()
{
foreach($this->_tables as $table => $t)
{
$res = $this->_db->query('SHOW CREATE TABLE `'.$table.'`')->fetchAll(\PDO::FETCH_ASSOC);
foreach($res as $r)
{
$str = $r['Create Table'];
$matches = array();
if(preg_match_all('/CONSTRAINT\s+\`([a-zA-Z0-9_]+)\`\s+FOREIGN\s+KEY\s+\(\`([a-zA-Z0-9_]+)\`\)\s+REFERENCES\s+\`([a-zA-Z0-9_]+)\`\s+\(\`([a-zA-Z0-9_]+)\`\)(\s+ON (DELETE|UPDATE) (SET NULL|NO ACTION|CASCADE|RESTRICT))?(\s+ON (DELETE|UPDATE) (SET NULL|NO ACTION|CASCADE|RESTRICT))?/', $str, $matches))
{
for($i = 0; $i < count($matches[0]); $i++)
{
$fromTable = $table;
$fromCol = $matches[2][$i];
$toTable = $matches[3][$i];
$toCol = $matches[4][$i];
$fkName = $matches[1][$i];
$fk = array();
if(isset($matches[6][$i]))
{
$fk[$matches[6][$i]] = $matches[7][$i];
}
if(isset($matches[9][$i]))
{
$fk[$matches[9][$i]] = $matches[10][$i];
}
$fk['UPDATE'] = empty($fk['UPDATE']) ? '' : $fk['UPDATE'];
$fk['DELETE'] = empty($fk['DELETE']) ? '' : $fk['DELETE'];
if(isset($this->_tables[$fromTable]) && isset($this->_tables[$toTable]))
{
$phpName = $this->_generateFkName($fromCol, $this->_tables[$fromTable]['php_name']);
$this->_tables[$fromTable]['relationships']['toOne'][$fromCol] = array('fk_name' => $fkName, 'fk_delete' => $fk['DELETE'], 'fk_update' => $fk['UPDATE'], 'table_php_name' => $this->_tables[$toTable]['php_name'], 'from_col_php' => $this->_generatePhpName($fromCol), 'from_col' => $fromCol, 'php_name' => $phpName, 'table' => $toTable, 'col' => $toCol, 'col_php' => $this->_generatePhpName($toCol));
$phpName = $this->_generateFkName($fromCol, $this->_tables[$fromTable]['php_name']) . $this->_tables[$fromTable]['php_name'].'s';
$this->_tables[$toTable]['relationships']['toMany'][] = array('from_col_php' => $this->_generatePhpName($fromCol), 'php_name' => $phpName, 'thisCol' => $toCol, 'table' => $fromTable, 'table_php' => $this->_generatePhpName($fromTable), 'fromCol' => $fromCol, 'col_php' => $this->_generatePhpName($toCol));
}
}
}
}
}
}
protected function _getColumns()
{
foreach($this->_tables as $key => &$val)
{
$cols = array();
foreach($this->_db->query('DESCRIBE `' . $key . '`')->fetchAll(\PDO::FETCH_ASSOC) as $column)
{
$col = $this->_processColumn(array(), $column, $val);
$cols[$col['name']] = $col;
}
$val['columns'] = $cols;
}
}
protected function _getIndexes()
{
foreach($this->_tables as $key => &$val)
{
$indexes = array();
foreach($this->_db->query('SHOW INDEXES FROM `' . $key . '`')->fetchAll(\PDO::FETCH_ASSOC) as $idx)
{
if(!isset($indexes[$idx['Key_name']]))
{
$indexes[$idx['Key_name']] = array();
$indexes[$idx['Key_name']]['name'] = $idx['Key_name'];
$indexes[$idx['Key_name']]['unique'] = ($idx['Non_unique'] == '0') ? true : false;
$indexes[$idx['Key_name']]['columns'] = array();
}
$indexes[$idx['Key_name']]['columns'][$idx['Seq_in_index']] = $idx['Column_name'];
}
$indexes = array_map(function($idx)
{
ksort($idx['columns']);
$idx['columns'] = implode(', ', $idx['columns']);
return $idx;
}, $indexes);
$val['indexes'] = $indexes;
}
}
protected function _processColumn($col, $column, &$table)
{
$col['name'] = $column['Field'];
$col['php_name']= $this->_generatePhpName($col['name']);
$matches = array();
preg_match('/^([a-zA-Z]+)(\()?([0-9\,]+)?(\))?/', $column['Type'], $matches);
$col['type'] = strtolower($matches[1]);
if(isset($matches[3]))
{
$col['length'] = $matches[3];
}
$col['null'] = strtolower($column['Null']) == 'yes' ? true : false;
$col['auto'] = strtolower($column['Extra']) == 'auto_increment' ? true : false;
if ($column['Default'] == 'NULL' || is_null($column['Default'])) {
$col['default_is_null'] = true;
} else {
$col['default_is_null'] = false;
$col['default'] = $column['Default'];
}
if(!empty($column['Key']))
{
if($column['Key'] == 'PRI')
{
$col['is_primary_key'] = true;
$table['primary_key'] = array('column' => $col['name'], 'php_name' => $col['php_name']);
}
if($column['Key'] == 'PRI' || $column['Key'] == 'UNI')
{
$col['unique_indexed'] = true;
}
else
{
$col['many_indexed'] = true;
}
}
$col['validate']= array();
if(!$col['null'])
{
$col['validate_null'] = true;
}
switch($col['type'])
{
case 'tinyint':
case 'smallint':
case 'int':
case 'mediumint':
case 'bigint':
$col['php_type'] = 'int';
$col['to_php'] = '_sqlToInt';
$col['validate_int']= true;
break;
case 'float':
case 'decimal':
$col['php_type'] = 'float';
$col['to_php'] = '_sqlToFloat';
$col['validate_float'] = true;
break;
case 'datetime':
case 'date':
$col['php_type'] = 'DateTime';
$col['to_php'] = '_sqlToDateTime';
$col['to_sql'] = '_dateTimeToSql';
$col['validate_date'] = true;
break;
case 'varchar':
case 'text':
default:
$col['php_type'] = 'string';
$col['validate_string'] = true;
break;
}
return $col;
}
protected function _generatePhpName($sqlName)
{
$rtn = $sqlName;
$rtn = str_replace('_', ' ', $rtn);
$rtn = ucwords($rtn);
$rtn = str_replace(' ', '', $rtn);
return $rtn;
}
protected function _generateFkName($sqlName, $tablePhpName)
{
$fkMethod = substr($sqlName, 0, strripos($sqlName, '_'));
if(empty($fkMethod))
{
$fkMethod = (substr(strtolower($sqlName), -2) == 'id') ? substr($sqlName, 0, -2) : $tablePhpName;
}
$fkMethod = str_replace('_', ' ', $fkMethod);
$fkMethod = ucwords($fkMethod);
$fkMethod = str_replace(' ', '', $fkMethod);
return $fkMethod;
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace b8\Exception;
class HttpException extends \Exception
{
protected $errorCode = 500;
protected $statusMessage = 'Internal Server Error';
public function getErrorCode()
{
return $this->errorCode;
}
public function getStatusMessage()
{
return $this->statusMessage;
}
public function getHttpHeader()
{
return 'HTTP/1.1 ' . $this->errorCode . ' ' . $this->statusMessage;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace b8\Exception\HttpException;
use b8\Exception\HttpException;
class BadRequestException extends HttpException
{
protected $errorCode = 400;
protected $statusMessage = 'Bad Request';
}

View file

@ -0,0 +1,10 @@
<?php
namespace b8\Exception\HttpException;
use b8\Exception\HttpException;
class ForbiddenException extends HttpException
{
protected $errorCode = 403;
protected $statusMessage = 'Forbidden';
}

View file

@ -0,0 +1,10 @@
<?php
namespace b8\Exception\HttpException;
use b8\Exception\HttpException;
class NotAuthorizedException extends HttpException
{
protected $errorCode = 401;
protected $statusMessage = 'Not Authorized';
}

View file

@ -0,0 +1,10 @@
<?php
namespace b8\Exception\HttpException;
use b8\Exception\HttpException;
class NotFoundException extends HttpException
{
protected $errorCode = 404;
protected $statusMessage = 'Not Found';
}

View file

@ -0,0 +1,8 @@
<?php
namespace b8\Exception\HttpException;
use b8\Exception\HttpException;
class ServerErrorException extends HttpException
{
}

View file

@ -0,0 +1,10 @@
<?php
namespace b8\Exception\HttpException;
use b8\Exception\HttpException;
class ValidationException extends HttpException
{
protected $errorCode = 400;
protected $statusMessage = 'Bad Request';
}

44
src/B8Framework/Form.php Executable file
View file

@ -0,0 +1,44 @@
<?php
namespace b8;
use b8\Form\FieldSet,
b8\View;
class Form extends FieldSet
{
protected $_action = '';
protected $_method = 'POST';
public function getAction()
{
return $this->_action;
}
public function setAction($action)
{
$this->_action = $action;
}
public function getMethod()
{
return $this->_method;
}
public function setMethod($method)
{
$this->_method = $method;
}
protected function _onPreRender(View &$view)
{
$view->action = $this->getAction();
$view->method = $this->getMethod();
parent::_onPreRender($view);
}
public function __toString()
{
return $this->render();
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace b8\Form;
class ControlGroup extends FieldSet
{
}

118
src/B8Framework/Form/Element.php Executable file
View file

@ -0,0 +1,118 @@
<?php
namespace b8\Form;
use b8\View;
use b8\Config;
abstract class Element
{
protected $_name;
protected $_id;
protected $_label;
protected $_css;
protected $_ccss;
protected $_parent;
public function __construct($name = null)
{
if(!is_null($name))
{
$this->setName($name);
}
}
public function getName()
{
return $this->_name;
}
public function setName($name)
{
$this->_name = strtolower(preg_replace('/([^a-zA-Z0-9_\-])/', '', $name));
return $this;
}
public function getId()
{
return !$this->_id ? 'element-'.$this->_name : $this->_id;
}
public function setId($id)
{
$this->_id = $id;
return $this;
}
public function getLabel()
{
return $this->_label;
}
public function setLabel($label)
{
$this->_label = $label;
return $this;
}
public function getClass()
{
return $this->_css;
}
public function setClass($class)
{
$this->_css = $class;
return $this;
}
public function getContainerClass()
{
return $this->_ccss;
}
public function setContainerClass($class)
{
$this->_ccss = $class;
return $this;
}
public function setParent(Element $parent)
{
$this->_parent = $parent;
return $this;
}
public function render($viewFile = null)
{
$viewPath = Config::getInstance()->get('b8.view.path');
if(is_null($viewFile))
{
$class = explode('\\', get_called_class());
$viewFile = end($class);
}
if(file_exists($viewPath . 'Form/' . $viewFile . '.phtml'))
{
$view = new View('Form/' . $viewFile);
}
else
{
$view = new View($viewFile, B8_PATH . 'Form/View/');
}
$view->name = $this->getName();
$view->id = $this->getId();
$view->label = $this->getLabel();
$view->css = $this->getClass();
$view->ccss = $this->getContainerClass();
$view->parent = $this->_parent;
$this->_onPreRender($view);
return $view->render();
}
abstract protected function _onPreRender(View &$view);
}

View file

@ -0,0 +1,19 @@
<?php
namespace b8\Form\Element;
use b8\Form\Input,
b8\View;
class Button extends Input
{
public function validate()
{
return true;
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->type = 'button';
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace b8\Form\Element;
use b8\View,
b8\Form\Input;
class Checkbox extends Input
{
protected $_checked;
protected $_checkedValue;
public function getCheckedValue()
{
return $this->_checkedValue;
}
public function setCheckedValue($value)
{
$this->_checkedValue = $value;
}
public function setValue($value)
{
if(is_bool($value) && $value == true)
{
$this->_value = $this->getCheckedValue();
$this->_checked = true;
return;
}
if($value == $this->getCheckedValue())
{
$this->_value = $this->getCheckedValue();
$this->_checked = true;
return;
}
$this->_value = $value;
$this->_checked = false;
}
public function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->checkedValue = $this->getCheckedValue();
$view->checked = $this->_checked;
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace b8\Form\Element;
use b8\Form\FieldSet;
class CheckboxGroup extends FieldSet
{
}

View file

@ -0,0 +1,28 @@
<?php
namespace b8\Form\Element;
use b8\Form\Element\Hidden,
b8\View;
class Csrf extends Hidden
{
protected $_rows = 4;
public function validate()
{
if($this->_value != $_COOKIE[$this->getName()])
{
return false;
}
return true;
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$csrf = md5(microtime(true));
$view->csrf = $csrf;
setcookie($this->getName(), $csrf);
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace b8\Form\Element;
use b8\View;
class Email extends Text
{
public function render($viewFile = null)
{
return parent::render(($viewFile ? $viewFile : 'Text'));
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->type = 'email';
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace b8\Form\Element;
use b8\Form\Input,
b8\View;
class Hidden extends Input
{
}

View file

@ -0,0 +1,19 @@
<?php
namespace b8\Form\Element;
use b8\Form\Element\Text,
b8\View;
class Password extends Text
{
public function render($viewFile = null)
{
return parent::render(($viewFile ? $viewFile : 'Text'));
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->type = 'password';
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace b8\Form\Element;
use b8\Form\Element\Select;
class Radio extends Select
{
}

View file

@ -0,0 +1,21 @@
<?php
namespace b8\Form\Element;
use b8\View,
b8\Form\Input;
class Select extends Input
{
protected $_options = array();
public function setOptions(array $options)
{
$this->_options = $options;
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->options = $this->_options;
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace b8\Form\Element;
use b8\Form\Element\Button,
b8\View;
class Submit extends Button
{
protected $_value = 'Submit';
public function render($viewFile = null)
{
return parent::render(($viewFile ? $viewFile : 'Button'));
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->type = 'submit';
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace b8\Form\Element;
use b8\Form\Input,
b8\View;
class Text extends Input
{
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->type = 'text';
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace b8\Form\Element;
use b8\Form\Element\Text,
b8\View;
class TextArea extends Text
{
protected $_rows = 4;
public function getRows()
{
return $this->_rows;
}
public function setRows($rows)
{
$this->_rows = $rows;
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->rows = $this->getRows();
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace b8\Form\Element;
use b8\View;
class Url extends Text
{
public function render($viewFile = null)
{
return parent::render(($viewFile ? $viewFile : 'Text'));
}
protected function _onPreRender(View &$view)
{
parent::_onPreRender($view);
$view->type = 'url';
}
}

114
src/B8Framework/Form/FieldSet.php Executable file
View file

@ -0,0 +1,114 @@
<?php
namespace b8\Form;
use b8\Form\Element,
b8\Form\Input,
b8\View;
class FieldSet extends Element
{
protected $_children = array();
public function getValues()
{
$rtn = array();
foreach($this->_children as $field)
{
if($field instanceof FieldSet)
{
$fieldName = $field->getName();
if(empty($fieldName))
{
$rtn = array_merge($rtn, $field->getValues());
}
else
{
$rtn[$fieldName] = $field->getValues();
}
}
elseif($field instanceof Input)
{
if($field->getName())
{
$rtn[$field->getName()] = $field->getValue();
}
}
}
return $rtn;
}
public function setValues(array $values)
{
foreach($this->_children as $field)
{
if($field instanceof FieldSet)
{
$fieldName = $field->getName();
if(empty($fieldName) || !isset($values[$fieldName]))
{
$field->setValues($values);
}
else
{
$field->setValues($values[$fieldName]);
}
}
elseif($field instanceof Input)
{
$fieldName = $field->getName();
if(isset($values[$fieldName]))
{
$field->setValue($values[$fieldName]);
}
}
}
}
public function addField(Element $field)
{
$this->_children[$field->getName()] = $field;
$field->setParent($this);
}
public function validate()
{
$rtn = true;
foreach($this->_children as $child)
{
if(!$child->validate())
{
$rtn = false;
}
}
return $rtn;
}
protected function _onPreRender(View &$view)
{
$rendered = array();
foreach($this->_children as $child)
{
$rendered[] = $child->render();
}
$view->children = $rendered;
}
public function getChildren()
{
return $this->_children;
}
public function getChild($fieldName)
{
return $this->_children[$fieldName];
}
}

124
src/B8Framework/Form/Input.php Executable file
View file

@ -0,0 +1,124 @@
<?php
namespace b8\Form;
use b8\Form\Element,
b8\View;
class Input extends Element
{
protected $_required = false;
protected $_pattern;
protected $_validator;
protected $_value;
protected $_error;
protected $_customError = false;
public static function create($name, $label, $required = false)
{
$el = new static();
$el->setName($name);
$el->setLabel($label);
$el->setRequired($required);
return $el;
}
public function getValue()
{
return $this->_value;
}
public function setValue($value)
{
$this->_value = $value;
return $this;
}
public function getRequired()
{
return $this->_required;
}
public function setRequired($required)
{
$this->_required = (bool)$required;
return $this;
}
public function getValidator()
{
return $this->_validator;
}
public function setValidator($validator)
{
if(is_callable($validator) || $validator instanceof \Closure)
{
$this->_validator = $validator;
}
return $this;
}
public function getPattern()
{
return $this->_pattern;
}
public function setPattern($pattern)
{
$this->_pattern = $pattern;
return $this;
}
public function validate()
{
if($this->getRequired() && empty($this->_value))
{
$this->_error = $this->getLabel() . ' is required.';
return false;
}
if($this->getPattern() && !preg_match('/'.$this->getPattern().'/', $this->_value))
{
$this->_error = 'Invalid value entered.';
return false;
}
$validator = $this->getValidator();
if(is_callable($validator))
{
try
{
call_user_func_array($validator, array($this->_value));
}
catch(\Exception $ex)
{
$this->_error = $ex->getMessage();
return false;
}
}
if ($this->_customError) {
return false;
}
return true;
}
public function setError($message)
{
$this->_customError = true;
$this->_error = $message;
return $this;
}
protected function _onPreRender(View &$view)
{
$view->value = $this->getValue();
$view->error = $this->_error;
$view->pattern = $this->_pattern;
$view->required = $this->_required;
}
}

View file

@ -0,0 +1 @@
<input class="btn <?php print $css; ?>" type="<?php print $type; ?>" value="<?php print $value; ?>">

View file

@ -0,0 +1,17 @@
<?php if(!($parent instanceof \b8\Form\Element\CheckboxGroup)): ?>
<div class="control-group <?php print $ccss ?> <?php print (isset($error) ? 'error' : ''); ?>">
<div class="controls">
<div class="checkbox">
<?php endif; ?>
<label class="checkbox <?php print $css; ?>" for="<?php print $id ?>">
<input type="checkbox" id="<?php print $id; ?>" name="<?php print $name; ?>" value="<?php print $checkedValue; ?>" <?php print ($checked ? 'checked' : ''); ?> <?php print $required ? 'required' : '' ?>>
<?php print $label; ?>
</label>
<?php if(isset($error)): ?>
<span class="help-block"><?php print $error; ?></span>
<?php endif; ?>
<?php if(!($parent instanceof \b8\Form\Element\CheckboxGroup)): ?>
</div>
</div>
</div>
<?php endif; ?>

View file

@ -0,0 +1,11 @@
<div class="control-group <?php print $css; ?>">
<?php if($label): ?>
<label class="control-label"><?php print $label; ?></label>
<?php endif; ?>
<div class="controls">
<?php foreach($children as $field): ?>
<?php print $field; ?>
<?php endforeach; ?>
</div>
</div>

View file

@ -0,0 +1,6 @@
<div class="control-group <?php print $css; ?>">
<?php foreach($children as $field): ?>
<?php print $field; ?>
<?php endforeach; ?>
</div>

View file

@ -0,0 +1 @@
<input type="hidden" id="<?php print $id; ?>" name="<?php print $name; ?>" value="<?php print $csrf; ?>">

View file

@ -0,0 +1,9 @@
<fieldset class="row <?php print $css; ?>">
<?php if($label): ?>
<legend><?php print $label; ?></legend>
<?php endif; ?>
<?php foreach($children as $field): ?>
<?php print $field; ?>
<?php endforeach; ?>
</fieldset>

View file

@ -0,0 +1,6 @@
<form id="<?php print $id; ?>" class="<?php print $css; ?>" action="<?php print $action; ?>" method="<?php print $method; ?>">
<?php foreach($children as $field): ?>
<?php print $field; ?>
<?php endforeach; ?>
</form>

View file

@ -0,0 +1 @@
<input type="hidden" id="<?php print $id; ?>" name="<?php print $name; ?>" value="<?php print $value; ?>">

View file

@ -0,0 +1,18 @@
<div id="<?php print $id; ?>" class="control-group <?php print $ccss; ?>">
<?php if($label): ?>
<label class="control-label"><?php print $label; ?></label>
<?php endif; ?>
<div class="controls">
<?php foreach($options as $val => $lbl): ?>
<label class="radio" for="radio-<?php print $id; ?>-<?php print $val; ?>">
<input type="radio" id="radio-<?php print $id; ?>-<?php print $val; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>" value="<?php print $val; ?>" <?php print ($value == $val) ? ' checked="checked"' : ''; ?> <?php print $required ? 'required' : '' ?>>
<?php print $lbl; ?>
</label>
<?php endforeach; ?>
<?php if(isset($error)): ?>
<span class="help-block"><?php print $error; ?></span>
<?php endif; ?>
</div>
</div>

View file

@ -0,0 +1,17 @@
<div class="control-group <?php print $ccss; ?>">
<?php if($label): ?>
<label class="control-label" for="<?php print $id ?>"><?php print $label; ?></label>
<?php endif; ?>
<div class="controls">
<select id="<?php print $id; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>">
<?php foreach($options as $val => $lbl): ?>
<option value="<?php print $val; ?>" <?php print ($value == $val) ? ' selected="selected"' : ''; ?>><?php print $lbl; ?></option>
<?php endforeach; ?>
</select>
<?php if(isset($error)): ?>
<span class="help-block"><?php print $error; ?></span>
<?php endif; ?>
</div>
</div>

View file

@ -0,0 +1,13 @@
<div class="control-group <?php print $ccss; ?> <?php print (isset($error) ? 'error' : ''); ?>">
<?php if($label): ?>
<label class="control-label" for="<?php print $id ?>"><?php print $label; ?></label>
<?php endif; ?>
<div class="controls">
<input id="<?php print $id; ?>" type="<?php print $type; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>" <?php print isset($value) ? ' value="' . $value . '"' : '' ?> <?php print isset($pattern) ? ' pattern="' . $pattern . '"' : '' ?> <?php print $required ? ' required' : '' ?>>
<?php if(isset($error)): ?>
<span class="help-block"><?php print $error; ?></span>
<?php endif; ?>
</div>
</div>

View file

@ -0,0 +1,13 @@
<div class="control-group <?php print $ccss; ?> <?php print (isset($error) ? 'error' : ''); ?>">
<?php if($label): ?>
<label class="control-label" for="<?php print $id ?>"><?php print $label; ?></label>
<?php endif; ?>
<div class="controls">
<textarea rows="<?php print $rows; ?>" id="<?php print $id; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>" <?php print $required ? ' required' : '' ?>><?php print isset($value) ? $value : '' ?></textarea>
<?php if(isset($error)): ?>
<span class="help-block"><?php print $error; ?></span>
<?php endif; ?>
</div>
</div>

149
src/B8Framework/Http/Request.php Executable file
View file

@ -0,0 +1,149 @@
<?php
namespace b8\Http;
class Request
{
/**
* @var array
*/
protected $params = array();
/**
* Request data.
*/
protected $data = array();
/**
* Set up the request.
*/
public function __construct()
{
$this->parseInput();
$this->data['path'] = $this->getRequestPath();
$this->data['parts'] = array_values(array_filter(explode('/', $this->data['path'])));
}
protected function getRequestPath()
{
$path = '';
// Start out with the REQUEST_URI:
if (!empty($_SERVER['REQUEST_URI'])) {
$path = $_SERVER['REQUEST_URI'];
}
if ($_SERVER['SCRIPT_NAME'] != $_SERVER['REQUEST_URI']) {
$scriptPath = str_replace('/index.php', '', $_SERVER['SCRIPT_NAME']);
$path = str_replace($scriptPath, '', $path);
}
// Remove index.php from the URL if it is present:
$path = str_replace(array('/index.php', 'index.php'), '', $path);
// Also cut out the query string:
$path = explode('?', $path);
$path = array_shift($path);
return $path;
}
/**
* Parse incoming variables, incl. $_GET, $_POST and also reads php://input for PUT/DELETE.
*/
protected function parseInput()
{
$params = $_REQUEST;
if(!isset($_SERVER['REQUEST_METHOD']) || in_array($_SERVER['REQUEST_METHOD'], array('PUT', 'DELETE')))
{
$vars = file_get_contents('php://input');
if(!is_string($vars) || strlen(trim($vars)) === 0)
{
$vars = '';
}
$inputData = array();
parse_str($vars, $inputData);
$params = array_merge($params, $inputData);
}
$this->setParams($params);
}
/**
* Returns all request parameters.
* @return array
*/
public function getParams()
{
return $this->params;
}
/**
* Return a specific request parameter, or a default value if not set.
*/
public function getParam($key, $default = null)
{
if (isset($this->params[$key])) {
return $this->params[$key];
} else {
return $default;
}
}
/**
* Set or override a request parameter.
*/
public function setParam($key, $value = null)
{
$this->params[$key] = $value;
}
/**
* Set an array of request parameters.
*/
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
}
/**
* Un-set a specific parameter.
*/
public function unsetParam($key)
{
unset($this->params[$key]);
}
public function getMethod()
{
return strtoupper($_SERVER['REQUEST_METHOD']);
}
public function getPath()
{
return $this->data['path'];
}
public function getPathParts()
{
return $this->data['parts'];
}
public function isAjax()
{
if (!isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
return false;
}
if (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
return true;
}
return false;
}
}

132
src/B8Framework/Http/Response.php Executable file
View file

@ -0,0 +1,132 @@
<?php
namespace b8\Http;
class Response
{
protected $data = array();
public function __construct(Response $createFrom = null)
{
if (!is_null($createFrom)) {
$this->data = $createFrom->getData();
}
}
public function hasLayout()
{
return !isset($this->data['layout']) ? true : $this->data['layout'];
}
public function disableLayout()
{
$this->data['layout'] = false;
}
public function enableLayout()
{
$this->data['layout'] = true;
}
public function getData()
{
return $this->data;
}
public function setResponseCode($code)
{
$this->data['code'] = (int)$code;
}
public function setHeader($key, $val)
{
$this->data['headers'][$key] = $val;
}
public function clearHeaders()
{
$this->data['headers'] = array();
}
public function setContent($content)
{
$this->data['body'] = $content;
}
public function getContent()
{
return $this->data['body'];
}
public function flush()
{
$this->sendResponseCode();
if (isset($this->data['headers'])) {
foreach ($this->data['headers'] as $header => $val) {
header($header . ': ' . $val, true);
}
}
return $this->flushBody();
}
protected function sendResponseCode()
{
if (!isset($this->data['code'])) {
$this->data['code'] = 200;
}
switch ($this->data['code'])
{
// 300 class
case 301:
$text = 'Moved Permanently';
break;
case 302:
$text = 'Moved Temporarily';
break;
// 400 class errors
case 400:
$text = 'Bad Request';
break;
case 401:
$text = 'Not Authorized';
break;
case 403:
$text = 'Forbidden';
break;
case 404:
$text = 'Not Found';
break;
// 500 class errors
case 500:
$text = 'Internal Server Error';
break;
// OK
case 200:
default:
$text = 'OK';
break;
}
header('HTTP/1.1 ' . $this->data['code'] . ' ' . $text, true, $this->data['code']);
}
protected function flushBody()
{
if (isset($this->data['body'])) {
return $this->data['body'];
}
return '';
}
public function __toString()
{
return $this->flush();
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace b8\Http\Response;
use b8\Http\Response;
class JsonResponse extends Response
{
public function __construct(Response $createFrom = null)
{
parent::__construct($createFrom);
$this->setContent(array());
$this->setHeader('Content-Type', 'application/json');
}
public function hasLayout()
{
return false;
}
protected function flushBody()
{
if (isset($this->data['body'])) {
return json_encode($this->data['body']);
}
return json_encode(null);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace b8\Http\Response;
use b8\Http\Response;
class RedirectResponse extends Response
{
public function __construct(Response $createFrom = null)
{
parent::__construct($createFrom);
$this->setContent(null);
$this->setResponseCode(302);
}
public function hasLayout()
{
return false;
}
public function flush()
{
parent::flush();
die;
}
}

123
src/B8Framework/Http/Router.php Executable file
View file

@ -0,0 +1,123 @@
<?php
namespace b8\Http;
use b8\Application;
use b8\Config;
use b8\Http\Request;
class Router
{
/**
* @var \b8\Http\Request;
*/
protected $request;
/**
* @var \b8\Http\Config;
*/
protected $config;
/**
* @var \b8\Application
*/
protected $application;
/**
* @var array
*/
protected $routes = array(array('route' => '/:controller/:action', 'callback' => null, 'defaults' => array()));
public function __construct(Application $application, Request $request, Config $config)
{
$this->application = $application;
$this->request = $request;
$this->config = $config;
}
public function clearRoutes()
{
$this->routes = array();
}
/**
* @param string $route Route definition
* @param array $options
* @param callable $callback
* @throws \InvalidArgumentException
*/
public function register($route, $options = array(), $callback = null)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('$callback must be callable.');
}
array_unshift($this->routes, array('route' => $route, 'callback' => $callback, 'defaults' => $options));
}
public function dispatch()
{
foreach ($this->routes as $route) {
$pathParts = $this->request->getPathParts();
//-------
// Set up default values for everything:
//-------
$thisNamespace = 'Controller';
$thisController = null;
$thisAction = null;
if (array_key_exists('namespace', $route['defaults'])) {
$thisNamespace = $route['defaults']['namespace'];
}
if (array_key_exists('controller', $route['defaults'])) {
$thisController = $route['defaults']['controller'];
}
if (array_key_exists('action', $route['defaults'])) {
$thisAction = $route['defaults']['action'];
}
$routeParts = array_filter(explode('/', $route['route']));
$routeMatches = true;
while (count($routeParts)) {
$routePart = array_shift($routeParts);
$pathPart = array_shift($pathParts);
switch ($routePart) {
case ':namespace':
$thisNamespace = !is_null($pathPart) ? $pathPart : $thisNamespace;
break;
case ':controller':
$thisController = !is_null($pathPart) ? $pathPart : $thisController;
break;
case ':action':
$thisAction = !is_null($pathPart) ? $pathPart : $thisAction;
break;
default:
if ($routePart != $pathPart) {
$routeMatches = false;
}
}
if (!$routeMatches || !count($pathParts)) {
break;
}
}
$thisArgs = $pathParts;
if ($routeMatches) {
$route = array('namespace' => $thisNamespace, 'controller' => $thisController, 'action' => $thisAction, 'args' => $thisArgs, 'callback' => $route['callback']);
if ($this->application->isValidRoute($route)) {
return $route;
}
}
}
return null;
}
}

188
src/B8Framework/HttpClient.php Executable file
View file

@ -0,0 +1,188 @@
<?php
namespace b8;
class HttpClient
{
protected $_base = '';
protected $_params = array();
protected $_headers = array();
public function __construct($base = null)
{
$settings = Config::getInstance()->get('b8.http.client', array('base_url' => '', 'params' => array()));
$this->_base = $settings['base_url'];
$this->_params = isset($settings['params']) && is_array($settings['params']) ? $settings['params'] : array();
$this->_headers = array('Content-Type: application/x-www-form-urlencoded');
if(!is_null($base))
{
$this->_base = $base;
}
}
public function setHeaders(array $headers)
{
$this->_headers = $headers;
}
public function request($method, $uri, $params = array())
{
// Clean incoming:
$method = strtoupper($method);
$getParams = $this->_params;
if($method == 'GET' || $method == 'DELETE')
{
$getParams = array_merge($getParams, $params);
}
else
{
$bodyParams = is_array($params) ? http_build_query($params) : $params;
}
$getParams = http_build_query($getParams);
if(substr($uri, 0, 1) != '/' && !empty($this->_base))
{
$uri = '/' . $uri;
}
// Build HTTP context array:
$context = array();
$context['http']['user_agent'] = 'b8/1.0';
$context['http']['timeout'] = 30;
$context['http']['method'] = $method;
$context['http']['ignore_errors'] = true;
$context['http']['header'] = implode(PHP_EOL, $this->_headers);
if(in_array($method, array('PUT', 'POST')))
{
$context['http']['content'] = $bodyParams;
}
$uri .= '?' . $getParams;
$context = stream_context_create($context);
$result = file_get_contents($this->_base . $uri, false, $context);
$res = array();
$res['headers'] = $http_response_header;
$res['code'] = (int)preg_replace('/HTTP\/1\.[0-1] ([0-9]+)/', '$1', $res['headers'][0]);
$res['success'] = false;
$res['body'] = $this->_decodeResponse($result);
if($res['code'] >= 200 && $res['code'] < 300)
{
$res['success'] = true;
}
// Handle JSON responses:
foreach($res['headers'] as $header)
{
if(stripos($header, 'Content-Type') !== false || stripos($header, 'b8-Type') !== false)
{
if(stripos($header, 'application/json') !== false)
{
$res['text_body'] = $res['body'];
$res['body'] = json_decode($res['body'], true);
}
}
}
return $res;
}
public function get($uri, $params = array())
{
return $this->request('GET', $uri, $params);
}
public function put($uri, $params = array())
{
return $this->request('PUT', $uri, $params);
}
public function post($uri, $params = array())
{
return $this->request('POST', $uri, $params);
}
public function delete($uri, $params = array())
{
return $this->request('DELETE', $uri, $params);
}
protected function _decodeResponse($originalResponse)
{
$response = $originalResponse;
$body = '';
do
{
$line = $this->_readChunk($response);
if($line == PHP_EOL)
{
continue;
}
$length = hexdec(trim($line));
if(!is_int($length) || empty($response) || $line === false || $length < 1)
{
break;
}
do
{
$data = $this->_readChunk($response, $length);
// remove the amount received from the total length on the next loop
// it'll attempt to read that much less data
$length -= strlen($data);
// store in string for later use
$body .= $data;
// zero or less or end of connection break
if($length <= 0 || empty($response))
{
break;
}
}
while(true);
}
while(true);
if(empty($body))
{
$body = $originalResponse;
}
return $body;
}
function _readChunk(&$string, $len = 4096)
{
$rtn = '';
for($i = 0; $i <= $len; $i++)
{
if(empty($string))
{
break;
}
$char = $string[0];
$string = substr($string, 1);
$rtn .= $char;
if($char == PHP_EOL)
{
break;
}
}
return $rtn;
}
}

149
src/B8Framework/Image.php Executable file
View file

@ -0,0 +1,149 @@
<?php
namespace b8;
class Image
{
public static $cachePath = '/tmp/';
public static $sourcePath = './';
/**
* @var \Imagick
*/
protected $source;
public function __construct($imagePath)
{
$this->setSource(new \Imagick(self::$sourcePath . $imagePath));
}
/**
* @return \Imagick
*/
public function getSource()
{
return $this->source;
}
/**
* @param \Imagick $image
*/
public function setSource(\Imagick $image)
{
$this->source = $image;
}
public function render($media, $width, $height, $format = 'jpeg')
{
$cachePath = self::$cachePath . $media['fileId'] . '.' . $width . 'x' . $height . '.' . $format;
if(file_exists($cachePath) && 0)
{
$output = file_get_contents($cachePath);
}
else
{
$output = $this->doRender($media, $width, $height, $format);
file_put_contents($cachePath, $output);
}
return $output;
}
public function doRender($media, $width, $height, $format = 'jpeg')
{
$focal = !empty($media['focal_point']) ? $media['focal_point'] : array(0, 0);
$focalX = (int)$focal[0];
$focalY = (int)$focal[1];
$width = (int)$width;
$height = (int)$height;
$source = $this->getSource();
$sourceWidth = $source->getImageWidth();
$sourceHeight = $source->getImageHeight();
$sourceRatio = $sourceWidth / $sourceHeight;
$targetRatio = $height != 'auto' ? $width / $height : $sourceRatio;
$quads = $this->_getQuadrants($sourceWidth, $sourceHeight);
foreach($quads as $name => $l)
{
if($focalX >= $l[0] && $focalX <= $l[1] && $focalY >= $l[2] && $focalY <= $l[3])
{
$useQuad = $name;
}
}
if($sourceRatio <= $targetRatio)
{
$scale = $sourceWidth / $width;
}
else
{
$scale = $sourceHeight / $height;
}
$resizeWidth = (int)($sourceWidth / $scale);
$resizeHeight = (int)($sourceHeight / $scale);
if($height == 'auto')
{
$height = $resizeHeight;
}
$source->scaleImage($resizeWidth, $resizeHeight);
switch($useQuad)
{
case 'top_left':
$cropX = 0;
$cropY = 0;
break;
case 'top_right':
$cropX = ($resizeWidth - $width);
$cropY = 0;
break;
case 'middle_left':
$cropX = 0;
$cropY = ($resizeHeight - $height) / 2;
break;
case 'middle-right':
$cropX = ($resizeWidth - $width);
$cropY = ($resizeHeight - $height) / 2;
break;
case 'bottom_left':
$cropX = 0;
$cropY = ($resizeHeight - $height);
break;
case 'bottom_right':
$cropX = ($resizeWidth - $width);
$cropY = ($resizeHeight - $height);
break;
}
$source->cropImage($width, $height, $cropX, $cropY);
$source->setImageFormat($format);
return $source;
}
protected function _getQuadrants($x, $y)
{
$rtn = array();
$rtn['top_left'] = array(0, $x / 2, 0, $y / 3);
$rtn['top_right'] = array(($x / 2) + 1, $x, 0, $y / 3);
$rtn['middle_left'] = array(0, $y / 2, ($y / 3)+1, (($y / 3) * 2));
$rtn['middle_right'] = array(($x / 2) + 1, $x, ($y / 3)+1, (($y / 3) * 2));
$rtn['bottom_left'] = array(0, $y / 2, (($y / 3) * 2)+1, $y);
$rtn['bottom_right'] = array(($x / 2) + 1, $x, (($y / 3) * 2)+1, $y);
return $rtn;
}
}

224
src/B8Framework/Model.php Executable file
View file

@ -0,0 +1,224 @@
<?php
namespace b8;
use b8\Exception\HttpException;
use b8\Cache;
class Model
{
public static $sleepable = array();
protected $getters = array();
protected $setters = array();
protected $data = array();
protected $modified = array();
protected $tableName;
protected $cache;
public function __construct($initialData = array())
{
if(is_array($initialData))
{
$this->data = array_merge($this->data, $initialData);
}
$this->cache = Cache::getCache(Cache::TYPE_REQUEST);
}
public function getTableName()
{
return $this->tableName;
}
public function toArray($depth = 2, $currentDepth = 0)
{
if(isset(static::$sleepable) && is_array(static::$sleepable) && count(static::$sleepable))
{
$sleepable = static::$sleepable;
}
else
{
$sleepable = array_keys($this->getters);
}
$rtn = array();
foreach($sleepable as $property)
{
$rtn[$property] = $this->_propertyToArray($property, $currentDepth, $depth);
}
return $rtn;
}
protected function _propertyToArray($property, $currentDepth, $depth)
{
$rtn = null;
if(array_key_exists($property, $this->getters))
{
$method = $this->getters[$property];
$rtn = $this->{$method}();
if(is_object($rtn) || is_array($rtn))
{
$rtn = ($depth > $currentDepth) ? $this->_valueToArray($rtn, $currentDepth, $depth) : null;
}
}
return $rtn;
}
protected function _valueToArray($value, $currentDepth, $depth)
{
$rtn = null;
if(!is_null($value))
{
if(is_object($value) && method_exists($value, 'toArray'))
{
$rtn = $value->toArray($depth, $currentDepth + 1);
}
elseif(is_array($value))
{
$childArray = array();
foreach($value as $k => $v)
{
$childArray[$k] = $this->_valueToArray($v, $currentDepth + 1, $depth);
}
$rtn = $childArray;
}
else
{
$rtn = (is_string($value) && !mb_check_encoding($value, 'UTF-8')) ? mb_convert_encoding($value, 'UTF-8') : $value;
}
}
return $rtn;
}
public function getDataArray()
{
return $this->data;
}
public function getModified()
{
return $this->modified;
}
public function setValues(array $values)
{
foreach($values as $key => $value)
{
if(isset($this->setters[$key]))
{
$func = $this->setters[$key];
if($value === 'null')
{
$value = null;
}
elseif($value === 'true')
{
$value = true;
}
elseif($value === 'false')
{
$value = false;
}
$this->{$func}($value);
}
}
}
protected function _setModified($column)
{
$this->modified[$column] = $column;
}
//----------------
// Validation
//----------------
protected function _validateString($name, $value)
{
if(!is_string($value) && !is_null($value))
{
throw new HttpException\ValidationException($name . ' must be a string.');
}
}
protected function _validateInt($name, &$value)
{
if(is_bool($value))
{
$value = $value ? 1 : 0;
}
if(!is_numeric($value) && !is_null($value))
{
throw new HttpException\ValidationException($name . ' must be an integer.');
}
if(!is_int($value) && !is_null($value))
{
$value = (int)$value;
}
}
protected function _validateFloat($name, &$value)
{
if(!is_numeric($value) && !is_null($value))
{
throw new HttpException\ValidationException($name . ' must be a float.');
}
if(!is_float($value) && !is_null($value))
{
$value = (float)$value;
}
}
protected function _validateDate($name, &$value)
{
if(is_string($value))
{
$value = empty($value) ? null : new \DateTime($value);
}
if((!is_object($value) || !($value instanceof \DateTime)) && !is_null($value))
{
throw new HttpException\ValidationException($name . ' must be a date object.');
}
$value = empty($value) ? null : $value->format('Y-m-d H:i:s');
}
protected function _validateNotNull($name, &$value)
{
if(is_null($value))
{
throw new HttpException\ValidationException($name . ' must not be null.');
}
}
public function __get($key)
{
if (array_key_exists($key, $this->getters)) {
$getter = $this->getters[$key];
return $this->{$getter}();
}
return null;
}
public function __set($key, $value)
{
if (array_key_exists($key, $this->setters)) {
$setter = $this->setters[$key];
return $this->{$setter}($value);
}
}
}

379
src/B8Framework/Store.php Executable file
View file

@ -0,0 +1,379 @@
<?php
namespace b8;
use b8\Exception\HttpException;
use b8\Database,
b8\Model;
abstract class Store
{
protected $modelName = null;
protected $tableName = null;
protected $primaryKey = null;
/**
* @return \b8\Model
*/
abstract public function getByPrimaryKey($key, $useConnection = 'read');
public function getWhere($where = array(), $limit = 25, $offset = 0, $joins = array(), $order = array(), $manualJoins = array(), $group = null, $manualWheres = array(), $whereType = 'AND')
{
$query = 'SELECT ' . $this->tableName . '.* FROM ' . $this->tableName;
$countQuery = 'SELECT COUNT(*) AS cnt FROM ' . $this->tableName;
$wheres = array();
$params = array();
foreach($where as $key => $value)
{
$key = $this->fieldCheck($key);
if(!is_array($value))
{
$params[] = $value;
$wheres[] = $key . ' = ?';
}
else
{
if(isset($value['operator']))
{
if(is_array($value['value']))
{
if($value['operator'] == 'between')
{
$params[] = $value['value'][0];
$params[] = $value['value'][1];
$wheres[] = $key . ' BETWEEN ? AND ?';
}
elseif($value['operator'] == 'IN')
{
$in = array();
foreach($value['value'] as $item)
{
$params[] = $item;
$in[] = '?';
}
$wheres[] = $key . ' IN (' . implode(', ', $in) . ') ';
}
else
{
$ors = array();
foreach($value['value'] as $item)
{
if($item == 'null')
{
switch($value['operator'])
{
case '!=':
$ors[] = $key . ' IS NOT NULL';
break;
case '==':
default:
$ors[] = $key . ' IS NULL';
break;
}
}
else
{
$params[] = $item;
$ors[] = $this->fieldCheck($key) . ' ' . $value['operator'] . ' ?';
}
}
$wheres[] = '(' . implode(' OR ', $ors) . ')';
}
}
else
{
if($value['operator'] == 'like')
{
$params[] = '%' . $value['value'] . '%';
$wheres[] = $key . ' ' . $value['operator'] . ' ?';
}
else
{
if($value['value'] === 'null')
{
switch($value['operator'])
{
case '!=':
$wheres[] = $key . ' IS NOT NULL';
break;
case '==':
default:
$wheres[] = $key . ' IS NULL';
break;
}
}
else
{
$params[] = $value['value'];
$wheres[] = $key . ' ' . $value['operator'] . ' ?';
}
}
}
}
else
{
$wheres[] = $key . ' IN (' . implode(', ', array_map(array(Database::getConnection('read'), 'quote'), $value)) . ')';
}
}
}
if(count($joins))
{
foreach($joins as $table => $join)
{
$query .= ' LEFT JOIN ' . $table . ' ' . $join['alias'] . ' ON ' . $join['on'] . ' ';
$countQuery .= ' LEFT JOIN ' . $table . ' ' . $join['alias'] . ' ON ' . $join['on'] . ' ';
}
}
if(count($manualJoins))
{
foreach($manualJoins as $join)
{
$query .= ' ' . $join . ' ';
$countQuery .= ' ' . $join . ' ';
}
}
$hasWhere = false;
if(count($wheres))
{
$hasWhere = true;
$query .= ' WHERE (' . implode(' ' . $whereType . ' ', $wheres) . ')';
$countQuery .= ' WHERE (' . implode(' ' . $whereType . ' ', $wheres) . ')';
}
if(count($manualWheres))
{
foreach($manualWheres as $where)
{
if(!$hasWhere)
{
$hasWhere = true;
$query .= ' WHERE ';
$countQuery .= ' WHERE ';
}
else
{
$query .= ' ' . $where['type'] . ' ';
$countQuery .= ' ' . $where['type'] . ' ';
}
$query .= ' ' . $where['query'];
$countQuery .= ' ' . $where['query'];
if(isset($where['params']))
{
foreach($where['params'] as $param)
{
$params[] = $param;
}
}
}
}
if(!is_null($group))
{
$query .= ' GROUP BY ' . $group . ' ';
}
if(count($order))
{
$orders = array();
if(is_string($order) && $order == 'rand')
{
$query .= ' ORDER BY RAND() ';
}
else
{
foreach($order as $key => $value)
{
$orders[] = $this->fieldCheck($key) . ' ' . $value;
}
$query .= ' ORDER BY ' . implode(', ', $orders);
}
}
if($limit)
{
$query .= ' LIMIT ' . $limit;
}
if($offset)
{
$query .= ' OFFSET ' . $offset;
}
try
{
$stmt = Database::getConnection('read')->prepare($countQuery);
$stmt->execute($params);
$res = $stmt->fetch(\PDO::FETCH_ASSOC);
$count = (int)$res['cnt'];
}
catch(\PDOException $ex)
{
$count = 0;
}
try
{
$stmt = Database::getConnection('read')->prepare($query);
$stmt->execute($params);
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$rtn = array();
foreach($res as $data)
{
$rtn[] = new $this->modelName($data);
}
return array('items' => $rtn, 'count' => $count);
}
catch(\PDOException $ex)
{
throw $ex;
}
}
public function save(Model $obj, $saveAllColumns = false)
{
if(!isset($this->primaryKey))
{
throw new HttpException\BadRequestException('Save not implemented for this store.');
}
if(!($obj instanceof $this->modelName))
{
throw new HttpException\BadRequestException(get_class($obj) . ' is an invalid model type for this store.');
}
$data = $obj->getDataArray();
if(isset($data[$this->primaryKey]))
{
$rtn = $this->saveByUpdate($obj, $saveAllColumns);
}
else
{
$rtn = $this->saveByInsert($obj, $saveAllColumns);
}
return $rtn;
}
public function saveByUpdate(Model $obj, $saveAllColumns = false)
{
$rtn = null;
$data = $obj->getDataArray();
$modified = ($saveAllColumns) ? array_keys($data) : $obj->getModified();
$updates = array();
$update_params = array();
foreach($modified as $key)
{
$updates[] = $key . ' = :' . $key;
$update_params[] = array($key, $data[$key]);
}
if(count($updates))
{
$qs = 'UPDATE ' . $this->tableName . '
SET ' . implode(', ', $updates) . '
WHERE ' . $this->primaryKey . ' = :primaryKey';
$q = Database::getConnection('write')->prepare($qs);
foreach($update_params as $update_param)
{
$q->bindValue(':' . $update_param[0], $update_param[1]);
}
$q->bindValue(':primaryKey', $data[$this->primaryKey]);
$q->execute();
$rtn = $this->getByPrimaryKey($data[$this->primaryKey], 'write');
}
else
{
$rtn = $obj;
}
return $rtn;
}
public function saveByInsert(Model $obj, $saveAllColumns = false)
{
$rtn = null;
$data = $obj->getDataArray();
$modified = ($saveAllColumns) ? array_keys($data) : $obj->getModified();
$cols = array();
$values = array();
$qParams = array();
foreach($modified as $key)
{
$cols[] = $key;
$values[] = ':' . $key;
$qParams[':' . $key] = $data[$key];
}
if(count($cols))
{
$qs = 'INSERT INTO ' . $this->tableName . ' (' . implode(', ', $cols) . ') VALUES (' . implode(', ', $values) . ')';
$q = Database::getConnection('write')->prepare($qs);
if($q->execute($qParams))
{
$id = !empty($data[$this->primaryKey]) ? $data[$this->primaryKey] : Database::getConnection('write')->lastInsertId();
$rtn = $this->getByPrimaryKey($id, 'write');
}
}
return $rtn;
}
public function delete(Model $obj)
{
if(!isset($this->primaryKey))
{
throw new HttpException\BadRequestException('Delete not implemented for this store.');
}
if(!($obj instanceof $this->modelName))
{
throw new HttpException\BadRequestException(get_class($obj) . ' is an invalid model type for this store.');
}
$data = $obj->getDataArray();
$q = Database::getConnection('write')->prepare('DELETE FROM ' . $this->tableName . ' WHERE ' . $this->primaryKey . ' = :primaryKey');
$q->bindValue(':primaryKey', $data[$this->primaryKey]);
$q->execute();
return true;
}
/**
*
*/
protected function fieldCheck($field)
{
if(empty($field))
{
throw new HttpException('You cannot have an empty field name.');
}
if(strpos($field, '.') === false)
{
return $this->tableName . '.' . $field;
}
return $field;
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace b8\Store;
use b8\Config;
class Factory
{
/**
* @var \b8\Store\Factory
*/
protected static $instance;
/**
* A collection of the stores currently loaded by the factory.
* @var \b8\Store[]
*/
protected $loadedStores = array();
/**
* @return Factory
*/
public static function getInstance()
{
if(!isset(self::$instance))
{
self::$instance = new self();
}
return self::$instance;
}
/**
* @param $storeName string Store name (should match a model name).
*
* @return \b8\Store
*/
public static function getStore($storeName, $namespace = null)
{
$factory = self::getInstance();
return $factory->loadStore($storeName, $namespace);
}
protected function __construct()
{
}
/**
* @param $store
*
* @return \b8\Store;
*/
public function loadStore($store, $namespace = null)
{
if(!isset($this->loadedStores[$store]))
{
$namespace = is_null($namespace) ? Config::getInstance()->get('b8.app.namespace') : $namespace;
$class = $namespace . '\\Store\\' . $store . 'Store';
$obj = new $class();
$this->loadedStores[$store] = $obj;
}
return $this->loadedStores[$store];
}
}

17
src/B8Framework/Type/Cache.php Executable file
View file

@ -0,0 +1,17 @@
<?php
namespace b8\Type;
interface Cache
{
public function get($key, $default = null);
public function set($key, $value = null, $ttl = 0);
public function delete($key);
public function contains($key);
public function isEnabled();
public function __get($key);
public function __set($key, $value = null);
public function __unset($key);
public function __isset($key);
}

View file

@ -0,0 +1,8 @@
<?php
namespace b8\Type;
interface RestUser
{
public function checkPermission($permission, $resource);
}

86
src/B8Framework/View.php Executable file
View file

@ -0,0 +1,86 @@
<?php
namespace b8;
use b8\Exception\HttpException;
class View
{
protected $_vars = array();
protected static $_helpers = array();
protected static $extension = 'phtml';
public function __construct($file, $path = null)
{
if (!self::exists($file, $path)) {
throw new \Exception('View file does not exist: ' . $file);
}
$this->viewFile = self::getViewFile($file, $path);
}
protected static function getViewFile($file, $path = null)
{
$viewPath = is_null($path) ? Config::getInstance()->get('b8.view.path') : $path;
$fullPath = $viewPath . $file . '.' . static::$extension;
return $fullPath;
}
public static function exists($file, $path = null)
{
if (!file_exists(self::getViewFile($file, $path))) {
return false;
}
return true;
}
public function __isset($var)
{
return isset($this->_vars[$var]);
}
public function __get($var)
{
return $this->_vars[$var];
}
public function __set($var, $val)
{
$this->_vars[$var] = $val;
}
public function __call($method, $params = array())
{
if(!isset(self::$_helpers[$method]))
{
$class = '\\' . Config::getInstance()->get('b8.app.namespace') . '\\Helper\\' . $method;
if(!class_exists($class))
{
$class = '\\b8\\View\\Helper\\' . $method;
}
if(!class_exists($class))
{
throw new HttpException('Helper class does not exist: ' . $class);
}
self::$_helpers[$method] = new $class();
}
return self::$_helpers[$method];
}
public function render()
{
extract($this->_vars);
ob_start();
require($this->viewFile);
$html = ob_get_contents();
ob_end_clean();
return $html;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace b8\View\Helper;
class Format
{
public function Currency($number, $symbol = true)
{
return ($symbol ? '£' : '') . number_format($number, 2, '.', ',');
}
}

544
src/B8Framework/View/Template.php Executable file
View file

@ -0,0 +1,544 @@
<?php
namespace b8\View;
use b8\View;
class Template extends View
{
public static $templateFunctions = array();
protected static $extension = 'html';
public function __construct($viewCode)
{
$this->viewCode = $viewCode;
if (!count(self::$templateFunctions)) {
self::$templateFunctions = array('include' => array($this, 'includeTemplate'), 'call' => array($this, 'callHelperFunction'));
}
}
public static function createFromFile($file, $path = null)
{
if (!static::exists($file, $path)) {
throw new \Exception('View file does not exist: ' . $file);
}
$viewFile = static::getViewFile($file, $path);
return new static(file_get_contents($viewFile));
}
public static function createFromString($string)
{
return new static($string);
}
public function addFunction($name, $handler)
{
self::$templateFunctions[$name] = $handler;
}
public function removeFunction($name)
{
unset(self::$templateFunctions[$name]);
}
public function render()
{
return $this->parse($this->viewCode);
}
protected function parse($string)
{
$lastCond = null;
$keywords = array('ifnot', 'if', 'else', 'for', 'loop', '@', '/ifnot', '/if', '/for', '/loop');
foreach (self::$templateFunctions as $function => $handler) {
$keywords[] = $function;
}
$stack = array('children' => array(array('type' => 'string', 'body' => '')));
$stack['children'][0]['parent'] =& $stack;
$current =& $stack['children'][0];
while (!empty($string)) {
$current['body'] .= $this->readUntil('{', $string);
if (!empty($string)) {
$gotKeyword = false;
foreach($keywords as $keyword) {
$kwLen = strlen($keyword) + 1;
if (substr($string, 0, $kwLen) == '{' . $keyword) {
$gotKeyword = true;
$item = array('type' => $keyword, 'cond' => '', 'children' => '');
$string = substr($string, $kwLen);
$cond = trim($this->readUntil('}', $string));
$item['cond'] = $cond;
$lastCond = $cond;
$string = substr($string, 1);
if (array_key_exists($keyword, self::$templateFunctions)) {
$item['function_name'] = $keyword;
$item['type'] = 'function';
}
$str = array('type' => 'string', 'body' => '');
$parent =& $current['parent'];
if (substr($current['body'], (0 - strlen(PHP_EOL))) === PHP_EOL) {
$current['body'] = substr($current['body'], 0, strlen($current['body']) - strlen(PHP_EOL));
}
$item['parent'] =& $parent;
$parent['children'][] = $item;
if ($keyword == '@' || $item['type'] == 'function') {
// If we're processing a variable, add a string to the parent and move up to that as current.
$parent['children'][] = $str;
$current =& $parent['children'][count($parent['children']) - 1];
$current['parent'] =& $parent;
} elseif (substr($keyword, 0, 1) == '/') {
// If we're processing the end of a block (if/loop), add a string to the parent's parent and move up to that.
$parent =& $parent['parent'];
$parent['children'][] = $str;
$current =& $parent['children'][count($parent['children']) - 1];
$current['parent'] =& $parent;
} else {
$parent['children'][count($parent['children']) - 1]['children'][] = $str;
$current =& $parent['children'][count($parent['children']) - 1]['children'][0];
$current['parent'] =& $parent['children'][count($parent['children']) - 1];
}
break;
}
}
if (!$gotKeyword) {
$current['body'] .= substr($string, 0, 1);
$string = substr($string, 1);
}
}
}
return $this->processStack($stack);
}
protected function processStack($stack)
{
$res = '';
while (count($stack['children'])) {
$current = array_shift($stack['children']);
switch ($current['type']) {
case 'string':
$res .= $current['body'];
break;
case '@':
$res .= $this->doParseVar($current['cond']);
break;
case 'if':
$res .= $this->doParseIf($current['cond'], $current);
break;
case 'ifnot':
$res .= $this->doParseIfNot($current['cond'], $current);
break;
case 'loop':
$res .= $this->doParseLoop($current['cond'], $current);
break;
case 'for':
$res .= $this->doParseFor($current['cond'], $current);
break;
case 'function':
$res .= $this->doParseFunction($current);
break;
}
}
return $res;
}
protected function readUntil($until, &$string)
{
$read = '';
while (!empty($string)) {
$char = substr($string, 0, 1);
if ($char == $until) {
break;
}
$read .= $char;
$string = substr($string, 1);
}
return $read;
}
protected function doParseVar($var)
{
if($var == 'year')
{
return date('Y');
}
$val = $this->processVariableName($var);
return $val;
}
protected function doParseIf($condition, $stack)
{
if ($this->ifConditionIsTrue($condition)) {
return $this->processStack($stack);
} else {
return '';
}
}
protected function doParseIfNot($condition, $stack)
{
if (!$this->ifConditionIsTrue($condition)) {
return $this->processStack($stack);
} else {
return '';
}
}
protected function ifConditionIsTrue($condition)
{
$matches = array();
if (preg_match('/([a-zA-Z0-9_\-\(\):\s.\"]+)\s+?([\!\=\<\>]+)?\s+?([a-zA-Z0-9\(\)_\-:\s.\"]+)?/', $condition, $matches)) {
$left = is_numeric($matches[1]) ? intval($matches[1]) : $this->processVariableName($matches[1]);
$right = is_numeric($matches[3]) ? intval($matches[3]) : $this->processVariableName($matches[3]);
$operator = $matches[2];
switch ($operator) {
case '==':
case '=':
return ($left == $right);
case '!=':
return ($left != $right);
case '>=':
return ($left >= $right);
case '<=':
return ($left <= $right);
case '>':
return ($left > $right);
case '<':
return ($left < $right);
}
} elseif (preg_match('/([a-zA-Z0-9_\-\(\):\s.]+)/', $condition, $matches)) {
return $this->processVariableName($condition) ? true : false;
}
}
protected function doParseLoop($var, $stack)
{
$working = $this->processVariableName($var);
if(is_null($working))
{
return '';
}
if(!is_array($working))
{
$working = array($working);
}
$rtn = '';
foreach ($working as $key => $val) {
// Make sure we support nesting loops:
$keyWas = isset($this->key) ? $this->key : null;
$valWas = isset($this->value) ? $this->value : null;
$itemWas = isset($this->item) ? $this->item : null;
// Set up the necessary variables within the stack:
$this->parent = $this;
$this->item = $val;
$this->key = $key;
$this->value = $val;
$rtn .= $this->processStack($stack);
// Restore state for any parent nested loops:
$this->item = $itemWas;
$this->key = $keyWas;
$this->value = $valWas;
}
return $rtn;
}
/**
* Processes loops in templates, of the following styles:
*
* <code>
* {for myarray.items}
* {@item.title}
* {/for}
* </code>
*
* Or:
*
* <code>
* {for 0:pages.count; i++}
* <a href="/item/{@i}">{@i}</a>
* {/for}
* </code>
*
* @param $cond string The condition string for the loop, to be parsed (e.g. "myarray.items" or "0:pages.count; i++")
* @param $stack string The child stack for this loop, to be processed for each item.
* @return string
* @throws \Exception
*/
protected function doParseFor($cond, $stack)
{
// If this is a simple foreach loop, jump over to parse loop:
if (strpos($cond, ';') === false) {
return $this->doParseLoop($cond, $stack);
}
// Otherwise, process as a for loop:
$parts = explode(';', $cond);
$range = explode(':', trim($parts[0]));
// Process range:
$rangeLeft = $this->getForRangePart($range[0]);
$rangeRight = $this->getForRangePart($range[1]);
// Process variable & incrementor / decrementor:
$parts[1] = trim($parts[1]);
$matches = array();
if (preg_match('/([a-zA-Z0-9_]+)(\+\+|\-\-)/', $parts[1], $matches)) {
$varName = $matches[1];
$direction = $matches[2] == '++' ? 'increment' : 'decrement';
} else {
throw new \Exception('Syntax error in for loop: ' . $cond);
}
$rtn = '';
if ($direction == 'increment') {
for ($i = $rangeLeft; $i < $rangeRight; $i++) {
$this->parent = $this;
$this->{$varName} = $i;
$rtn .= $this->processStack($stack);
}
} else {
for ($i = $rangeLeft; $i > $rangeRight; $i--) {
$this->parent = $this;
$this->{$varName} = $i;
$rtn .= $this->processStack($stack);
}
}
return $rtn;
}
protected function getForRangePart($part)
{
if (is_numeric($part)) {
return intval($part);
}
$varPart = $this->processVariableName($part);
if (is_numeric($varPart)) {
return intval($varPart);
}
throw new \Exception('Invalid range in for loop: ' . $part);
}
public function processVariableName($varName)
{
// Case one - Test for function calls:
if (substr($varName, 0, 1) == '(' && substr($varName, -1) == ')') {
$functionCall = substr($varName, 1, -1);
$parts = explode(' ', $functionCall, 2);
$functionName = $parts[0];
$arguments = isset($parts[1]) ? $parts[1] : null;
return $this->executeTemplateFunction($functionName, $arguments);
}
// Case two - Test if it is just a string:
if (substr($varName, 0, 1) == '"' && substr($varName, -1) == '"') {
return substr($varName, 1, -1);
}
// Case three - Test if it is just a number:
if (is_numeric($varName)) {
return $varName;
}
// Case four - Test for helper calls:
if (strpos($varName, ':') !== false) {
list($helper, $property) = explode(':', $varName);
$helper = $this->{$helper}();
if (property_exists($helper, $property) || method_exists($helper, '__get')) {
return $helper->{$property};
}
return null;
}
// Case five - Process as a variable:
$varPart = explode('.', $varName);
$thisPart = array_shift($varPart);
if(!array_key_exists($thisPart, $this->_vars))
{
return null;
}
$working = $this->{$thisPart};
while(count($varPart))
{
$thisPart = array_shift($varPart);
if(is_object($working)) {
// Check if we're working with an actual property:
if (property_exists($working, $thisPart)) {
$working = $working->{$thisPart};
continue;
}
// Check if the object has a magic __get method:
if (method_exists($working, '__get')) {
$working = $working->{$thisPart};
continue;
}
}
if(is_array($working) && array_key_exists($thisPart, $working))
{
$working = $working[$thisPart];
continue;
}
if($thisPart == 'toLowerCase')
{
$working = strtolower($working);
continue;
}
if($thisPart == 'toUpperCase')
{
$working = strtoupper($working);
continue;
}
if ($thisPart == 'isNumeric')
{
return is_numeric($working);
}
return null;
}
return $working;
}
protected function doParseFunction($stack)
{
return $this->executeTemplateFunction($stack['function_name'], $stack['cond']);
}
protected function executeTemplateFunction($function, $args)
{
if (array_key_exists($function, self::$templateFunctions)) {
$handler = self::$templateFunctions[$function];
$args = $this->processFunctionArguments($args);
return $handler($args, $this);
}
return null;
}
protected function processFunctionArguments($args)
{
$rtn = array();
$args = explode(';', $args);
foreach ($args as $arg) {
$arg = explode(':', $arg);
if (count($arg) == 2) {
$key = trim($arg[0]);
$val = trim($arg[1]);
if (strpos($val, ',') !== false) {
$val = explode(',', $val);
}
$rtn[$key] = $val;
}
}
return $rtn;
}
public function getVariable($variable)
{
return $this->processVariableName($variable);
}
protected function includeTemplate($args, $view)
{
$template = static::createFromFile($view->getVariable($args['template']));
if (isset($args['variables'])) {
if (!is_array($args['variables'])) {
$args['variables'] = array($args['variables']);
}
foreach ($args['variables'] as $variable) {
$variable = explode('=>', $variable);
$variable = array_map('trim', $variable);
if (count($variable) == 1) {
$template->{$variable[0]} = $view->getVariable($variable[0]);
} else {
$template->{$variable[1]} = $view->getVariable($variable[0]);
}
}
}
return $template->render();
}
protected function callHelperFunction($args)
{
$helper = $args['helper'];
$function = $args['method'];
return $this->{$helper}()->{$function}();
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace b8\View;
use b8\View\Template;
class UserView extends Template
{
public function __construct($string)
{
trigger_error('Use of UserView is now deprecated. Please use Template instead.', E_USER_NOTICE);
parent::__construct($string);
}
}