Page MenuHomePhabricator

No OneTemporary

diff --git a/Model.php b/Model.php
index 493b908..6162b74 100644
--- a/Model.php
+++ b/Model.php
@@ -1,305 +1,305 @@
<?php namespace spitfire;
use Serializable;
use spitfire\exceptions\PrivateException;
use spitfire\storage\database\Restriction;
use spitfire\storage\database\Schema;
use spitfire\storage\database\Table;
use spitfire\storage\database\Field;
/**
* This class allows to track changes on database data along the use of a program
* and creates interactions with the database in a safe way.
*
* @todo Make this class implement Iterator
* @author César de la Cal <cesar@magic3w.com>
*/
abstract class Model implements Serializable
{
/**
* The actual data that the record contains. The record is basically a wrapper
* around the array that allows to validate data on the go and to alert the
* programmer about inconsistent types.
*
* @var \spitfire\model\adapters\AdapterInterface[]
*/
private $data;
/**
* Keeps information about the table that owns the record this Model represents.
* This allows it to power functions like store that require knowledge about
* the database keeping the information.
*
* @var Table
*/
private $table;
#Status vars
private $new = false;
/**
* Creates a new record.
*
* @param Table $table DB Table this record belongs to. Easiest way
* to get this is by using $this->model->*tablename*
*
* @param mixed $data Attention! This parameter is intended to be
* used by the system. To create a new record, leave
* empty and use setData.
*/
public function __construct(Table$table = null, $data = null) {
$this->table = $table;
$this->new = empty($data);
$this->makeAdapters();
$this->populateAdapters($data);
}
/**
* This method is used to generate the 'template' for the table that allows
* spitfire to automatically generate tables and allows it to check the types
* of data and fix tables.
*
* @param Schema $schema
* @return Schema
* @abstract
*/
public abstract function definitions(Schema$schema);
/**
* Returns the data this record currently contains as associative array.
* Remember that this data COULD be invalid when using setData to provide
* it.
*
* @return mixed
*/
public function getData() {
return $this->data;
}
/**
* This method stores the data of this record to the database. In case
* of database error it throws an Exception and leaves the state of the
* record unchanged.
*
* @throws PrivateException
*/
public function store() {
#Check if onbeforesave is there and use it.
if (method_exists($this, 'onbeforesave')) {
$this->onbeforesave();
}
#Decide whether to insert or update depending on the Model
if ($this->new) { $this->insert(); }
else { $this->update(); }
$this->new = false;
foreach($this->data as $value) {
$value->commit();
}
}
/**
* Returns the fields that compound the primary key of this record.
*
* @return storage\database\IndexInterface
*/
public function getUniqueFields() {
return $this->table->getPrimaryKey();
}
/**
* Returns the values of the fields included in this records primary
* fields
*
* @todo Find better function name
* @return array
*/
public function getPrimaryData() {
$primaryFields = $this->getUniqueFields()->getFields();
$ret = Array();
foreach ($primaryFields as $field) {
$logical = $field->getLogicalField();
$ret = array_merge($ret, $this->data[$logical->getName()]->dbGetData());
}
return $ret;
}
/**
* Creates a list of restrictions that identify this record inside it's
* database table.
*
* @todo Fix, still works the old way.
* @deprecated since version 0.1
* @return Restriction[]
*/
public function getUniqueRestrictions() {
$primaries = $this->table->getPrimaryKey();
/** @var $query Query */
$query = $this->table->getQuery();
$restrictions = Array();
$values = Array();
foreach($primaries as $primary) {
$values[] = $this->data[$primary->getLogicalField()->getName()]->dbGetData();
}
foreach ($values as $primary => $value) {
$r = $query->restrictionInstance($query->queryFieldInstance($this->getTable()->getField($primary)), $value, '=');
$restrictions[] = $r;
}
return $restrictions;
}
public function getQuery() {
$query = $this->getTable()->getDb()->getObjectFactory()->queryInstance($this->getTable());
- $primaries = $this->table->getModel()->getPrimary();
+ $primaries = $this->table->getModel()->getPrimary()->getFields();
foreach ($primaries as $primary) {
$name = $primary->getName();
$query->addRestriction($name, $this->$name);
}
return $query;
}
/**
* Returns the table this record belongs to.
*
* @return \spitfire\storage\database\Table
*/
public function getTable() {
return $this->table;
}
public function __set($field, $value) {
if (!isset($this->data[$field])) {
throw new PrivateException("Setting non existent field: " . $field);
}
$this->data[$field]->usrSetData($value);
}
public function __get($field) {
#If the field is in the record we return it's contents
if (isset($this->data[$field])) {
return $this->data[$field]->usrGetData();
} else {
//TODO: In case debug is enabled this should throw an exception
return null;
}
}
//TODO: This now breaks due to the adapters
public function serialize() {
$data = array();
foreach($this->data as $adapter) {
if (! $adapter->isSynced()) throw new PrivateException("Database record cannot be serialized out of sync");
$data = array_merge($data, $adapter->dbGetData());
}
$output = Array();
$output['model'] = $this->table->getModel()->getName();
$output['data'] = $data;
return serialize($output);
}
public function unserialize($serialized) {
$input = unserialize($serialized);
$this->table = db()->table($input['model']);
$this->makeAdapters();
$this->populateAdapters($input['data']);
}
public function __toString() {
return sprintf('%s(%s)', $this->getTable()->getModel()->getName(), implode(',', $this->getPrimaryData()) );
}
public function delete() {
$this->table->getCollection()->delete($this);
}
public function insert() {
#Insert the record by calling the driver.
$id = $this->table->getCollection()->insert($this);
#Get the autoincrement field
$ai = $this->table->getAutoIncrement();
if ($ai) {
$payload = array_filter($this->data[$ai->getName()]->dbGetData());
}
#If the autoincrement field is empty set the new DB given id
if ($ai && empty($payload) ) {
$this->data[$ai->getName()]->dbSetData(Array($ai->getName() => $id));
}
return $id;
}
public function update() {
$this->table->getCollection()->update($this);
}
public function restrictionInstance($query, Field$field, $value, $operator = null) {
return $this->table->getDb()->getObjectFactory()->restrictionInstance($query, $field, $value, $operator);
}
/**
* Increments a value on high read/write environments. Using update can
* cause data to be corrupted. Increment requires the data to be in sync
* aka. stored to database.
*
* @param String $key
* @param int|float $diff
* @throws PrivateException
*/
public function increment($key, $diff = 1) {
$this->table->increment($this, $key, $diff);
}
protected function makeAdapters() {
#If there is no table defined there is no need to create adapters
if ($this->table === null) { return; }
$fields = $this->getTable()->getModel()->getFields();
foreach ($fields as $field) {
$this->data[$field->getName()] = $field->getAdapter($this);
}
}
protected function populateAdapters($data) {
#If the set carries no data, why bother reading?
if (empty($data)) { return; }
#Retrieves the full list of fields this adapter needs to populate
$fields = $this->getTable()->getModel()->getFields();
#Loops through the fields retrieving the physical fields
foreach ($fields as $field) {
$physical = $field->getPhysical();
$current = Array();
#The physical fields are matched to the content and it is assigned.
foreach ($physical as $p) {
$current[$p->getName()] = $data[$p->getName()];
}
#Set the data into the adapter and let it work it's magic.
$this->data[$field->getName()]->dbSetData($current);
}
}
}
diff --git a/core/Context.php b/core/Context.php
index e0e413c..f23b301 100644
--- a/core/Context.php
+++ b/core/Context.php
@@ -1,139 +1,143 @@
<?php namespace spitfire\core;
use Controller;
use publicException;
use spitfire\App;
use spitfire\cache\MemcachedAdapter;
use spitfire\core\annotations\ActionReflector;
use spitfire\core\annotations\AnnotationParser;
use spitfire\core\Request;
use spitfire\core\Response;
use spitfire\exceptions\PrivateException;
use spitfire\InputSanitizer;
use spitfire\io\session\Session;
use spitfire\mvc\middleware\MiddlewareStack;
use spitfire\mvc\View;
use function spitfire;
/**
* The context is a wrapper for an Intent. Basically it describes a full request
* for a page inside Spitfire. Usually you would have a single Context in any
* execution.
*
* Several contexts will usually only be found in Unit Tests that mock the context,
* when using nested controllers or in a CLI application.
*
* @link http://www.spitfirephp.com/wiki/index.php/Class:Context
* @author César de la Cal <cesar@magic3w.com>
* @last-revision 2013-10-25
*/
class Context implements ContextInterface
{
/**
* This is a reference to the context itself. This is a little helper for the
* View and Controller objects that do expose the Context's elements via Magic
* methods, this way we do not need any extra cases for the context.
*
* @var Context
*/
public $context;
/**
*
* @var MiddlewareStack
*/
public $middleware;
/**
* The application running the current context. The app will provide the controller
* to handle the request / context provided.
*
* @var App
*/
public $app;
/**
* The controller is in charge of preparig a proper response to the request.
* This is the first logical level that is user-defined.
*
* @var Controller
*/
public $controller;
public $action;
public $object;
public $extension;
public $annotations;
/**
* Holds the view the app uses to handle the current request. This view is in
* charge of rendering the page once the controller has finished processing
* it.
*
* @var View
*/
public $view;
public $parameters;
public $get;
public $post;
public $cache;
/**
*
* @var Request
*/
public $request;
public $response;
public $session;
function __construct() {
$this->context = $this;
}
public static function create() {
$context = new Context;
$context->get = new InputSanitizer($_GET);
$context->post = new InputSanitizer($_POST);
$context->session = Session::getInstance();
$context->cache = MemcachedAdapter::getInstance();
$context->request = Request::get();
$context->parameters = new InputSanitizer($context->request->getPath()->getParameters());
$context->response = new Response($context);
$context->middleware = new MiddlewareStack($context);
$context->app = spitfire()->getApp($context->request->getPath()->getApp());
$context->controller = $context->app->getController($context->request->getPath()->getController(), $context);
$context->action = $context->request->getPath()->getAction();
$context->object = $context->request->getPath()->getObject();
$context->view = $context->app->getView($context->controller);
- $reflector = new \ReflectionMethod($context->controller, $context->action);
- $annotationParser = new AnnotationParser();
- $context->annotations = $annotationParser->parse($reflector->getDocComment());
+ try {
+ $reflector = new \ReflectionMethod($context->controller, $context->action);
+ $annotationParser = new AnnotationParser();
+ $context->annotations = $annotationParser->parse($reflector->getDocComment());
+ } catch(\Exception$e) {
+ $context->annotations = [];
+ }
return $context;
}
public function run() {
#Run the onload
if (method_exists($this->controller, '_onload') ) {
call_user_func_array(Array($this->controller, '_onload'), Array($this->action));
}
$this->middleware->before();
#Check if the controller can handle the request
$request = Array($this->controller, $this->action);
if (is_callable($request)) { $_return = call_user_func_array($request, $this->object); }
else { throw new publicException('Page not found', 404, new PrivateException('Action not found', 0)); }
$this->middleware->after();
if ($_return instanceof Context) { return $_return; }
else { return $this; }
}
public function __clone() {
$this->context = $this;
}
}
diff --git a/core/annotations/AnnotationParser.php b/core/annotations/AnnotationParser.php
index 2da2019..34d1f38 100644
--- a/core/annotations/AnnotationParser.php
+++ b/core/annotations/AnnotationParser.php
@@ -1,114 +1,115 @@
<?php namespace spitfire\core\annotations;
use BadMethodCallException;
use Reflector;
use Strings;
/**
* Reads a docblock and parses the information that it provides to extract the
* annotations that a programmer may use to modify an application's behavior.
*
* To keep these as flexible as possible, the parser will just return an array
* of information that it extracted from the docblock.
*
* @author César de la Cal Bretschneider <cesar@magic3w.com>
*/
class AnnotationParser
{
/**
* Filters the data from a docblock, extracting the lines that do contain
* annotations and the information these provide.
*
* The return for this will look like this:
* <code>Array('annotationA paramA paramB', 'annotationB paramA paramB')</code>
*
* With this information, the parse function will be able to organize the data
* so it's easily accessible to the programmer using it.
*
* Notice that this function will trim off asterisks and forward slashes of your
* annotation, so if you wish to use those you need to make sure to ask the user
* to quote them for the application to properly use them.
*
* Annotations for this function may as well be numeric or contain special
* characters. You're though encouraged to use simple alphanumeric characters,
* since we're not testing for the operation with Unicode.
*
* @param Reflector|string $doc
* @return string[]
*/
protected function filter($doc) {
#Raw contains the complete docblock comment, which will contain extra data
#that may be uninteresting, we will filter it and return.
#This is an interesting case of a _pain in the ass_, you would expect to be
#able to somehow decently test if the thing is a reflection. But you can't...
$raw = is_object($doc) && method_exists($doc, 'getDocComment') ? $doc->getDocComment() : $doc;
#Check if raw is a string or if whatever we got passed was bogus
+ if (!is_string($raw)) { return []; }
if (!is_string($raw)) { throw new BadMethodCallException('Invalid argument', 1607131552); }
#Individual lines make it easier to parse the data
$pieces = explode("\n", $raw);
$clean = [];
#Remove unrelated data
array_walk($pieces, function ($e) use (&$clean) {
$trimmed = trim($e, "\r\t */");
if (Strings::startsWith($trimmed, '@')) {
$clean[] = ltrim($trimmed, '@');
}
elseif (!empty($clean)) {
$last = array_pop($clean);
$last.= empty($trimmed)? PHP_EOL : $trimmed;
array_push($clean, $last);
}
});
return $clean;
}
/**
* The parser will retrieve a DocComment, or any similar structure and read
* the annotations, providing you with an array that is structured like this
*
* <code>Array('annotation' => Array(Array('paramA'))</code>
*
* Please note that there are three levels to the array:
* * Annotation type
* * Annotation disambiguation (you can have several &at;param for example)
* * Parameters
*
* With the data structured, although inside a multi-layer array, it should be
* fairly simple to access the data that you need to make use of the annotation.
*
* @todo The return of this parser is weird enough to justify a wiki page or
* a special return type
*
* @param Reflector|string $doc
* @return string[][][]
*/
public function parse($doc) {
#Prepare the variables we need.
$annotations = Array();
$clean = $this->filter($doc);
#Sort the data
foreach ($clean as $line) {
$segments = array_filter(explode(' ', $line, 2));
$name = array_shift($segments);
#If uninitialized, initialize the array for the docblock
if (!isset($annotations[$name])) { $annotations[$name] = Array(); }
#Add the value we parsed
$annotations[$name][] = trim(array_shift($segments));
}
return $annotations;
}
}
diff --git a/mvc/middleware/standard/ModelMiddleware.php b/mvc/middleware/standard/ModelMiddleware.php
index 92ab2ce..5bdafcd 100644
--- a/mvc/middleware/standard/ModelMiddleware.php
+++ b/mvc/middleware/standard/ModelMiddleware.php
@@ -1,92 +1,96 @@
<?php namespace spitfire\mvc\middleware\standard;
use ReflectionClass;
use spitfire\core\Context;
use spitfire\core\ContextInterface;
use spitfire\core\Response;
use spitfire\Model;
use spitfire\mvc\middleware\MiddlewareInterface;
/*
* The MIT License
*
* Copyright 2018 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.
*/
/**
* This middleware component does fulfill the rather simple task of handling
* Arguments to Controller actions which require passing a Model as argument.
*
* This makes it less tedious to write code that allows developers to manipulate
* a single record from a database. Your code may look like this:
*
* <code>public function detail(Usermodel$user)</code>
*
* In this case, if the user does not provide a valid user-id as parameter, the
* application will fail with a server error.
*
* If you wish to make the parameter optional, just write your code like this:
*
* <code>public function detail(UserModel$user = null)</code>
*
* If the user didn't provide the value, it will be null. You then will have to
* test the value before using it.
*/
class ModelMiddleware implements MiddlewareInterface
{
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
*
* @param Context $context
*/
public function before(ContextInterface $context) {
- $controller = new ReflectionClass($context instanceof Context? $context->controller : $context->director);
- $action = $controller->getMethod($context->action);
- $object = $context->object;
+ try {
+ $controller = new ReflectionClass($context instanceof Context? $context->controller : $context->director);
+ $action = $controller->getMethod($context->action);
+ $object = $context->object;
+ } catch (\Exception$e) {
+ return;
+ }
$params = $action->getParameters();
for ($i = 0; $i < count($params); $i++) {
/*@var $param \ParameterReflection*/
$param = $params[$i];
if (!$param->getClass()) { continue; }
if (!$param->getClass()->isSubclassOf(Model::class)) { continue; }
$table = $this->db->table(substr($param->getClass()->getName(), 0, 0 - strlen('model')));
$object[$i] = $table->getById($object[$i]);
}
$context->object = $object;
}
public function after(ContextInterface $context, Response $response = null) {
}
}

File Metadata

Mime Type
text/x-diff
Expires
Apr 13 2021, 5:32 PM (9 w, 4 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1739
Default Alt Text
(20 KB)

Event Timeline