diff --git a/B8Framework/.gitignore b/B8Framework/.gitignore new file mode 100755 index 00000000..d3aa3c1e --- /dev/null +++ b/B8Framework/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +._.DS_Store +Thumbs.db +.idea +composer.lock +vendor diff --git a/B8Framework/README.md b/B8Framework/README.md new file mode 100755 index 00000000..a6651294 --- /dev/null +++ b/B8Framework/README.md @@ -0,0 +1,11 @@ +b8 Framework +============ +[](https://travis-ci.org/Block8/b8framework) + + +*b8 framework* is a lightweight, simple framework for high-throughput PHP applications. It does not enforce a specific type of application, nor particularly an application structure, it simply helps you to build things. + +We've used the framework to build web sites, APIs, and web applications, each handling tens of millions of requests a month. + + +*b8 framework repository is _not quite_ ready for general consumption yet. We have a tool to help you generate all of your project CRUD for you, which still needs to be included. This will be done over the next few days.* diff --git a/B8Framework/b8/Application.php b/B8Framework/b8/Application.php new file mode 100755 index 00000000..7c9c5f96 --- /dev/null +++ b/B8Framework/b8/Application.php @@ -0,0 +1,126 @@ +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; + } + } + + $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 \b8\Controller + */ + public function getController() + { + if (empty($this->controller)) { + $namespace = $this->toPhpName($this->route['namespace']); + $controller = $this->toPhpName($this->route['controller']); + $controllerClass = $this->config->get('b8.app.namespace') . '\\' . $namespace . '\\' . $controller . 'Controller'; + $this->controller = $this->loadController($controllerClass); + } + + return $this->controller; + } + + protected function loadController($class) + { + $controller = new $class($this->config, $this->request, $this->response); + $controller->init(); + + return $controller; + } + + protected function controllerExists($route) + { + $namespace = $this->toPhpName($route['namespace']); + $controller = $this->toPhpName($route['controller']); + + $controllerClass = $this->config->get('b8.app.namespace') . '\\' . $namespace . '\\' . $controller . 'Controller'; + + return class_exists($controllerClass); + } + + 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; + } +} diff --git a/B8Framework/b8/Cache.php b/B8Framework/b8/Cache.php new file mode 100755 index 00000000..506c6491 --- /dev/null +++ b/B8Framework/b8/Cache.php @@ -0,0 +1,38 @@ +isEnabled()) { + return $default; + } + + $success = false; + $rtn = apc_fetch($key, $success); + + if (!$success) { + $rtn = $default; + } + + return $rtn; + } + + /** + * Add an item to the cache: + */ + 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: + */ + public function delete($key) + { + if (!$this->isEnabled()) { + return false; + } + + return apc_delete($key); + } + + /** + * Check if an item is in the cache: + */ + public function contains($key) + { + if (!$this->isEnabled()) { + return false; + } + + return apc_exists($key); + } + + /** + * 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); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Cache/RequestCache.php b/B8Framework/b8/Cache/RequestCache.php new file mode 100755 index 00000000..d8ed3035 --- /dev/null +++ b/B8Framework/b8/Cache/RequestCache.php @@ -0,0 +1,90 @@ +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); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Config.php b/B8Framework/b8/Config.php new file mode 100755 index 00000000..b4bd080c --- /dev/null +++ b/B8Framework/b8/Config.php @@ -0,0 +1,176 @@ +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; + } +} diff --git a/B8Framework/b8/Controller.php b/B8Framework/b8/Controller.php new file mode 100755 index 00000000..269209fd --- /dev/null +++ b/B8Framework/b8/Controller.php @@ -0,0 +1,116 @@ +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 b8\Http\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); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Controller/RestController.php b/B8Framework/b8/Controller/RestController.php new file mode 100755 index 00000000..93b61925 --- /dev/null +++ b/B8Framework/b8/Controller/RestController.php @@ -0,0 +1,246 @@ +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); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Database.php b/B8Framework/b8/Database.php new file mode 100755 index 00000000..284ab770 --- /dev/null +++ b/B8Framework/b8/Database.php @@ -0,0 +1,149 @@ + 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); + + // 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; + } +} diff --git a/B8Framework/b8/Database/CodeGenerator.php b/B8Framework/b8/Database/CodeGenerator.php new file mode 100755 index 00000000..7cc13576 --- /dev/null +++ b/B8Framework/b8/Database/CodeGenerator.php @@ -0,0 +1,153 @@ +_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(); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Database/CodeGenerator/BaseControllerTemplate.html b/B8Framework/b8/Database/CodeGenerator/BaseControllerTemplate.html new file mode 100755 index 00000000..68871652 --- /dev/null +++ b/B8Framework/b8/Database/CodeGenerator/BaseControllerTemplate.html @@ -0,0 +1,22 @@ + 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}} diff --git a/B8Framework/b8/Database/CodeGenerator/BaseStoreTemplate.html b/B8Framework/b8/Database/CodeGenerator/BaseStoreTemplate.html new file mode 100755 index 00000000..de651549 --- /dev/null +++ b/B8Framework/b8/Database/CodeGenerator/BaseStoreTemplate.html @@ -0,0 +1,101 @@ +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} +} diff --git a/B8Framework/b8/Database/CodeGenerator/ControllerTemplate.html b/B8Framework/b8/Database/CodeGenerator/ControllerTemplate.html new file mode 100755 index 00000000..61e94afe --- /dev/null +++ b/B8Framework/b8/Database/CodeGenerator/ControllerTemplate.html @@ -0,0 +1,20 @@ +_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; + } +} diff --git a/B8Framework/b8/Database/Map.php b/B8Framework/b8/Database/Map.php new file mode 100755 index 00000000..70d3e86d --- /dev/null +++ b/B8Framework/b8/Database/Map.php @@ -0,0 +1,258 @@ +_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; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Exception/HttpException.php b/B8Framework/b8/Exception/HttpException.php new file mode 100755 index 00000000..ad6baa0c --- /dev/null +++ b/B8Framework/b8/Exception/HttpException.php @@ -0,0 +1,24 @@ +errorCode; + } + + public function getStatusMessage() + { + return $this->statusMessage; + } + + public function getHttpHeader() + { + return 'HTTP/1.1 ' . $this->errorCode . ' ' . $this->statusMessage; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Exception/HttpException/BadRequestException.php b/B8Framework/b8/Exception/HttpException/BadRequestException.php new file mode 100755 index 00000000..89b5df96 --- /dev/null +++ b/B8Framework/b8/Exception/HttpException/BadRequestException.php @@ -0,0 +1,10 @@ +_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(); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/ControlGroup.php b/B8Framework/b8/Form/ControlGroup.php new file mode 100755 index 00000000..cb20f1f6 --- /dev/null +++ b/B8Framework/b8/Form/ControlGroup.php @@ -0,0 +1,7 @@ +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); +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Button.php b/B8Framework/b8/Form/Element/Button.php new file mode 100755 index 00000000..f46472f0 --- /dev/null +++ b/B8Framework/b8/Form/Element/Button.php @@ -0,0 +1,19 @@ +type = 'button'; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Checkbox.php b/B8Framework/b8/Form/Element/Checkbox.php new file mode 100755 index 00000000..07607ebf --- /dev/null +++ b/B8Framework/b8/Form/Element/Checkbox.php @@ -0,0 +1,48 @@ +_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; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/CheckboxGroup.php b/B8Framework/b8/Form/Element/CheckboxGroup.php new file mode 100755 index 00000000..e924dfaf --- /dev/null +++ b/B8Framework/b8/Form/Element/CheckboxGroup.php @@ -0,0 +1,8 @@ +_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); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Email.php b/B8Framework/b8/Form/Element/Email.php new file mode 100755 index 00000000..3913de65 --- /dev/null +++ b/B8Framework/b8/Form/Element/Email.php @@ -0,0 +1,18 @@ +type = 'email'; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Hidden.php b/B8Framework/b8/Form/Element/Hidden.php new file mode 100755 index 00000000..6687b9c5 --- /dev/null +++ b/B8Framework/b8/Form/Element/Hidden.php @@ -0,0 +1,9 @@ +type = 'password'; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Radio.php b/B8Framework/b8/Form/Element/Radio.php new file mode 100755 index 00000000..4afd18fc --- /dev/null +++ b/B8Framework/b8/Form/Element/Radio.php @@ -0,0 +1,8 @@ +_options = $options; + } + + protected function _onPreRender(View &$view) + { + parent::_onPreRender($view); + $view->options = $this->_options; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Submit.php b/B8Framework/b8/Form/Element/Submit.php new file mode 100755 index 00000000..01edbe8b --- /dev/null +++ b/B8Framework/b8/Form/Element/Submit.php @@ -0,0 +1,21 @@ +type = 'submit'; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Text.php b/B8Framework/b8/Form/Element/Text.php new file mode 100755 index 00000000..4f6c92a7 --- /dev/null +++ b/B8Framework/b8/Form/Element/Text.php @@ -0,0 +1,14 @@ +type = 'text'; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/TextArea.php b/B8Framework/b8/Form/Element/TextArea.php new file mode 100755 index 00000000..1e623632 --- /dev/null +++ b/B8Framework/b8/Form/Element/TextArea.php @@ -0,0 +1,26 @@ +_rows; + } + + public function setRows($rows) + { + $this->_rows = $rows; + } + + protected function _onPreRender(View &$view) + { + parent::_onPreRender($view); + $view->rows = $this->getRows(); + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Element/Url.php b/B8Framework/b8/Form/Element/Url.php new file mode 100755 index 00000000..4cad534f --- /dev/null +++ b/B8Framework/b8/Form/Element/Url.php @@ -0,0 +1,18 @@ +type = 'url'; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/FieldSet.php b/B8Framework/b8/Form/FieldSet.php new file mode 100755 index 00000000..a246e0cd --- /dev/null +++ b/B8Framework/b8/Form/FieldSet.php @@ -0,0 +1,114 @@ +_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]; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/Input.php b/B8Framework/b8/Form/Input.php new file mode 100755 index 00000000..94a0027e --- /dev/null +++ b/B8Framework/b8/Form/Input.php @@ -0,0 +1,124 @@ +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; + } +} \ No newline at end of file diff --git a/B8Framework/b8/Form/View/Button.phtml b/B8Framework/b8/Form/View/Button.phtml new file mode 100755 index 00000000..afd5efdb --- /dev/null +++ b/B8Framework/b8/Form/View/Button.phtml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/B8Framework/b8/Form/View/Checkbox.phtml b/B8Framework/b8/Form/View/Checkbox.phtml new file mode 100755 index 00000000..20f060d7 --- /dev/null +++ b/B8Framework/b8/Form/View/Checkbox.phtml @@ -0,0 +1,15 @@ + +
+ * {for myarray.items}
+ * {@item.title}
+ * {/for}
+ *
+ *
+ * Or:
+ *
+ *
+ * {for 0:pages.count; i++}
+ * {@i}
+ * {/for}
+ *
+ *
+ * @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}();
+ }
+}
\ No newline at end of file
diff --git a/B8Framework/b8/View/UserView.php b/B8Framework/b8/View/UserView.php
new file mode 100755
index 00000000..954f9a42
--- /dev/null
+++ b/B8Framework/b8/View/UserView.php
@@ -0,0 +1,14 @@
+=5.3.0",
+ "symfony/yaml": "2.*"
+ },
+ "autoload": {
+ "psr-0": { "b8": "" }
+ }
+}
diff --git a/B8Framework/phpci.yml b/B8Framework/phpci.yml
new file mode 100755
index 00000000..7a0169bd
--- /dev/null
+++ b/B8Framework/phpci.yml
@@ -0,0 +1,12 @@
+build_settings:
+ ignore:
+ - "vendor"
+ - "tests"
+ irc:
+ server: "irc.freenode.net"
+ port: 6667
+ room: "#phpci"
+ nick: "phpcidev"
+
+test:
+ lint:
diff --git a/B8Framework/tests/CacheTest.php b/B8Framework/tests/CacheTest.php
new file mode 100755
index 00000000..69f544f3
--- /dev/null
+++ b/B8Framework/tests/CacheTest.php
@@ -0,0 +1,40 @@
+assertTrue($cache instanceof Cache);
+ }
+
+ public function testDisableCaching()
+ {
+ b8\Registry::getInstance()->set('DisableCaching', true);
+
+ $cache = b8\Cache::getInstance();
+ $this->assertFalse($cache->isEnabled());
+ $this->assertFalse($cache->set('anything', 10));
+ $this->assertTrue(is_null($cache->get('anything')));
+
+ b8\Registry::getInstance()->set('DisableCaching', false);
+ }
+
+ public function testCaching()
+ {
+ $cache = b8\Cache::getInstance();
+
+ if($cache->isEnabled())
+ {
+ $this->assertTrue($cache->set('anything', 10));
+ $this->assertTrue($cache->get('anything') == 10);
+ $this->assertTrue(is_null($cache->get('invalid')));
+ }
+ }
+}
diff --git a/B8Framework/tests/CodeGenerationTest.php b/B8Framework/tests/CodeGenerationTest.php
new file mode 100755
index 00000000..d5121da4
--- /dev/null
+++ b/B8Framework/tests/CodeGenerationTest.php
@@ -0,0 +1,641 @@
+set('b8.app.namespace', 'Generation');
+
+ self::$_db = Database::getConnection('write');
+
+ self::$_db->query('DROP TABLE IF EXISTS tres');
+ self::$_db->query('DROP TABLE IF EXISTS dos');
+ self::$_db->query('DROP TABLE IF EXISTS uno');
+
+ self::$_base = dirname(__FILE__) . '/data/generation/';
+ $gen = new Generator(self::$_db, 'Test', self::$_base .'models/');
+ $gen->generate();
+ }
+
+ public static function tearDownAfterClass()
+ {
+ self::$_db->query('DROP TABLE IF EXISTS tres');
+ self::$_db->query('DROP TABLE IF EXISTS dos');
+ self::$_db->query('DROP TABLE IF EXISTS uno');
+
+ unlink(self::$_base . 'Generation/Model/Base/UnoBase.php');
+ unlink(self::$_base . 'Generation/Model/Base/DosBase.php');
+ unlink(self::$_base . 'Generation/Model/Base/TresBase.php');
+ unlink(self::$_base . 'Generation/Store/Base/UnoStoreBase.php');
+ unlink(self::$_base . 'Generation/Store/Base/DosStoreBase.php');
+ unlink(self::$_base . 'Generation/Store/Base/TresStoreBase.php');
+ unlink(self::$_base . 'Generation/Controller/Base/UnoControllerBase.php');
+ unlink(self::$_base . 'Generation/Controller/Base/DosControllerBase.php');
+ unlink(self::$_base . 'Generation/Controller/Base/TresControllerBase.php');
+ unlink(self::$_base . 'Generation/Model/Uno.php');
+ unlink(self::$_base . 'Generation/Model/Dos.php');
+ unlink(self::$_base . 'Generation/Model/Tres.php');
+ unlink(self::$_base . 'Generation/Store/UnoStore.php');
+ unlink(self::$_base . 'Generation/Store/DosStore.php');
+ unlink(self::$_base . 'Generation/Store/TresStore.php');
+ unlink(self::$_base . 'Generation/Controller/UnoController.php');
+ unlink(self::$_base . 'Generation/Controller/DosController.php');
+ unlink(self::$_base . 'Generation/Controller/TresController.php');
+ }
+
+ public function testGenerate()
+ {
+ error_reporting(E_ALL);
+ $codeGenerator = new CodeGenerator(self::$_db, 'Generation', self::$_base . 'Generation/');
+ $codeGenerator->generateModels();
+ $codeGenerator->generateStores();
+ $codeGenerator->generateControllers();
+
+ $this->assertFileExists(self::$_base . 'Generation/Model/Base/UnoBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Model/Base/DosBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Model/Base/TresBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Store/Base/UnoStoreBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Store/Base/DosStoreBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Store/Base/TresStoreBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Controller/Base/UnoControllerBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Controller/Base/DosControllerBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Controller/Base/TresControllerBase.php');
+ $this->assertFileExists(self::$_base . 'Generation/Model/Uno.php');
+ $this->assertFileExists(self::$_base . 'Generation/Model/Dos.php');
+ $this->assertFileExists(self::$_base . 'Generation/Model/Tres.php');
+ $this->assertFileExists(self::$_base . 'Generation/Store/UnoStore.php');
+ $this->assertFileExists(self::$_base . 'Generation/Store/DosStore.php');
+ $this->assertFileExists(self::$_base . 'Generation/Store/TresStore.php');
+ $this->assertFileExists(self::$_base . 'Generation/Controller/UnoController.php');
+ $this->assertFileExists(self::$_base . 'Generation/Controller/DosController.php');
+ $this->assertFileExists(self::$_base . 'Generation/Controller/TresController.php');
+ }
+
+ /**
+ * @depends testGenerate
+ */
+ public function testGeneratedModels()
+ {
+ if(!defined('APPLICATION_PATH'))
+ {
+ define('APPLICATION_PATH', self::$_base);
+ }
+
+ require_once(self::$_base . 'Generation/Model/Base/UnoBase.php');
+ require_once(self::$_base . 'Generation/Model/Base/DosBase.php');
+ require_once(self::$_base . 'Generation/Model/Base/TresBase.php');
+ require_once(self::$_base . 'Generation/Model/Uno.php');
+ require_once(self::$_base . 'Generation/Model/Dos.php');
+ require_once(self::$_base . 'Generation/Model/Tres.php');
+ require_once(self::$_base . 'ArrayPropertyModel.php');
+
+ $uno = new Generation\Model\Uno();
+ $dos = new Generation\Model\Dos();
+ $tres = new Generation\Model\Tres();
+
+ $this->assertTrue($uno instanceof b8\Model);
+ $this->assertTrue($dos instanceof b8\Model);
+ $this->assertTrue($tres instanceof b8\Model);
+
+ $this->assertTrue($uno instanceof Generation\Model\Base\UnoBase);
+ $this->assertTrue($dos instanceof Generation\Model\Base\DosBase);
+ $this->assertTrue($tres instanceof Generation\Model\Base\TresBase);
+
+ $this->assertTrue($uno->getTableName() == 'uno');
+ $this->assertTrue($dos->getTableName() == 'dos');
+ $this->assertTrue($tres->getTableName() == 'tres');
+
+ $uno->setId(1);
+ $uno->setFieldDatetime(new DateTime());
+ $this->assertTrue($uno->getFieldDatetime() instanceof DateTime);
+
+ $unoArray = $uno->toArray();
+ $this->assertArrayHasKey('field_varchar', $unoArray);
+ $this->assertTrue($unoArray['field_datetime'] instanceof DateTime);
+
+ Generation\Model\Uno::$sleepable = array('id', 'field_varchar');
+ $unoArray = $uno->toArray();
+ $this->assertArrayHasKey('field_varchar', $unoArray);
+ $this->assertFalse(array_key_exists('field_datetime', $unoArray));
+
+ $tres->setField($uno);
+ $this->assertTrue($tres->getFieldInt() == 1);
+
+ $this->assertTrue(in_array('id', $uno->getModified()));
+ $this->assertTrue(is_array($uno->getDataArray()));
+
+ $uno->setValues(array('field_int' => 100, 'field_bob' => 100));
+ $this->assertFalse(in_array('field_bob', $uno->getModified()));
+ $this->assertTrue($uno->getFieldInt() === 100);
+
+ $uno->setFieldInt(true);
+ $this->assertTrue($uno->getFieldInt() === 1);
+
+ $caught = false;
+
+ try
+ {
+ $uno->setFieldInt('invalid');
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $uno->setFieldInt('500');
+ $this->assertTrue($uno->getFieldInt() === 500);
+
+ $caught = false;
+
+ try
+ {
+ $uno->setFieldFloat('invalid');
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $uno->setFieldFloat('4.12');
+ $this->assertTrue($uno->getFieldFloat() === 4.12);
+
+
+ $uno->setFieldDatetime('2014-01-01');
+ $this->assertTrue($uno->getFieldDatetime() instanceof DateTime);
+
+ $caught = false;
+
+ try
+ {
+ $uno->setFieldDatetime(2012);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+
+ $caught = false;
+
+ try
+ {
+ $uno->setFieldInt(null);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $caught = false;
+
+ try
+ {
+ $uno->setValues(array('field_int' => 'null'));
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $uno->setValues(array('field_int' => 'true'));
+ $this->assertTrue($uno->getFieldInt() === 1);
+
+ $uno->setValues(array('field_int' => 'false'));
+ $this->assertTrue($uno->getFieldInt() === 0);
+
+ $caught = false;
+
+ try
+ {
+ $uno->setFieldVarchar(false);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $caught = false;
+
+ try
+ {
+ $uno->setFieldVarchar('Hi');
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertFalse($caught);
+
+ // Test toArray() with an array property:
+ $aModel = new Generation\ArrayPropertyModel();
+ $array = $aModel->toArray();
+
+ $this->assertArrayHasKey('array_property', $array);
+ $this->assertTrue(is_array($array['array_property']));
+ $this->assertTrue(is_array($array['array_property']['three']));
+ $this->assertTrue($array['array_property']['one'] == 'two');
+ }
+
+ /**
+ * @depends testGeneratedModels
+ */
+ public function testGeneratedStores()
+ {
+ require_once(self::$_base . 'Generation/Store/Base/UnoStoreBase.php');
+ require_once(self::$_base . 'Generation/Store/Base/DosStoreBase.php');
+ require_once(self::$_base . 'Generation/Store/Base/TresStoreBase.php');
+ require_once(self::$_base . 'Generation/Store/UnoStore.php');
+ require_once(self::$_base . 'Generation/Store/DosStore.php');
+ require_once(self::$_base . 'Generation/Store/TresStore.php');
+
+ $uno = new Generation\Store\UnoStore();
+ $dos = new Generation\Store\DosStore();
+ $tres = new Generation\Store\TresStore();
+
+ $this->assertTrue($uno instanceof b8\Store);
+ $this->assertTrue($dos instanceof b8\Store);
+ $this->assertTrue($tres instanceof b8\Store);
+
+ $this->assertTrue($uno instanceof Generation\Store\Base\UnoStoreBase);
+ $this->assertTrue($dos instanceof Generation\Store\Base\DosStoreBase);
+ $this->assertTrue($tres instanceof Generation\Store\Base\TresStoreBase);
+
+ $tresModel = new Generation\Model\Tres();
+ $tresModel->setFieldVarchar('Hi');
+
+ $caught = false;
+
+ try
+ {
+ $tres->save($tresModel);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+
+ $caught = false;
+
+ try
+ {
+ $uno->save($tresModel);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+
+ $unoModel = new Generation\Model\Uno();
+ $unoModel->setFieldVarchar('Hi');
+
+ $unoModel = $uno->save($unoModel);
+ $id = $unoModel->getId();
+ $this->assertTrue(!empty($id));
+ $this->assertTrue($unoModel->getFieldVarchar() == 'Hi');
+
+ $unoModel->setFieldVarchar('Ha');
+ $unoModel = $uno->save($unoModel);
+ $this->assertTrue($id == $unoModel->getId());
+ $this->assertTrue($unoModel->getFieldVarchar() == 'Ha');
+
+ $unoModel = $uno->save($unoModel);
+ $this->assertTrue($id == $unoModel->getId());
+ $this->assertTrue($unoModel->getFieldVarchar() == 'Ha');
+
+ $unoModel2 = $uno->getByPrimaryKey($id);
+ $this->assertTrue($unoModel2->getId() == $unoModel->getId());
+
+ $res = $uno->getWhere(array('field_varchar' => 'Ha'));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => array('operator' => 'between', 'value' => array(0, 100))));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => array('operator' => 'IN', 'value' => array(1, 2, 3, 4))));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => array('operator' => '!=', 'value' => array('null', 100))));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => array('operator' => '==', 'value' => array('null'))));
+ $this->assertTrue($res['count'] == 0);
+
+ $res = $uno->getWhere(array('id' => array('operator' => '==', 'value' => 'null')));
+ $this->assertTrue($res['count'] == 0);
+
+ $res = $uno->getWhere(array('id' => array('operator' => '!=', 'value' => 'null')));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('field_varchar' => array('operator' => 'like', 'value' => 'Ha')));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('field_varchar' => array('operator' => '!=', 'value' => 'Hi')));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('field_varchar' => array('Ha', 'Hi')));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => 1), 1, 0, array('dos' => array('alias' => 'd', 'on' => 'd.id = uno.id')), array('id' => 'ASC'));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => 1), 1, 0, array('dos' => array('alias' => 'd', 'on' => 'd.id = uno.id')), 'rand');
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => 1), 1, 10);
+ $this->assertTrue(count($res['items']) == 0 && $res['count'] == 1);
+
+
+ $caught = false;
+
+ try
+ {
+ $uno->getWhere(array('invalid_column' => 1));
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $res = $uno->getWhere(array('id' => 1), 1, 0, array(), 'rand', array('LEFT JOIN dos d ON d.id = uno.id'));
+ $this->assertTrue($res['count'] != 0);
+
+
+ $res = $uno->getWhere(array('id' => 1), 1, 0, array(), 'rand', array(), 'field_varchar');
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array(), 1, 0, array(), 'rand', array(), null, array(array('type' => 'AND', 'query' => 'id = 1', 'params' => array())));
+ $this->assertTrue($res['count'] != 0);
+
+ $res = $uno->getWhere(array('id' => 2), 1, 0, array(), 'rand', array(), null, array(array('type' => 'AND', 'query' => 'id = ?', 'params' => array('id'))));
+ $this->assertTrue($res['count'] == 0);
+
+ $caught = false;
+
+ try
+ {
+ $uno->getWhere(array('' => 1));
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ // ----
+ // Tests for Model::toArray() with relationships:
+ // ----
+ $tresModel->setField($unoModel);
+ $array = $tresModel->toArray();
+
+ $this->assertTrue(array_key_exists('Field', $array));
+ $this->assertTrue(array_key_exists('id', $array['Field']));
+ $this->assertTrue($array['Field']['id'] == $unoModel->getId());
+
+ // ----
+ // Tests for Store::delete()
+ // ----
+
+ $caught = false;
+ try
+ {
+ $tres->delete($tresModel);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $caught = false;
+ try
+ {
+ $uno->delete($tresModel);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+
+ $this->assertTrue($uno->delete($unoModel));
+ $this->assertTrue(is_null($uno->getByPrimaryKey(1)));
+ }
+
+ /**
+ * @depends testGeneratedStores
+ */
+ public function testGeneratedControllers()
+ {
+ require_once(self::$_base . 'Generation/Controller/Base/UnoControllerBase.php');
+ require_once(self::$_base . 'Generation/Controller/Base/DosControllerBase.php');
+ require_once(self::$_base . 'Generation/Controller/Base/TresControllerBase.php');
+ require_once(self::$_base . 'Generation/Controller/UnoController.php');
+ require_once(self::$_base . 'Generation/Controller/DosController.php');
+ require_once(self::$_base . 'Generation/Controller/TresController.php');
+ require_once(self::$_base . 'TestUser.php');
+
+ $uno = new Generation\Controller\UnoController();
+ $dos = new Generation\Controller\DosController();
+ $tres = new Generation\Controller\TresController();
+
+ $uno->init();
+ $dos->init();
+ $tres->init();
+
+ $this->assertTrue($uno instanceof b8\Controller);
+ $this->assertTrue($dos instanceof b8\Controller);
+ $this->assertTrue($tres instanceof b8\Controller);
+
+ $this->assertTrue($uno instanceof Generation\Controller\Base\UnoControllerBase);
+ $this->assertTrue($dos instanceof Generation\Controller\Base\DosControllerBase);
+ $this->assertTrue($tres instanceof Generation\Controller\Base\TresControllerBase);
+
+
+ Registry::getInstance()->setParam('hello', 'world');
+ $this->assertTrue($uno->getParam('hello', 'dave') == 'world');
+
+ $uno->setParam('hello', 'dave');
+ $this->assertTrue($uno->getParam('hello', 'world') == 'dave');
+ $this->assertTrue(array_key_exists('hello', $uno->getParams()));
+
+ $uno->unsetParam('hello');
+ $this->assertFalse(array_key_exists('hello', $uno->getParams()));
+
+ $testUser = new \TestUser();
+ $uno->setActiveUser($testUser);
+ $dos->setActiveUser($uno->getActiveUser());
+ $tres->setActiveUser($uno->getActiveUser());
+
+ $unoModel = new Generation\Model\Uno();
+ $unoStore = \b8\Store\Factory::getStore('Uno');
+ $unoModel->setFieldVarchar('Hi');
+
+ $unoStore->save($unoModel);
+ $list = $uno->index();
+
+ $this->assertTrue(is_array($list));
+ $this->assertTrue(is_array($list['items']));
+ $this->assertTrue(count($list['items']) > 0);
+
+ $caught = false;
+ try
+ {
+ $dos->index();
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+
+ $first = array_shift($list['items']);
+
+ $uno1 = $uno->get($first['id']);
+ $this->assertTrue(is_array($uno1));
+ $this->assertTrue(isset($uno1['uno']));
+ $this->assertTrue($uno1['uno']['id'] == $first['id']);
+
+ $caught = false;
+ try
+ {
+ $dos->get(1);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+
+ $uno->setParam('field_varchar', 'Un');
+ $uno1 = $uno->put($first['id']);
+ $this->assertTrue($uno1['uno']['id'] == $first['id']);
+
+ $caught = false;
+ try
+ {
+ $dos->put(1);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+ $this->assertTrue(is_null($uno->put(10000)));
+
+
+ $uno->setParam('field_text', 'Hello');
+ $res = $uno->post();
+ $this->assertTrue($res['uno']['field_varchar'] == 'Un');
+ $this->assertTrue(!empty($res['uno']['id']));
+
+ $caught = false;
+ try
+ {
+ $dos->post();
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ $del = $uno->delete($res['uno']['id']);
+ $this->assertTrue($del['deleted']);
+
+ $del = $uno->delete($res['uno']['id']);
+ $this->assertFalse($del['deleted']);
+
+ $del = $tres->delete(100);
+ $this->assertFalse($del['deleted']);
+
+ $caught = false;
+ try
+ {
+ $dos->delete(1000);
+ }
+ catch(Exception $ex)
+ {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+
+ //----
+ // Tests for _parseWhere()
+ //----
+ $uno->setParam('where', array('id' => array(1000)));
+ $uno->setParam('neq', 'id');
+ $list = $uno->index();
+
+ $this->assertTrue(is_array($list));
+ $this->assertTrue(count($list['items']) != 0);
+
+ Registry::getInstance()->forceReset();
+ $uno->setParam('where', array('id' => 1000));
+ $uno->setParam('fuzzy', 'id');
+ $list = $uno->index();
+
+ $this->assertTrue(is_array($list));
+ $this->assertTrue(count($list['items']) == 0);
+ }
+}
diff --git a/B8Framework/tests/DatabaseGenerationTest.php b/B8Framework/tests/DatabaseGenerationTest.php
new file mode 100755
index 00000000..00674f7e
--- /dev/null
+++ b/B8Framework/tests/DatabaseGenerationTest.php
@@ -0,0 +1,80 @@
+_name, $this->_user, $this->_pass);
+ \b8\Database::setWriteServers(array($this->_host));
+
+ $this->_db = \b8\Database::getConnection('write');
+
+ $this->_db->query('DROP TABLE IF EXISTS tres');
+ $this->_db->query('DROP TABLE IF EXISTS dos');
+ $this->_db->query('DROP TABLE IF EXISTS uno');
+ }
+
+ public function testCreateDatabase()
+ {
+ $gen = new Generator($this->_db, 'Test', dirname(__FILE__) . '/data/generation/models/');
+ $gen->generate();
+
+ $map = new Map($this->_db);
+ $t = $map->generate();
+
+ $this->assertTrue(array_key_exists('uno', $t));
+ $this->assertTrue(array_key_exists('dos', $t));
+ $this->assertTrue(array_key_exists('tres', $t));
+ $this->assertFalse(array_key_exists('bad_table', $t));
+ $this->assertTrue(count($t['uno']['indexes']) == 1);
+ $this->assertTrue(count($t['dos']['indexes']) == 3);
+ $this->assertTrue(count($t['tres']['indexes']) == 2);
+ $this->assertTrue(count($t['uno']['columns']) == 11);
+ $this->assertTrue(count($t['dos']['columns']) == 4);
+ $this->assertTrue(count($t['tres']['columns']) == 6);
+ $this->assertTrue(array_key_exists('PRIMARY', $t['uno']['indexes']));
+ $this->assertTrue(array_key_exists('PRIMARY', $t['dos']['indexes']));
+ $this->assertFalse(array_key_exists('PRIMARY', $t['tres']['indexes']));
+ }
+
+ public function testUpdateDatabase()
+ {
+ $gen = new Generator($this->_db, 'Test', dirname(__FILE__) . '/data/generation/models/');
+ $gen->generate();
+
+ $gen = new Generator($this->_db, 'Update', dirname(__FILE__) . '/data/generation/update_models/');
+ $gen->generate();
+
+ $map = new Map($this->_db);
+ $t = $map->generate();
+
+ $this->assertTrue(array_key_exists('uno', $t));
+ $this->assertTrue(array_key_exists('dos', $t));
+ $this->assertTrue(array_key_exists('tres', $t));
+ $this->assertFalse(array_key_exists('bad_table', $t));
+ $this->assertTrue(count($t['uno']['indexes']) == 1);
+ $this->assertTrue(count($t['dos']['indexes']) == 3);
+ $this->assertTrue(count($t['tres']['indexes']) == 3);
+ $this->assertTrue(count($t['uno']['columns']) == 10);
+ $this->assertTrue(count($t['dos']['columns']) == 4);
+ $this->assertTrue(count($t['tres']['columns']) == 10);
+ $this->assertTrue(array_key_exists('PRIMARY', $t['uno']['indexes']));
+ $this->assertTrue(array_key_exists('PRIMARY', $t['dos']['indexes']));
+ $this->assertTrue(array_key_exists('PRIMARY', $t['tres']['indexes']));
+ }
+}
diff --git a/B8Framework/tests/DatabaseTest.php b/B8Framework/tests/DatabaseTest.php
new file mode 100755
index 00000000..e60e815b
--- /dev/null
+++ b/B8Framework/tests/DatabaseTest.php
@@ -0,0 +1,54 @@
+_name, $this->_user, $this->_pass);
+ \b8\Database::setReadServers(array($this->_host));
+
+ $connection = \b8\Database::getConnection('read');
+
+ $this->assertInstanceOf('\b8\Database', $connection);
+ }
+
+ public function testGetWriteConnection()
+ {
+ \b8\Database::setDetails($this->_name, $this->_user, $this->_pass);
+ \b8\Database::setWriteServers(array($this->_host));
+
+ $connection = \b8\Database::getConnection('write');
+
+ $this->assertInstanceOf('\b8\Database', $connection);
+ }
+
+ public function testGetDetails()
+ {
+ \b8\Database::setDetails($this->_name, $this->_user, $this->_pass);
+ \b8\Database::setReadServers(array('localhost'));
+
+ $details = \b8\Database::getConnection('read')->getDetails();
+ $this->assertTrue(is_array($details));
+ $this->assertTrue(($details['db'] == $this->_name));
+ $this->assertTrue(($details['user'] == $this->_user));
+ $this->assertTrue(($details['pass'] == $this->_pass));
+ }
+
+ /**
+ * @expectedException \Exception
+ */
+ public function testConnectionFailure()
+ {
+ \b8\Database::setDetails('non_existant', 'invalid_user', 'incorrect_password');
+ \b8\Database::setReadServers(array('localhost'));
+ \b8\Database::getConnection('read');
+ }
+}
\ No newline at end of file
diff --git a/B8Framework/tests/FormTest.php b/B8Framework/tests/FormTest.php
new file mode 100755
index 00000000..e8973d9d
--- /dev/null
+++ b/B8Framework/tests/FormTest.php
@@ -0,0 +1,196 @@
+setAction('/');
+ $f->setMethod('POST');
+
+ $this->assertTrue($f->getAction() == '/');
+ $this->assertTrue($f->getMethod() == 'POST');
+
+ Registry::getInstance()->set('ViewPath', dirname(__FILE__) . '/data/view/');
+
+ $this->assertTrue($f->render('form') == '/POST');
+
+ Registry::getInstance()->set('ViewPath', '');
+ $this->assertTrue(strpos((string)$f, '