Page MenuHomePhabricator

No OneTemporary

diff --git a/core/Collection.php b/core/Collection.php
index e397d4f..e00b0d7 100644
--- a/core/Collection.php
+++ b/core/Collection.php
@@ -1,296 +1,296 @@
<?php namespace spitfire\core;
use ArrayAccess;
use BadMethodCallException;
use spitfire\exceptions\OutOfBoundsException;
use spitfire\exceptions\OutOfRangeException;
/**
* The collection class is intended to supercede the array and provide additional
* functionality and ease of use to the programmer.
*/
class Collection implements ArrayAccess, CollectionInterface
{
private $items;
/**
* The collection element allows to extend array functionality to provide
* programmers with simple methods to aggregate the data in the array.
*
* @param Collection|mixed $e
*/
public function __construct($e = null) {
if ($e === null) { $this->items = []; }
elseif ($e instanceof Collection) { $this->items = $e->toArray(); }
elseif (is_array($e)) { $this->items = $e; }
else { $this->items = [$e]; }
}
/**
* This method iterates over the elements of the array and applies a provided
* callback to each of them. The value your function returns if placed in the
* array.
*
* @param callable|array $callable
* @return Collection
* @throws BadMethodCallException
*/
public function each($callable) {
/*
* If the callback provided is not a valid callable then the function cannot
* properly continue.
*/
if (!is_callable($callable)) {
throw new BadMethodCallException('Invalid callable provided to collection::each()', 1703221329);
}
return new Collection(array_map($callable, $this->items));
}
/**
* Reduces the array to a single value using a callback function.
*
* @param callable $callback
* @param mixed $initial
* @return mixed
*/
public function reduce($callback, $initial = null) {
return array_reduce($this->items, $callback, $initial);
}
public function flatten() {
$_ret = new self();
foreach ($this->items as $item) {
if ($item instanceof Collection) { $_ret->add($item->flatten()); }
elseif (is_array($item)) { $c = new self($item); $_ret->add($c->flatten()); }
else { $_ret->push($item); }
}
return $_ret;
}
/**
* This function checks whether a collection contains only elements with a
* given type. This function also accepts base types.
*
* Following base types are accepted:
*
* <ul>
* <li>int</li><li>float</li>
* <li>number</li><li>string</li>
* <li>array</li>
* <ul>
*
* @param string $type Base type or class name to check.
* @return boolean
*/
public function containsOnly($type) {
switch($type) {
case 'int' : return $this->reduce(function ($p, $c) { return $p && is_int($c); }, true);
case 'float' : return $this->reduce(function ($p, $c) { return $p && is_float($c); }, true);
case 'number': return $this->reduce(function ($p, $c) { return $p && is_numeric($c); }, true);
case 'string': return $this->reduce(function ($p, $c) { return $p && is_string($c); }, true);
case 'array' : return $this->reduce(function ($p, $c) { return $p && is_array($c); }, true);
default : return $this->reduce(function ($p, $c) use ($type) { return $p && is_a($c, $type); }, true);
}
}
/**
* Reports whether the collection is empty.
*
* @return boolean
*/
public function isEmpty() {
return empty($this->items);
}
public function has($idx) {
return isset($this->items[$idx]);
}
public function contains($e) {
return array_search($e, $this->items);
}
/**
* Filters the collection using a callback. This allows a collection to shed
* values that are not useful to the programmer.
*
* Please note that this will return a copy of the collection and the original
* collection will remain unmodified.
*
* @param callable $callback
* @return \spitfire\core\Collection
*/
public function filter($callback = null) {
#If there was no callback defined, then we filter the array without params
if ($callback === null) { return new Collection(array_filter($this->items)); }
#Otherwise we use the callback parameter to filter the array
return new Collection(array_filter($this->items, $callback));
}
/**
* Removes all duplicates from the collection.
*
* @return \spitfire\core\Collection
*/
public function unique() {
return new Collection(array_unique($this->items));
}
/**
* Counts the number of elements inside the collection.
*
* @return int
*/
public function count() {
return count($this->items);
}
/**
* Adds up the elements in the collection. Please note that this method will
* double check to see if all the provided elements are actually numeric and
* can be added together.
*
* @return int|float
* @throws BadMethodCallException
*/
public function sum() {
if ($this->isEmpty()) { throw new BadMethodCallException('Collection is empty'); }
if (!$this->containsOnly('number')) { throw new BadMethodCallException('Collection does contain non-numeric types'); }
return array_sum($this->items);
}
public function sort($callback = null) {
if (!$callback) { return new Collection(sort($this->items)); }
else { return new Collection(usort($this->items, $callback)); }
}
/**
* Returns the average value of the elements inside the collection.
*
* @throws BadMethodCallException If the collection contains non-numeric values
* @return int|float
*/
public function avg() {
return $this->sum() / $this->count();
}
public function join($glue) {
return implode($glue, $this->items);
}
/**
* Extracts a certain key from every element in the collection. This requires
* every element in the collection to be either an object or an array.
*
* The method does not accept values that are neither array nor object, but
* will return null if the key is undefined in the array or object being used.
*
* @param mixed $key
*/
public function extract($key) {
return new Collection(array_map(function ($e) use ($key) {
if (is_array($e)) { return isset($e[$key])? $e[$key] : null; }
if (is_object($e)) { return isset($e->$key)? $e->$key : null; }
throw new OutOfBoundsException('Collection::extract requires array to contain only arrays and objects');
}, $this->items));
}
public function push($element) {
$this->items[] = $element;
return $element;
}
public function add($elements) {
if ($elements instanceof Collection) { $elements = $elements->toArray(); }
$this->items = array_merge($this->items, $elements);
return $this;
}
public function remove($element) {
$i = array_search($element, $this->items);
if ($i === false) { throw new OutOfRangeException('Not found', 1804292224); }
unset($this->items[$i]);
return $this;
}
public function reset() {
$this->items = [];
return $this;
}
public function current() {
return current($this->items);
}
public function key() {
return key($this->items);
}
public function next() {
return next($this->items);
}
public function offsetExists($offset) {
return array_key_exists($offset, $this->items);
}
public function offsetGet($offset) {
if (!array_key_exists($offset, $this->items)) {
throw new OutOfRangeException('Undefined index: ' . $offset, 1703221322);
}
return $this->items[$offset];
}
public function offsetSet($offset, $value) {
$this->items[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->items[$offset]);
}
public function rewind() {
return reset($this->items);
}
public function last() {
if (!isset($this->items)) { throw new \spitfire\exceptions\PrivateException('Collection error', 1709042046); }
return end($this->items);
}
-
- public function pluck() {
+
+ public function shift() {
return array_shift($this->items);
}
/**
* Indicates whether the current element in the Iterator is valid. To achieve
* this we use the key() function in PHP which will return the key the array
* is currently forwarded to or (which is interesting to us) NULL in the event
* that the array has been forwarded past it's end.
*
* @see key
* @return boolean
*/
public function valid() {
return null !== key($this->items);
}
public function toArray() {
return $this->items;
}
public function __isset($name) {
return isset($this->items[$name]);
}
}
diff --git a/core/CollectionInterface.php b/core/CollectionInterface.php
index 36a7e86..47e72a2 100644
--- a/core/CollectionInterface.php
+++ b/core/CollectionInterface.php
@@ -1,108 +1,108 @@
<?php namespace spitfire\core;
use Iterator;
use spitfire\exceptions\core\UndefinedCollectionException;
/*
* 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.
*/
/**
* A collection is a set of values that can be iterated over and can apply certain
* operations to it's values.
*
* One quirk of the collection is that it superceeds both array like data that
* is defined within the app's scope and for pointers - for which the application
* does not know the scope ahead of reading it from a source.
*
* The collection should always be able to provide an array for the data it
* contains via the toArray() method. It is possible though that this method
* throws an exception to indicate that the data cannot be casted to array.
*/
interface CollectionInterface extends Iterator
{
/**
* Indicates whether the collection contains any values at all.
*
* @return boolean
*/
function isEmpty();
/**
* Counts the number of elements that the collection holds. This method may
* throw an exception if the set's size is undefined.
*/
function count();
/**
* Removes the first element from the collection (shifts it off).
*
* @return mixed
*/
- function pluck();
+ function shift();
/**
* Uses a callback function to filter the elements of the array. The function
* passed will receive each element of the collection and if it returns true the
* element will be removed from the collection.
*
* @param callable $callback Function returning a boolean value that indicates
* whether the element should be removed.
*
* @return CollectionInterface The filtered collection.
*/
function filter($callback = null);
/**
* Loops over the elements of the collection applying the callable function,
* the return value will be placed in the output collection.
*
* @param callable $callable Function to be applied to each element
* @return CollectionInterface The collection of elements after the function
* was applied
*/
function each($callable);
/**
* Reduces the collection to a single element. It does this by looping over
* the elements of the collection and combining the "initial" value or the
* value of the previous iteration with the next value.
*
* @param callable $callable
* @param mixed $initial
*/
function reduce($callable, $initial = null);
/**
* Returns the elements of the collection as a PHP array. Please note that in
* the event of a collection not being cast-able you will be required to catch
* the exception generated.
*
* @return mixed
* @throws UndefinedCollectionException If the collection cannot be casted to array
* @todo Name the exception thrown.
*/
function toArray();
}
diff --git a/io/session/FileSessionHandler.php b/io/session/FileSessionHandler.php
index 8044eb4..421c0d1 100644
--- a/io/session/FileSessionHandler.php
+++ b/io/session/FileSessionHandler.php
@@ -1,97 +1,98 @@
<?php namespace spitfire\io\session;
use spitfire\exceptions\FileNotFoundException;
use spitfire\exceptions\PrivateException;
use spitfire\io\session\Session;
class FileSessionHandler extends SessionHandler
{
private $directory;
private $handle;
private $data = false;
public function __construct($directory, $timeout = null) {
$this->directory = $directory;
parent::__construct($timeout);
}
public function close() {
flock($this->getHandle(), LOCK_UN);
fclose($this->getHandle());
return true;
}
public function destroy($id) {
$file = sprintf('%s/sess_%s', $this->directory, $id);
+ $this->handle = null;
file_exists($file) && unlink($file);
return true;
}
public function gc($maxlifetime) {
if ($this->getTimeout()) { $maxlifetime = $this->getTimeout(); }
foreach (glob("$this->directory/sess_*") as $file) {
if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
unlink($file);
}
}
return true;
}
public function getHandle() {
if ($this->handle) { return $this->handle; }
if (!Session::sessionId()) { return false; }
#Initialize the session itself
$id = Session::sessionId(false);
$file = sprintf('%s/sess_%s', $this->directory, $id);
$this->handle = fopen($file, file_exists($file)? 'r+' : 'w+');
flock($this->handle, LOCK_EX);
return $this->handle;
}
public function open($savePath, $sessionName) {
if (empty($this->directory)) {
$this->directory = $savePath;
}
if (!is_dir($this->directory) && !mkdir($this->directory, 0777, true)) {
throw new FileNotFoundException($this->directory . 'does not exist and could not be created');
}
return true;
}
public function read($__garbage) {
//The system can only read the first 8MB of the session.
//We do hardcode to improve the performance since PHP will stop at EOF
fseek($this->getHandle(), 0);
return $this->data = (string) fread($this->getHandle(), 8 * 1024 * 1024);
}
public function write($__garbage, $data) {
//If your session contains more than 8MB of data you're probably doing
//something wrong.
if (isset($data[8*1024*1024])) {
throw new PrivateException('Session length overflow', 171228);
}
if ($data === $this->data) {
return true;
}
ftruncate($this->getHandle(), 0);
fseek($this->getHandle(), 0);
fwrite($this->getHandle(), $data);
}
}
diff --git a/model/fields/manytomany.php b/model/fields/manytomany.php
index 812532f..22c2a1f 100644
--- a/model/fields/manytomany.php
+++ b/model/fields/manytomany.php
@@ -1,146 +1,146 @@
<?php
use spitfire\storage\database\Field as PhysicalField;
use spitfire\storage\database\Schema;
use spitfire\model\Field;
use spitfire\Model;
use spitfire\model\adapters\ManyToManyAdapter;
class ManyToManyField extends ChildrenField
{
/** @var Schema */
private $meta;
public function __construct($model) {
parent::__construct($model, null);
}
public function getRole() {
return parent::getModel()->getName();
}
/** @return Schema */
public function getTarget() {
if ($this->meta) { return $this->target; }
$src = $this->getModel()->getName();
$target = $this->target;
$first = ($src > $target)? $target : $src;
$second = ($first == $src)? $target : $src;
if ($src === $target) { $targetalias = $target . '_1'; }
else { $targetalias = $target; }
if (!$this->getTable()->getDb()->getTableCache()->contains("{$first}_{$second}")) {
$model = $this->meta = new Schema("{$first}_{$second}");
unset($model->_id);
$model->{$src} = new Reference($src);
$model->{$targetalias} = new Reference($target);
$model->{$src}->setPrimary(true);
$model->{$targetalias}->setPrimary(true);
#Register the table
$this->getModel()->getTable()->getDb()->table($model);
} else {
- $this->meta = $this->getTable()->getDb()->table("{$first}_{$second}")->getTable()->getSchema();
+ $this->meta = $this->getTable()->getDb()->table("{$first}_{$second}")->getSchema();
}
#Return the remote model
- $this->target = $this->getModel()->getTable()->getDb()->table($this->target)->getTable()->getSchema();
+ $this->target = $this->getModel()->getTable()->getDb()->table($this->target)->getSchema();
return $this->target;
}
/**
* @param Schema $schema
* @return PhysicalField
*/
public function getModelField($schema) {
return $this->meta->getField($schema->getName());
}
/**
* Returns the table that connects the two tables to form a many to many
* relationship
*
* @return Schema
*/
public function getBridge() {
if ($this->meta) { return $this->meta; }
$this->getTarget();
return $this->meta;
}
public function getDataType() {
return Field::TYPE_BRIDGED;
}
public function getAdapter(Model $model) {
return new ManyToManyAdapter($this, $model);
}
public function getConnectorQueries(spitfire\storage\database\Query$parent) {
$table = $this->getTarget()->getTable();
$query = $table->getDb()->getObjectFactory()->queryInstance($table);
$query->setAliased(true);
if ($this->target !== $this->getModel()) {
#In case the models are different we just return the connectors via a simple route.
$bridge = $this->getBridge()->getTable();
$route = $bridge->getDb()->getObjectFactory()->queryInstance($bridge);
$fields = $this->getBridge()->getFields();
$route->setAliased(true);
foreach ($fields as $field) {
if ($field->getTarget() === $this->getModel()) {
$physical = $field->getPhysical();
foreach ($physical as $p) { $route->addRestriction($route->queryFieldInstance($p), $parent->queryFieldInstance($p->getReferencedField()));}
} else {
$physical = $field->getPhysical();
foreach ($physical as $p) { $query->addRestriction($route->queryFieldInstance($p), $query->queryFieldInstance($p->getReferencedField()));}
}
}
return Array($route, $query);
} else {
#In case the models are the same, well... That's hell
$route1 = $this->getBridge()->getTable()->getAll();
$route2 = $this->getBridge()->getTable()->getAll();
$fields = $this->getBridge()->getFields();
#Alias the routes so they don't collide
$route1->setAliased(true);
$route2->setAliased(true);
$f1 = reset($fields);
$f2 = end($fields);
$f1p = $f1->getPhysical();
$f2p = $f2->getPhysical();
#Start with routes from src
foreach ($f1p as $p) {$route1->addRestriction($route1->queryFieldInstance($p), $parent->queryFieldInstance($p->getReferencedField()));}
foreach ($f2p as $p) {$route2->addRestriction($route2->queryFieldInstance($p), $parent->queryFieldInstance($p->getReferencedField()));}
#Exclude repeated results from Route2
$group = $route2->group(\spitfire\storage\database\RestrictionGroup::TYPE_OR);
foreach ($f1p as $k => $v) {$group->addRestriction($route2->queryFieldInstance($v), $route2->queryFieldInstance($f2p[$k]), '<>');}
#Link back
$groupback = $query->group(spitfire\storage\database\RestrictionGroup::TYPE_OR);
$r1group = $groupback->group(spitfire\storage\database\RestrictionGroup::TYPE_AND);
$r2group = $groupback->group(spitfire\storage\database\RestrictionGroup::TYPE_AND);
#Note that the fields are now swaped
foreach ($f2p as $p) {$r1group->addRestriction($route1->queryFieldInstance($p), $query->queryFieldInstance($p->getReferencedField()));}
foreach ($f1p as $p) {$r2group->addRestriction($route2->queryFieldInstance($p), $query->queryFieldInstance($p->getReferencedField()));}
return Array($route1, $route2, $query);
}
}
}
diff --git a/storage/database/IndexInterface.php b/storage/database/IndexInterface.php
index 3a4a147..1ea0424 100644
--- a/storage/database/IndexInterface.php
+++ b/storage/database/IndexInterface.php
@@ -1,73 +1,75 @@
<?php namespace spitfire\storage\database;
+use spitfire\core\Collection;
+
/*
* 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.
*/
/**
* This interface allows the database driver to provide the system with indexes
* it can understand. These indexes will be fed back to the driver in the event
* the system detects a missing table. Allowing the driver to assemble the table
* properly.
*/
interface IndexInterface
{
/**
* Returns an array of fields, please note that your driver should respect
* the order on these. The order of the fields may affect query performance
* heavily on a relational model.
*
* @return Collection <Field>
*/
function getFields();
/**
* Returns a name for the index. In most DBMS this is optional, but allows
* for better understanding of the schema.
*
* @return string
*/
function getName() : string;
/**
* Indicates whether this is a unique index. Therefore requesting the DBMS to
* enforce no-duplicates on the index.
*
* A driver requesting this value should always OR this value with isPrimary()
* like $index->isOptional() || $index->isPrimary() to know whether a index
* is unique.
*
* @see IndexInterface::isPrimary()
* @return bool
*/
function isUnique() : bool;
/**
* Indicates whether this index is primary. If your index returns this value
* as true, the isUnique() value will be overriden by the system internally.
*
* @return bool
*/
function isPrimary() : bool;
}
\ No newline at end of file
diff --git a/storage/database/Table.php b/storage/database/Table.php
index c9fda1c..7acce6e 100644
--- a/storage/database/Table.php
+++ b/storage/database/Table.php
@@ -1,293 +1,294 @@
<?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();
+ $primary = $table->getPrimaryKey()->getFields();
$query = $table->getDb()->getObjectFactory()->queryInstance($this);
#Add the restrictions
- while(count($primary))
- { $query->addRestriction (array_shift($primary), array_shift($id)); }
+ 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 6013c3b..fb59a00 100644
--- a/storage/database/drivers/mysqlpdo/Layout.php
+++ b/storage/database/drivers/mysqlpdo/Layout.php
@@ -1,239 +1,240 @@
<?php namespace spitfire\storage\database\drivers\mysqlpdo;
use Reference;
use spitfire\cache\MemoryCache;
-use spitfire\core\Environment;
+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 = Environment::get('db_table_prefix') . $table->getSchema()->getTableName();
+ $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;
- $stt = "DESCRIBE $table";
+ $table = $this->table;
+ $stt = "DESCRIBE {$this}";
$fields = $table->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*/}
}
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}`";
}
-}
\ No newline at end of file
+}
diff --git a/tests/core/CollectionTest.php b/tests/core/CollectionTest.php
index 6a323d3..35a5ab6 100644
--- a/tests/core/CollectionTest.php
+++ b/tests/core/CollectionTest.php
@@ -1,121 +1,121 @@
<?php namespace tests\spitfire\core;
use BadMethodCallException;
use PHPUnit\Framework\TestCase;
use spitfire\core\Collection;
class CollectionTest extends TestCase
{
/**
*
* @covers \spitfire\core\Collection::containsOnly
*/
public function testContainsOnly() {
$c1 = new Collection([1, 2, 3, 4, 5, 6]);
$c2 = new Collection(['a', 'b', 'c']);
$c3 = new Collection([[], []]);
$c4 = new Collection([$c1, $c2]);
$c5 = new Collection([1.1, 2.2, 3.3, 4.4, 5.5, 6.6]);
$c6 = new Collection([1.1, 2.2, 3.3, 4.4, 5.5, 6]);
$c7 = new Collection(['1', '2', '3']);
$this->assertEquals(true, $c1->containsOnly('int'));
$this->assertEquals(true, $c1->containsOnly('number'));
$this->assertEquals(true, $c2->containsOnly('string'));
$this->assertEquals(true, $c3->containsOnly('array'));
$this->assertEquals(false, $c3->containsOnly(Collection::class));
$this->assertEquals(true, $c4->containsOnly(Collection::class));
$this->assertEquals(false, $c4->containsOnly('array'));
$this->assertEquals(true, $c5->containsOnly('float'));
$this->assertEquals(true, $c5->containsOnly('number'));
$this->assertEquals(false, $c6->containsOnly('float'));
$this->assertEquals(true, $c6->containsOnly('number'));
$this->assertEquals(true, $c7->containsOnly('string'));
$this->assertEquals(true, $c7->containsOnly('number'));
}
/**
*
* @covers \spitfire\core\Collection::avg
*/
public function testAverage() {
$collection = new Collection([1, 2, 3]);
$this->assertEquals($collection->avg(), 2, 'Average of 1, 2 and 3 is 2');
}
/**
*
* @covers \spitfire\core\Collection::avg
* @expectedException BadMethodCallException
*/
public function testAverage2() {
$collection = new Collection([]);
$collection->avg();
}
/**
*
* @covers \spitfire\core\Collection::avg
* @expectedException BadMethodCallException
*/
public function testAverage3() {
$collection = new Collection(['a', 'b', 'c']);
$collection->avg();
}
/**
*
* @covers \spitfire\core\Collection::each
*/
public function testExtraction() {
$collection = new Collection([['a' => 1, 'b' => 2], ['a' => 'c', 'b' => 4]]);
$result = $collection->each(function ($e) { return $e['b']; })->avg();
$this->assertEquals($result, 3, 'The average value of 2 and 4 is expected to be 3');
}
public function testExtract() {
$collection = new Collection([['a' => 1, 'b' => 2], ['a' => 'c', 'b' => 4]]);
$result = $collection->extract('b')->avg();
$this->assertEquals($result, 3, 'The average value of 2 and 4 is expected to be 3');
}
/**
* Tests whether the filtering algorithm of a collection works properly.
*/
public function testFilter() {
$collection = new Collection([0, 1, 0, 2, 0, 3]);
$this->assertInstanceOf(Collection::class, $collection->filter());
$this->assertEquals(3, $collection->filter()->count());
- $this->assertEquals(1, $collection->filter(function ($e) { return $e === 1; })->pluck());
+ $this->assertEquals(1, $collection->filter(function ($e) { return $e === 1; })->shift());
}
public function testIsset() {
$collection = new Collection([0, 1, null, false, true]);
$this->assertEquals(true, isset($collection[0]));
$this->assertEquals(true, isset($collection->{0}));
$this->assertEquals(true, isset($collection[3]));
$this->assertEquals(true, isset($collection->{3}));
$this->assertEquals(true, isset($collection[4]));
$this->assertEquals(true, isset($collection->{4}));
$this->assertEquals(false, isset($collection['a']));
$this->assertEquals(false, isset($collection->a));
}
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Apr 13 2021, 2:25 AM (9 w, 19 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1904
Default Alt Text
(42 KB)

Event Timeline