Page MenuHomePhabricator

No OneTemporary

diff --git a/SpitFire.php b/SpitFire.php
index 7aff3ea..02939fd 100644
--- a/SpitFire.php
+++ b/SpitFire.php
@@ -1,200 +1,200 @@
<?php namespace spitfire;
use spitfire\App;
use spitfire\core\Context;
use spitfire\core\Environment;
use spitfire\core\Request;
use spitfire\core\Response;
use spitfire\exceptions\ExceptionHandler;
use spitfire\exceptions\PrivateException;
use Strings;
if (!defined('APP_DIRECTORY')){
define ('APP_DIRECTORY', 'bin/apps/', true);
define ('CONFIG_DIRECTORY', 'bin/settings/', true);
define ('ASSET_DIRECTORY', 'assets/', true);
define ('CONTROLLERS_DIRECTORY', 'bin/controllers/', true);
define ('TEMPLATES_DIRECTORY', 'bin/templates/', true);
}
/**
* Dispatcher class of Spitfire. Calls all the required classes for Spitfire to run.
*
* @author César de la Cal <cesar@magic3w.com>
* @package spitfire
*/
class SpitFire extends App
{
static $started = false;
private $cwd;
private $request;
private $debug;
private $apps = Array();
public function __construct() {
#Check if SF is running
if (self::$started) { throw new PrivateException('Spitfire is already running'); }
#Set the current working directory
$this->cwd = BASEDIR;
#Import the exception handler for logging
- $this->debug = new ExceptionHandler();
+ $this->debug = php_sapi_name() === 'cli'? new exceptions\ExceptionHandlerCLI() : new ExceptionHandler();
#Call parent
parent::__construct('bin/', null);
self::$started = true;
}
public function prepare() {
#Try to include the user's evironment & routes
ClassInfo::includeIfPossible(CONFIG_DIRECTORY . 'environments.php');
ClassInfo::includeIfPossible(CONFIG_DIRECTORY . 'routes.php');
#Define the current timezone
date_default_timezone_set(Environment::get('timezone'));
#Set the display errors directive to the value of debug
ini_set("display_errors" , Environment::get('debug_mode')? '1' : '0');
}
public function fire() {
#Import the apps
include CONFIG_DIRECTORY . 'apps.php';
#Every app now gets the chance to create appropriate routes for it's operation
foreach ($this->apps as $app) { $app->createRoutes(); }
$this->createRoutes();
#Get the current path...
$request = $this->request = Request::fromServer();
#If the developer responded to the current route with a response we do not need
#to handle the request
if ($request instanceof Response) {
return $request->getPath()->send();
}
#Start debugging output
ob_start();
#If the request has no defined controller, action and object it will define
#those now.
$path = $request->getPath();
#Receive the initial context for the app. The controller can replace this later
/*@var $initContext Context*/
$initContext = ($path instanceof Context)? $path : $request->makeContext();
#Define the context, include the application's middleware configuration.
current_context($initContext);
include CONFIG_DIRECTORY . 'middleware.php';
#Get the return context
/*@var $context Context*/
$context = $initContext->run();
#End debugging output
$context->view->set('_SF_DEBUG_OUTPUT', ob_get_clean());
#Send the response
$context->response->send();
}
public function registerApp($app, $namespace) {
$this->apps[$namespace] = $app;
}
public function appExists($namespace) {
if (!is_string($namespace)) { return false; }
return isset($this->apps[$namespace]);
}
public function findAppForClass($name) {
if (empty($this->apps)) return $this;
foreach($this->apps as $app) {
if (Strings::startsWith($name, $app->getNameSpace())) {
return $app;
}
}
return $this;
}
/**
*
* @param string $namespace
* @return App
*/
public function getApp($namespace) {
return isset($this->apps[$namespace]) ? $this->apps[$namespace] : $this;
}
public static function baseUrl(){
if (Environment::get('base_url')) { return Environment::get('base_url'); }
list($base_url) = explode('/index.php', $_SERVER['PHP_SELF'], 2);
return $base_url;
}
public function log($msg) {
if ($this->debug) $this->debug->log ($msg);
return $msg;
}
public function getMessages() {
return $this->debug->getMessages();
}
public function getCWD() {
return $this->cwd;
}
/**
* Returns the directory the assets are located in. This function should be
* avoided in favor of the ASSET_DIRECTORY constant.
*
* Please note that the fact that this function provides a bit more flexibility
* than a constant would also allow users to create erratic patterns.
*
* @deprecated since version 0.1-dev 20150423
*/
public function getAssetsDirectory() {
return ASSET_DIRECTORY;
}
/**
* Returns the directory the templates are located in. This function should be
* avoided in favor of the TEMPLATE_DIRECTORY constant.
*
* @deprecated since version 0.1-dev 20150423
*/
public function getTemplateDirectory() {
return TEMPLATES_DIRECTORY;
}
public function enable() {
return null;
}
/**
* Returns the current request.
*
* @return Request The current request
*/
public function getRequest() {
return $this->request;
}
public function getNameSpace() {
return '';
}
}
diff --git a/core/functions.php b/core/functions.php
index a2a0e43..17fc225 100644
--- a/core/functions.php
+++ b/core/functions.php
@@ -1,323 +1,323 @@
<?php
use spitfire\App;
use spitfire\core\Collection;
use spitfire\core\ContextInterface;
use spitfire\core\Environment;
use spitfire\core\http\URL;
use spitfire\io\cli\Console;
use spitfire\locale\Domain;
use spitfire\locale\DomainGroup;
use spitfire\locale\Locale;
use spitfire\SpitFire;
use spitfire\SpitFireCLI;
use spitfire\storage\database\DB;
use spitfire\storage\database\Settings;
use spitfire\validation\ValidationException;
use spitfire\validation\Validator;
use spitfire\validation\ValidatorInterface;
/**
* This is a quick hand method to use Spitfire's main App class as a singleton.
* It allows you to quickly access many of the components the framework provides
* to make it easier to read and maintain the code being created.
*
* @staticvar type $sf
- * @return SpitFire
+ * @return \spitfire\SpitFire
*/
function spitfire() {
static $sf = null;
if ($sf !== null) {
return $sf;
} else {
$sf = php_sapi_name() === 'cli'? new SpitFireCLI() : new SpitFire();
$sf->prepare();
return $sf;
}
}
/**
*
* Registers a new Application in Spitfire, allowing it to handle requests directed
* to it.
*
* @param string $name The name of the Application
* @param string $namespace The namespace in which the requests will be sent to
* the application.
* @return App The App created by the system, use this to pass parameters and
* configuration to the application.
*/
function app($name, $namespace) {
$appName = $name . 'App';
$app = new $appName(APP_DIRECTORY . $name . DIRECTORY_SEPARATOR, $namespace);
spitfire()->registerApp($app, $namespace);
return $app;
}
/**
* Shorthand function to create / retrieve the model the application is using
* to store data. We could consider this a little DB handler factory.
*
* @param Settings $options
* @return \spitfire\storage\database\DB
*/
function db(Settings$options = null) {
static $db = null;
#If we're requesting the standard driver and have it cached, we use this
if ($options === null && $db !== null) { return $db; }
#If no options were passed, we try to fetch them from the environment
$settings = Settings::fromURL($options? : Environment::get('db'));
#Instantiate the driver
$driver = 'spitfire\storage\database\drivers\\' . $settings->getDriver() . '\Driver';
$driver = new $driver($settings);
#If no options were provided we will assume that this is the standard DB handler
if ($options === null) { $db = $driver; }
#Return the driver
return $driver;
}
/**
* Returns HTML escaped string and if desired it adds ellipsis. If the string is
* numeric it will reduce unnecessary decimals.
*
* @param String $str
* @param int $maxlength
* @return String
*/
function __($str, $maxlength = false) {
if ($maxlength) { $str = Strings::ellipsis ($str, $maxlength); }
if (defined('ENT_HTML5'))
{ $str = htmlspecialchars($str, ENT_HTML5, Environment::get('system_encoding')); }
else
{ $str = htmlspecialchars($str, ENT_COMPAT, Environment::get('system_encoding')); }
return $str;
}
/**
* Translation helper.
*
* Depending on the arguments this function receives, it will have one of several
* behaviors.
*
* If the first argument is a spitfire\locale\Locale and the function receives a
* optional second parameter, then it will assign the locale to either the global
* domain / the domain provided in the second parameter.
*
* Otherwise, if the first parameter is a string, it will call the default locale's
* say method. Which will translate the string using the standard locale.
*
* If no parameters are provided, this function returns a DomainGroup object,
* which provides access to the currency and date functions as well as the other
* domains that the system has for translations.
*
* @return string|DomainGroup
*/
function _t() {
static $domains = null;
#If there are no domains we need to set them up first
if ($domains === null) { $domains = new DomainGroup(); }
#Get the functions arguments afterwards
$args = func_get_args();
#If the first parameter is a Locale, then we proceed to registering it so it'll
#provide translations for the programs
if (isset($args[0]) && $args[0] instanceof Locale) {
$locale = array_shift($args);
$domain = array_shift($args);
return $domains->putDomain($domain, new Domain($domain, $locale));
}
#If the args is empty, then we give return the domains that allow for printing
#and localizing of the data.
if (empty($args)) {
return $domains;
}
return call_user_func_array(Array($domains->getDefault(), 'say'), $args);
}
function current_context(ContextInterface$set = null) {
static $context = null;
if ($set!==null) {$context = $set;}
return $context;
}
function console() {
static $console = null;
if ($console === null) {
$console = new Console();
}
return $console;
}
function validate($target = null) {
$targets = array_filter(is_array($target)? $target : func_get_args());
if (!empty($targets) && reset($targets) instanceof ValidatorInterface) {
$messages = Array();
#Retrieve the messages from the validators
foreach ($targets as $target) {
$messages = array_merge($messages, $target->getMessages());
}
if (!empty($messages)) { throw new ValidationException('Validation failed', 1604200115, $messages); }
return $targets;
} else {
$validator = new Validator();
$validator->setValue($target);
return $validator;
}
}
/**
* Retrieves the current path from the request. This will retrieve the path
* without query string or document root.
*
* @see http://www.spitfirephp.com/wiki/index.php/NgiNX_Configuration For NGiNX setup
* @return string
*/
function getPathInfo() {
$base_url = spitfire()->baseUrl();
list($path) = explode('?', substr($_SERVER['REQUEST_URI'], strlen($base_url)));
if (strlen($path) !== 0) { return $path; }
else { return '/'; }
}
function _def(&$a, $b) {
return ($a)? $a : $b;
}
/**
* This function is a shorthand for "new Collection" which also allows fluent
* usage of the collection in certain environments where the PHP version still
* limits that behavior.
*
* @param mixed $elements
* @return Collection
*/
function collect($elements) {
return new Collection($elements);
}
/**
* Creates a new URL. Use this class to generate dynamic URLs or to pass
* URLs as parameters. For consistency (double base prefixes and this
* kind of misshaps aren't funny) use this object to pass or receive URLs
* as paramaters.
*
* Please note that when passing a URL that contains the URL as a string like
* "/hello/world?a=b&c=d" you cannot pass any other parameters. It implies that
* you already have a full URL.
*
* You can pass any amount of parameters to this class,
* the constructor will try to automatically parse the URL as good as possible.
* <ul>
* <li>Arrays are used as _GET</li>
* <li>App objects are used to identify the namespace</li>
* <li>Strings that contain / or ? will be parsed and added to GET and path</li>
* <li>The rest of strings will be pushed to the path.</li>
* </ul>
*/
function url() {
#Get the parameters the first time
$sf = spitfire();
$params = func_get_args();
#Extract the app
if (reset($params) instanceof App || $sf->appExists(reset($params))) {
- $app = array_shift($params);
+ $app = $sf->getApp(array_shift($params));
}
else {
$app = $sf;
}
#Get the controller, and the action
$controller = null;
$action = null;
$object = Array();
#Get the object
- while(!empty($params) && !is_array(reset($params)) ) {
+ while(!empty($params) && (!is_array(reset($params)) || (!$controller && $app->hasController(reset($params))))) {
if (!$controller) { $controller = array_shift($params); }
elseif (!$action) { $action = array_shift($params); }
else { $object[] = array_shift($params); }
}
#Get potential environment variables that can be used for additional information
#like loccalization
$get = array_shift($params);
$environment = array_shift($params);
return new URL($app, $controller, $action, $object, 'php', $get, $environment);
}
/**
* The within function is a math function that allows to determine whether a
* value is within a range and returns either the value, or the closest range
* delimiter.
*
* The first and the last parameter delimit the range. The second parameter is
* the one being tested.
*
* <code>within(1, 50, 100); //Outputs: 50</code>
* <code>within(1, 500, 100); //Outputs: 100</code>
* <code>within(1, -50, 100); //Outputs: 1</code>
*
* @param number $min
* @param number $val
* @param number $max
* @return number
*/
function within($min, $val, $max) {
return min(max($min, $val), $max);
}
function media() {
static $dispatcher = null;
if (!$dispatcher) {
$dispatcher = new \spitfire\io\media\MediaDispatcher();
$dispatcher->register('image/png', new \spitfire\io\media\GDManipulator());
$dispatcher->register('image/jpg', new \spitfire\io\media\GDManipulator());
$dispatcher->register('image/psd', new \spitfire\io\media\ImagickManipulator());
$dispatcher->register('image/gif', new \spitfire\io\media\FFMPEGManipulator());
$dispatcher->register('video/mp4', new \spitfire\io\media\FFMPEGManipulator());
$dispatcher->register('image/vnd.adobe.photoshop', new \spitfire\io\media\ImagickManipulator());
}
return $dispatcher;
}
function storage() {
static $dispatcher = null;
if (!$dispatcher) {
$dispatcher = new \spitfire\storage\objectStorage\ObjectStorageDispatcher();
$dispatcher->register('file://', new spitfire\storage\drive\Directory('/'));
}
return $dispatcher;
}
function request($url) {
return new \spitfire\io\curl\Request($url);
}
\ No newline at end of file
diff --git a/core/router/Route.php b/core/router/Route.php
index 86c8bef..8420dc3 100644
--- a/core/router/Route.php
+++ b/core/router/Route.php
@@ -1,95 +1,95 @@
<?php namespace spitfire\core\router;
use Closure;
use Exception;
use spitfire\core\router\reverser\ClosureReverser;
use spitfire\core\router\reverser\RouteReverserInterface;
/**
* A route is a class that rewrites a URL path (route) that matches a
* route or pattern (old_route) into a new route that the system can
* use (new_route) to handle the current request.
*
* A Route will only accept Closures, Responses or Paths (including arrays that
* can be interpreted as Paths by the translation class) as the target.
*
* @author César de la Cal <cesar@magic3w.com>
*/
class Route extends RewriteRule
{
/* These constants are meant for evaluating if a request should be answered
* depending on if the request is done via HTTP(S). This is especially useful
* when your application wants to enforce HTTPS for certain requests.
*/
const PROTO_HTTP = 0x01;
const PROTO_HTTPS = 0x02;
const PROTO_ANY = 0x03;
/* These constants are intended to allow routes to react differently depending
* on the METHOD used to issue the request the server is receiving. Spitfire
* accepts any of the standard GET, POST, PUT or DELETE methods.
*/
const METHOD_GET = 0x01;
const METHOD_POST = 0x02;
const METHOD_PUT = 0x04;
const METHOD_DELETE = 0x08;
const METHOD_HEAD = 0x10;
private $reverser = null;
/**
*
* @param string $URI
* @param string $method
* @param string $protocol
* @param Parameters $server
* @param string $extension
* @return \spitfire\core\Path|\spitfire\core\Response
*/
public function rewrite($URI, $method, $protocol, $server, $extension = 'php') {
$params = $this->getSource()->test($URI);
/*
* Closures are the most flexible way to handle requests. They allow to
* determine how the application should react depending on any of the
* request's components.
*/
if ($this->getTarget() instanceof Closure) {
return call_user_func_array($this->getTarget(), Array($params, $server, $extension, $method, $protocol));
}
/*
* When using a parameterized path, the idea is to replace the parameters
* we extracted from the URL and construct a valid Path that can then be
* used to answer the request.
*/
if ($this->getTarget() instanceof ParametrizedPath) {
- return $this->getTarget()->replace($server->merge($params))->setFormat($extension);
+ return $this->getTarget()->replace($server->merge($params)->setUnparsed($params->getUnparsed()))->setFormat($extension);
}
}
/**
*
* @return RouteReverserInterface
*/
public function getReverser() {
if ($this->reverser || !$this->getTarget() instanceof ParametrizedPath) { return $this->reverser; }
return $this->reverser = new ClosureReverser(function ($path) {
try { return $this->getSource()->reverse($this->getTarget()->extract($path)); }
catch (Exception$e) { return false; }
});
}
/**
*
* @param RouteReverserInterface $reverser
* @return Route
*/
public function setReverser($reverser) {
$this->reverser = $reverser;
return $this;
}
}
diff --git a/exceptions/ExceptionHandlerCLI.php b/exceptions/ExceptionHandlerCLI.php
new file mode 100644
index 0000000..bfb9a4b
--- /dev/null
+++ b/exceptions/ExceptionHandlerCLI.php
@@ -0,0 +1,71 @@
+<?php namespace spitfire\exceptions;
+
+use BadMethodCallException;
+use Exception;
+use Throwable;
+use function console;
+
+/**
+ * Silent exception handler.
+ *
+ * Whenever an uncaught exception reaches the server it will use this
+ * function for "discrete" failure. The function retrieves (depending
+ * on the error) a error page and logs the error so it can be
+ * analyzed later.
+ * In case there is a failover, and the function fails or cannot
+ * find a file to display the error page it will try to handle the error
+ * by causing a "white screen of death" to the user adding error information
+ * to a HTML comment block. As it is the only failsafe way of communication
+ * when there is a DB Error or permission error on the log files.
+ *
+ * @param Exception $e
+ */
+
+class ExceptionHandlerCLI
+{
+
+ private $msgs = Array();
+
+ public function __construct() {
+ set_exception_handler( Array($this, 'exceptionHandle'));
+ register_shutdown_function( Array($this, 'shutdownHook'));
+ }
+
+ /**
+ *
+ * @param \Throwable|\Exception $e
+ */
+ public function exceptionHandle ($e) {
+ if (!$e instanceof Exception && !$e instanceof Throwable) {
+ throw new BadMethodCallException('Requires throwable type to work.', 1608011002);
+ }
+
+ console()->error($e->getCode() . ': ' . $e->getMessage())->ln();
+ console()->error($e->getTraceAsString())->ln();
+
+ }
+
+ public function shutdownHook () {
+ $last_error = error_get_last();
+
+ switch($last_error['type']){
+ case E_ERROR:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ case E_USER_ERROR:
+ case E_PARSE:
+ case E_RECOVERABLE_ERROR:
+ console()->error($last_error['message'] . "@$last_error[file] [$last_error[line]]")->ln();
+ }
+
+ }
+
+ public function log ($msg) {
+ $this->msgs[] = $msg;
+ }
+
+ public function getMessages () {
+ return $this->msgs;
+ }
+
+}
diff --git a/io/stp/SimpleStackTracePrinter.php b/io/stp/SimpleStackTracePrinter.php
index aba03e7..d52746f 100644
--- a/io/stp/SimpleStackTracePrinter.php
+++ b/io/stp/SimpleStackTracePrinter.php
@@ -1,29 +1,29 @@
<?php namespace spitfire\io\stp;
class SimpleStackTracePrinter extends StackTracePrinter
{
public function printLine($line, $type = StackTracePrinter::LINE_TYPE_NORMAL){
- return sprintf('<li class="line %s">%s</li>', $type, $line);
+ return sprintf('<li class="line %s">%s</li>', $type, \Strings::strToHTML($line));
}
public function makeCSS() {
return file_get_contents(dirname(__FILE__) . '/stp.css');
}
public function wrapExcerpt($html, $startLine) {
return sprintf('<pre class="excerpt"><ol start="%d">%s</ol></pre>', $startLine, $html);
}
public function printMethodSignature($function, $args) {
return sprintf('<strong>%s</strong> ( %s )', $function, $this->stringifyArgs($args));
}
public function wrapMethodSignature($html) {
return sprintf('<div class="signature">%s</div>', $html);
}
public function wrapStackTrace($html, $title) {
return sprintf('<div class="stacktrace"><h2>%s</h2>%s</div>', $title, $html);
}
}
\ No newline at end of file
diff --git a/storage/database/QueryTable.php b/storage/database/QueryTable.php
index 91e18c3..0e2eb66 100644
--- a/storage/database/QueryTable.php
+++ b/storage/database/QueryTable.php
@@ -1,88 +1,88 @@
<?php
namespace spitfire\storage\database;
abstract class QueryTable
{
private $table;
/**
* The following variables manage the aliasing system inside spitfire. To avoid
* having different tables with the same name in them, Spitfire uses aliases
* for the tables. These aliases are automatically generated by adding a unique
* number to the table's name.
*
* The counter is in charge of making sure that every table is uniquely named,
* every time a new query table is created the current value is assigned and
* incremented.
*
* @var int
*/
private static $counter = 1;
private $id;
private $aliased = false;
public function __construct(Table$table) {
#In case this table is aliased, the unique alias will be generated using this.
$this->id = self::$counter++;
$this->table = $table;
}
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function newId() {
$this->id = self::$counter++;
}
public function setAliased($aliased) {
$this->aliased = $aliased;
}
public function isAliased() {
return $this->aliased;
}
public function getAlias() {
/*
* Get the name for the table. We use it to provide a consistent naming
* system that makes it easier for debugging.
*/
$name = $this->table->getLayout()->getTablename();
return $this->aliased? sprintf('%s_%s', $name, $this->id) : $name;
}
public function getField($name) {
$of = $this->table->getDb()->getObjectFactory();
- return $of->queryFieldInstance($this, $this->table->getField($name));
+ return $of->queryFieldInstance($this, $this->table->getLayout()->getField($name));
}
public function getFields() {
$of = $this->table->getDb()->getObjectFactory();
$fields = $this->table->getLayout()->getFields();
foreach ($fields as &$field) {
$field = $of->queryFieldInstance($this, $field);
}
return $fields;
}
/**
*
* @return \spitfire\storage\database\Table
*/
public function getTable() {
return $this->table;
}
abstract public function definition();
abstract public function __toString();
}
\ No newline at end of file
diff --git a/storage/database/Table.php b/storage/database/Table.php
index 7acce6e..f68fd1d 100644
--- a/storage/database/Table.php
+++ b/storage/database/Table.php
@@ -1,294 +1,273 @@
<?php namespace spitfire\storage\database;
use CoffeeBean;
use Model;
use spitfire\exceptions\PrivateException;
use spitfire\storage\database\Schema;
/**
* This class simulates a table belonging to a database. This way we can query
* and handle tables with 'compiler-friendly' code that will inform about errors.
*
* @author César de la Cal <cesar@magic3w.com>
*/
class Table
{
/**
* A reference to the database driver loaded. This allows the system to
* use several databases without the models colliding.
*
* @var DB
*/
protected $db;
/**
* The model this table uses as template to create itself on the DBMS. This is
* one of the key components to Spitfire's ORM as it allows the DB engine to
* create the tables automatically and to discover the data relations.
*
* @var Schema
*/
protected $schema;
/**
* Provides access to the table's layout (physical schema)
*
* @var LayoutInterface
*/
private $layout = false;
/**
* Provides access to the table's record operations. Basically, a relational
* table is composed of schema + relation (data).
*
* @var Relation
*/
private $relation;
/**
* Contains the bean this table uses to generate forms for itself. The bean
* contains additional data to make the data request more user friendly.
*
* @var CoffeeBean
*/
protected $bean;
/**
* Caches a list of fields that compound this table's primary key. The property
* is empty when the table is constructed and collects the primary key's fields
* once they are requested for the first time.
*
* @var \spitfire\storage\database\Index|null
*/
protected $primaryK;
/**
* Just like the primary key field, this property caches the field that contains
* the autonumeric field. This will usually be the ID that the DB refers to
* when working with the table.
*
* @var Field
*/
protected $autoIncrement;
/**
* Creates a new Database Table instance. The tablename will be used to find
* the right model for the table and will be stored prefixed to this object.
*
* @param DB $db
* @param string|Schema $schema
*
* @throws PrivateException
*/
public function __construct(DB$db, $schema) {
$this->db = $db;
$factory = $this->db->getObjectFactory();
if (!$schema instanceof Schema) {
throw new PrivateException('Table requires a Schema to be passed');
}
#Attach the schema to this table
$this->schema = $schema;
$this->schema->setTable($this);
#Create a database table layout (physical schema)
$this->layout = $factory->makeLayout($this);
#Create the relation
$this->relation = $factory->makeRelation($this);
}
/**
* Fetch the fields of the table the database works with. If the programmer
* has defined a custom set of fields to work with, this function will
* return the overridden fields.
*
* @return Field[] The fields this table handles.
*/
public function getFields() {
trigger_error('Deprecated function Table::getFields() called', E_USER_DEPRECATED);
return $this->layout->getFields();
}
/**
*
* @deprecated since version 0.1-dev 20171128
* @param type $name
* @return type
*/
public function getField($name) {
trigger_error('Deprecated function Table::getField() called', E_USER_DEPRECATED);
return $this->layout->getField($name);
}
/**
* Returns the database the table belongs to.
* @return DB
*/
public function getDb() {
return $this->db;
}
/**
* Get's the table's primary key. This will always return an array
* containing the fields the Primary Key contains.
*
* @return IndexInterface
*/
public function getPrimaryKey() {
/*
* If the primary was already determined, we use the cached version.
*/
if ($this->primaryK) { return $this->primaryK; }
$indexes = $this->layout->getIndexes();
return $this->primaryK = $indexes->filter(function (IndexInterface$i) { return $i->isPrimary(); })->rewind();
}
public function getAutoIncrement() {
if ($this->autoIncrement) { return $this->autoIncrement; }
//Implicit else
$fields = $this->layout->getFields();
foreach($fields as $field) {
if ($field->getLogicalField()->isAutoIncrement()) { return $this->autoIncrement = $field; }
}
return null;
}
/**
* Looks for a record based on it's primary data. This can be one of the
* following:
* <ul>
* <li>A single basic data field like a string or a int</li>
* <li>A string separated by : to separate those fields (SF POST standard)</li>
* <li>An array with the data</li>
* </ul>
* This function is intended to be used to provide controllers with prebuilt
* models so they don't need to fetch it again.
*
* @todo Move to relation
*
* @param mixed $id
*
* @return Model
*/
public function getById($id) {
#If the data is a string separate by colons
if (!is_array($id)) { $id = explode(':', $id); }
#Create a query
$table = $this;
$primary = $table->getPrimaryKey()->getFields();
$query = $table->getDb()->getObjectFactory()->queryInstance($this);
#Add the restrictions
while(!$primary->isEmpty()) {
$query->where($primary->shift(), array_shift($id));
}
#Return the result
$_return = $query->fetch();
return $_return;
}
/**
*
* @deprecated since version 0.1-dev 20160902
* @return Schema
*/
public function getModel() {
return $this->schema;
}
/**
*
* @deprecated since version 0.1-dev 20170801
* @return Relation
*/
public function getCollection() {
return $this->relation;
}
/**
* Gives access to the relation, the table's component that manages the data
* that the table contains.
*
* @return Relation
*/
public function getRelation() {
return $this->relation;
}
/**
*
* @return LayoutInterface
*/
public function getLayout(): LayoutInterface {
return $this->layout;
}
/**
*
* @return Schema
*/
public function getSchema() {
return $this->schema;
}
/**
* Returns the bean this model uses to generate Forms to feed itself with data
* the returned value normally is a class that inherits from CoffeeBean.
*
* @deprecated since version 0.1-dev 20161220
* @return CoffeeBean
*/
public function getBean($name = null) {
if (!$name) { $beanName = $this->schema->getName() . 'Bean'; }
else { $beanName = $name . 'Bean'; }
$bean = new $beanName($this);
return $bean;
}
public function get($field, $value, $operator = '=') {
return $this->relation->get($field, $value, $operator);
}
public function getAll() {
return $this->relation->getAll();
}
public function newRecord($data = Array()) {
return $this->relation->newRecord($data);
}
- /**
- * If the table cannot handle the request it will pass it on to the db
- * and add itself to the arguments list.
- *
- * @param string $name
- * @param mixed $arguments
- *
- * @return mixed
- */
- public function __call($name, $arguments) {
-
- #We basically reject __call since it is a bad programming habit to rely on
- #redirecting every call
- trigger_error("Called Table::__call() requesting $name()", E_USER_DEPRECATED);
-
- #Add the table to the arguments for the db
- array_unshift($arguments, $this);
- #Pass on
- return call_user_func_array(Array($this->db, $name), $arguments);
- }
-
}
diff --git a/storage/database/drivers/mysqlpdo/Layout.php b/storage/database/drivers/mysqlpdo/Layout.php
index fb59a00..71adc5b 100644
--- a/storage/database/drivers/mysqlpdo/Layout.php
+++ b/storage/database/drivers/mysqlpdo/Layout.php
@@ -1,240 +1,239 @@
<?php namespace spitfire\storage\database\drivers\mysqlpdo;
use Reference;
use spitfire\cache\MemoryCache;
use spitfire\core\Collection;
use spitfire\exceptions\PrivateException;
use spitfire\model\Index as LogicalIndex;
use spitfire\storage\database\Field;
use spitfire\storage\database\IndexInterface;
use spitfire\storage\database\LayoutInterface;
use spitfire\storage\database\Table;
/*
* The MIT License
*
* Copyright 2017 César de la Cal Bretschneider <cesar@magic3w.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* The MySQL PDO driver for the table layouts. This allows Spitfire to create,
* destroy and repair tables on a MySQL or MariaDB database.
*
* @todo This driver contains a lot of legacy code imported from the previous
* iteration and would require a lot of fixing and maintaining.
*/
class Layout implements LayoutInterface
{
/**
* The table that the system uses to connect layout and relation.
*
* @var Table
*/
private $table;
/**
* The prefixed name of the table. The prefix is defined by the environment
* and allows to have several environments on the same database.
*
* @var string
*/
private $tablename;
/**
* List of the physical fields this table handles. This array is just a
* shortcut to avoid looping through model-fields everytime a query is
* performed.
*
* @var Field[]
*/
private $fields;
/**
* An array of indexes that this table defines to manage it's queries and
* data.
*
* @var MemoryCache
*/
private $indexes;
/**
*
* @param Table $table
*/
public function __construct(Table$table) {
#Assume the table
$this->table = $table;
#Get the physical table name. This will use the prefix to allow multiple instances of the DB
$this->tablename = $this->table->getDb()->getSettings()->getPrefix() . $table->getSchema()->getTableName();
#Create the physical fields
$fields = $this->table->getSchema()->getFields();
$columns = Array();
foreach ($fields as $field) {
$physical = $field->getPhysical();
while ($phys = array_shift($physical)) { $columns[$phys->getName()] = $phys; }
}
$this->fields = $columns;
$this->indexes = new MemoryCache();
}
public function create() {
$table = $this;
$definitions = $table->columnDefinitions();
$indexes = $table->getIndexes();
#Strip empty definitions from the list
$clean = array_filter(array_merge(
$definitions,
$indexes->each(function($e) { return $e->definition(); })->toArray()
));
$stt = sprintf('CREATE TABLE %s (%s) ENGINE=InnoDB CHARACTER SET=utf8',
$table,
implode(', ', $clean)
);
return $this->table->getDb()->execute($stt);
}
public function destroy() {
$this->table->getDb()->execute('DROP TABLE ' . $this);
}
/**
* Fetch the fields of the table the database works with. If the programmer
* has defined a custom set of fields to work with, this function will
* return the overridden fields.
*
* @return Field[] The fields this table handles.
*/
public function getFields() {
return $this->fields;
}
public function getField($name) : Field {
#If the data we get is already a DBField check it belongs to this table
if ($name instanceof Field) {
if ($name->getTable() === $this->table) { return $name; }
else { throw new PrivateException('Field ' . $name . ' does not belong to ' . $this); }
}
if (is_object($name)) {
throw new PrivateException('Expected a field name, got an object', 1708101329);
}
#Otherwise search for it in the fields list
if (isset($this->fields[(string)$name])) { return $this->fields[(string)$name]; }
#The field could not be found in the Database
throw new PrivateException('Field ' . $name . ' does not exist in ' . $this);
}
/**
*
* @return Collection <Index>
*/
public function getIndexes() {
return $this->indexes->get('indexes', function() {
/*
* First we get the defined indexes.
*/
$logical = $this->table->getSchema()->getIndexes();
$indexes = $logical->each(function (LogicalIndex$e) {
return new Index($e);
});
/*
* Then we get those implicitly defined by reference fields. These are
* defined by the driver, sicne they're required for it to work.
*/
$fields = array_filter($this->table->getSchema()->getFields(), function($e) { return $e instanceof Reference;});
foreach ($fields as $field) {
$indexes->push(new ForeignKey(new LogicalIndex([$field])));
}
return $indexes;
});
}
public function getTableName() : string {
return $this->tablename;
}
public function repair() {
- $table = $this->table;
$stt = "DESCRIBE {$this}";
- $fields = $table->getFields();
+ $fields = $this->getFields();
foreach ($this->table->getSchema()->getFields() as $f) {
if ($f instanceof Reference && $f->getTarget() !== $this->table->getSchema()) {
$f->getTarget()->getTable()->getLayout()->repair();
}
}
//Fetch the DB Fields and create on error.
try {
$query = $this->table->getDb()->execute($stt, Array(), false);
}
catch(\Exception $e) {
return $this->create();
}
//Loop through the exiting fields
while (false != ($f = $query->fetch())) {
try {
$field = $this->getField($f['Field']);
unset($fields[$field->getName()]);
}
- catch(Exception $e) {/*Ignore*/}
+ catch(\Exception $e) {/*Ignore*/}
}
foreach($fields as $field) $field->add();
}
/**
* Creates the column definitions for each column
*
* @return mixed
*/
protected function columnDefinitions() {
$fields = $this->getFields();
foreach ($fields as $name => $f) {
$fields[$name] = '`'. $name . '` ' . $f->columnDefinition();
}
return $fields;
}
/**
* Returns the name of a table as DB Object reference (with quotes).
*
* @return string The name of the table escaped and ready for use inside
* of a query.
*/
public function __toString() {
return "`{$this->tablename}`";
}
}
diff --git a/storage/database/drivers/mysqlpdo/Relation.php b/storage/database/drivers/mysqlpdo/Relation.php
index 517891c..5b008b2 100644
--- a/storage/database/drivers/mysqlpdo/Relation.php
+++ b/storage/database/drivers/mysqlpdo/Relation.php
@@ -1,147 +1,147 @@
<?php namespace spitfire\storage\database\drivers\mysqlpdo;
/*
* The MIT License
*
* Copyright 2016 César de la Cal Bretschneider <cesar@magic3w.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
class Relation extends \spitfire\storage\database\Relation
{
/**
* Deletes this record from the database. This method's call cannot be
* undone. <b>[NOTICE]</b>Usually Spitfire will cascade all the data
* related to this record. So be careful calling it deliberately.
*
* @param Model $record Database record to be deleted from the DB.
*/
public function delete(\spitfire\Model$record) {
$table = $this->getTable();
$db = $table->getDb();
$key = $record->getPrimaryData();
$restrictions = Array();
foreach ($key as $k => $v) {
$restrictions[] = sprintf('%s = %s', $k, $db->quote($v));
}
$stt = sprintf('DELETE FROM `%s` WHERE %s',
$table->getLayout()->getTablename(),
implode(' AND ', $restrictions)
);
$db->execute($stt);
}
/**
* Modifies this record on high write environments. If two processes modify
* this record simultaneously they won't generate unconsistent data.
* This function is especially useful for counters i.e. pageviews, clicks,
* plays or any kind of transactions.
*
* @throws PrivateException If the database couldn't handle the request.
* @param Model $record Database record to be modified.
* @param string $key
* @param int|float|double $diff
*/
public function increment(\spitfire\Model$record, $key, $diff = 1) {
$table = $this->getTable();
$db = $table->getDb();
$pk = $record->getPrimaryData();
$restrictions = Array();
foreach ($pk as $k => $v) {$restrictions[] = "$k = $v";}
$stt = sprintf('UPDATE %s SET `%s` = `%s` + %s WHERE %s',
$table,
$key,
$key,
$db->quote($diff),
implode(' AND ', $restrictions)
);
$db->execute($stt);
}
public function insert(\spitfire\Model$record) {
$data = $record->getData();
$table = $record->getTable();
$db = $table->getDb();
$write = Array();
foreach ($data as $value) {
$write = array_merge($write, $value->dbGetData());
}
$fields = array_keys($write);
foreach ($fields as &$field) { $field = '`' . $field . '`'; }
unset($field);
$quoted = array_map(Array($db, 'quote'), $write);
$stt = sprintf('INSERT INTO %s (%s) VALUES (%s)',
$table->getLayout(),
implode(', ', $fields),
implode(', ', $quoted)
);
$db->execute($stt);
return $db->getConnection()->lastInsertId();
}
public function update(\spitfire\Model$record) {
$data = $record->getData();
$table = $record->getTable();
$db = $table->getDb();
$key = $record->getPrimaryData();
$restrictions = Array();
- foreach ($key as $k => $v) {$restrictions[] = "{$table->getField($k)} = {$db->quote($v)}";}
+ foreach ($key as $k => $v) {$restrictions[] = "{$table->getLayout()->getField($k)} = {$db->quote($v)}";}
$write = Array();
foreach ($data as $value) {
#If the data we want to write to the database is already there and
#properly synced, we just skip it.
if ($value->isSynced()) { continue; }
$write = array_merge($write, $value->dbGetData());
}
if (empty($write)) { return; }
$quoted = Array();
- foreach ($write as $f => $v) { $quoted[] = "{$table->getField($f)} = {$db->quote($v)}"; }
+ foreach ($write as $f => $v) { $quoted[] = "{$table->getLayout()->getField($f)} = {$db->quote($v)}"; }
$stt = sprintf('UPDATE %s SET %s WHERE %s',
$table->getLayout(),
implode(', ', $quoted),
implode(' AND ', $restrictions)
);
$this->getDb()->execute($stt);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Apr 14, 5:34 AM (3 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1847
Default Alt Text
(44 KB)

Event Timeline