diff --git a/backend/bin/doctrine.php b/backend/bin/doctrine.php index f30be4b..e632ac1 100644 --- a/backend/bin/doctrine.php +++ b/backend/bin/doctrine.php @@ -1,4 +1,4 @@ -dview->request->getParameter('uuid')) { $ec = new EntityController($this->view, $this->em); - $entity = $ec->get($uuid); - - if ($entity instanceof Model\Channel) { - $aggregator->addChannel($entity); - } - elseif ($entity instanceof Model\Aggregator) { - $aggregator->addAggregator($entity); - } + $aggregator->addChild($ec->get($uuid)); } - else { // create new aggregator + else { throw new \Exception('You have to specifiy a uuid to add'); } } - else { + else { // create new aggregator $aggregator = new Model\Aggregator('group'); // TODO support for other aggregator types foreach ($this->view->request->getParameters() as $parameter => $value) { @@ -94,22 +87,15 @@ class AggregatorController extends EntityController { if ($uuid) { $ec = new EntityController($this->view, $this->em); - $entity = $ec->get($uuid); - - if ($entity instanceof Model\Channel) { - $aggregator->removeChannel($entity); - } - elseif ($entity instanceof Model\Aggregator) { - $aggregator->removeAggregator($entity); - } + $aggregator->removeChild($ec->get($uuid)); $this->em->flush(); } - else { // remove aggregator + else { throw new \Exception('You have to specifiy a uuid to remove'); } } - else { + else { // remove aggregator parent::delete($identifier); } diff --git a/backend/lib/Controller/Controller.php b/backend/lib/Controller/Controller.php index affdcb8..0306bc7 100644 --- a/backend/lib/Controller/Controller.php +++ b/backend/lib/Controller/Controller.php @@ -49,12 +49,12 @@ abstract class Controller { * * @param string $operation runs the operation if class method is available */ - public function run($operation, array $params = array()) { + public function run($operation, array $identifiers = array()) { if (!is_callable(array($this, $operation))) { throw new \Exception('Invalid context operation: ' . $operation); } - return call_user_func_array(array($this, $operation), $params); + return call_user_func_array(array($this, $operation), $identifiers); } } diff --git a/backend/lib/Controller/DataController.php b/backend/lib/Controller/DataController.php index a9e6949..f15c19f 100644 --- a/backend/lib/Controller/DataController.php +++ b/backend/lib/Controller/DataController.php @@ -68,11 +68,11 @@ class DataController extends Controller { $this->em->flush(); } - public function run($operation, array $params = array()) { + public function run($operation, array $identifiers = array()) { $ec = new EntityController($this->view, $this->em); - $params[0] = $ec->get($params[0]); + $identifiers[0] = $ec->get($identifiers[0]); - return parent::run($operation, $params); + return parent::run($operation, $identifiers); } } diff --git a/backend/lib/Controller/EntityController.php b/backend/lib/Controller/EntityController.php index 6bef8fb..b3c3a50 100644 --- a/backend/lib/Controller/EntityController.php +++ b/backend/lib/Controller/EntityController.php @@ -38,19 +38,23 @@ class EntityController extends Controller { * * @param string $identifier */ - public function get($identifier) { + public function get($uuid) { + if (!Util\UUID::validate($uuid)) { + throw new \Exception('Invalid UUID: ' . $uuid); + } + $dql = 'SELECT a, p FROM Volkszaehler\Model\Entity a LEFT JOIN a.properties p WHERE a.uuid = ?1'; $q = $this->em->createQuery($dql); - $q->setParameter(1, $identifier); + $q->setParameter(1, $uuid); try { return $q->getSingleResult(); } catch (\Doctrine\ORM\NoResultException $e) { - throw new \Exception('No entity found with uuid: ' . $identifier); + throw new \Exception('No entity found with UUID: ' . $uuid); } } diff --git a/backend/lib/Interpreter/Interpreter.php b/backend/lib/Interpreter/Interpreter.php index 042aa87..c01c696 100644 --- a/backend/lib/Interpreter/Interpreter.php +++ b/backend/lib/Interpreter/Interpreter.php @@ -23,16 +23,12 @@ namespace Volkszaehler\Interpreter; -/** - * - * @package default - * @author Steffen Vogel - * - */ -use Volkszaehler\Iterator; -use Volkszaehler; -use Doctrine\ORM\Query; +use Volkszaehler\Util; +use Volkszaehler\Interpreter\Iterator; +use Doctrine\ORM; +use Volkszaehler\Model; +use Doctrine\ORM\Query; /** * Interpreter superclass for all interpreters @@ -56,12 +52,14 @@ abstract class Interpreter implements InterpreterInterface { * @param integer $from timestamp in ms since 1970 * @param integer $to timestamp in ms since 1970 */ - public function __construct(\Volkszaehler\Model\Channel $channel, \Doctrine\ORM\EntityManager $em, $from = NULL, $to = NULL) { + public function __construct(Model\Channel $channel, ORM\EntityManager $em, $from, $to) { $this->channel = $channel; $this->em = $em; - $this->from = $from; - $this->to = $to; + $this->from = (isset($from)) ? self::parseDateTimeString($from, time() * 1000) : NULL; + $this->to = (isset($to)) ? self::parseDateTimeString($to, (isset($this->from)) ? $this->from : time() * 1000) : NULL; + + Util\Debug::log('interval', $this->from, $this->to, strftime('%c', $this->from/1000), strftime('%c', $this->to/1000)); } /** @@ -78,7 +76,7 @@ abstract class Interpreter implements InterpreterInterface { $params = array(':id' => $this->channel->getId()); $sqlFrom = ' FROM data'; - $sqlWhere = ' WHERE channel_id = :id' . self::buildTimeFilterSQL($this->from, $this->to); + $sqlWhere = ' WHERE channel_id = :id' . self::buildDateTimeFilterSQL($this->from, $this->to); $sqlOrderBy = ' ORDER BY timestamp ASC'; if ($sqlGroupBy = self::buildGroupBySQL($groupBy)) { @@ -161,7 +159,7 @@ abstract class Interpreter implements InterpreterInterface { * @param integer $to timestamp in ms since 1970 * @return string the sql part */ - protected static function buildTimeFilterSQL($from = NULL, $to = NULL) { + protected static function buildDateTimeFilterSQL($from = NULL, $to = NULL) { $sql = ''; if (isset($from)) { @@ -175,6 +173,28 @@ abstract class Interpreter implements InterpreterInterface { return $sql; } + /** + * Parses a timestamp + * + * @link http://de3.php.net/manual/en/datetime.formats.php + * @todo add millisecond resolution + * + * @param string $ts string to parse + * @param float $now in ms since 1970 + * @return float + */ + protected static function parseDateTimeString($string, $now) { + if ($ts = strtotime($string, $now / 1000)) { + return $ts * 1000; + } + elseif (ctype_digit($string)) { + return (float) $string; + } + else { + throw new \Exception('Invalid time format: ' . $string); + } + } + /* * Getter & setter */ diff --git a/backend/lib/Iterator/DataAggregationIterator.php b/backend/lib/Interpreter/Iterator/DataAggregationIterator.php similarity index 98% rename from backend/lib/Iterator/DataAggregationIterator.php rename to backend/lib/Interpreter/Iterator/DataAggregationIterator.php index 63c6333..5cc6c77 100644 --- a/backend/lib/Iterator/DataAggregationIterator.php +++ b/backend/lib/Interpreter/Iterator/DataAggregationIterator.php @@ -21,7 +21,7 @@ * along with volkszaehler.org. If not, see . */ -namespace Volkszaehler\Iterator; +namespace Volkszaehler\Interpreter\Iterator; use Doctrine\DBAL; diff --git a/backend/lib/Iterator/DataIterator.php b/backend/lib/Interpreter/Iterator/DataIterator.php similarity index 97% rename from backend/lib/Iterator/DataIterator.php rename to backend/lib/Interpreter/Iterator/DataIterator.php index 9e9b122..864c8cf 100644 --- a/backend/lib/Iterator/DataIterator.php +++ b/backend/lib/Interpreter/Iterator/DataIterator.php @@ -21,7 +21,7 @@ * along with volkszaehler.org. If not, see . */ -namespace Volkszaehler\Iterator; +namespace Volkszaehler\Interpreter\Iterator; use Doctrine\DBAL; diff --git a/backend/lib/Model/Aggregator.php b/backend/lib/Model/Aggregator.php index 63d58c9..2a78ee0 100644 --- a/backend/lib/Model/Aggregator.php +++ b/backend/lib/Model/Aggregator.php @@ -23,6 +23,8 @@ namespace Volkszaehler\Model; +use Doctrine\ORM\Mapping; + use Volkszaehler\Interpreter; use Doctrine\ORM; use Doctrine\Common\Collections; @@ -39,28 +41,14 @@ use Doctrine\Common\Collections\ArrayCollection; */ class Aggregator extends Entity { /** - * @ManyToMany(targetEntity="Channel", inversedBy="groups") - * @JoinTable(name="groups_channel", - * joinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="channel_id", referencedColumnName="id")} - * ) - */ - protected $channels = NULL; - - /** - * @ManyToMany(targetEntity="Aggregator", inversedBy="parents") - * @JoinTable(name="groups_groups", + * @ManyToMany(targetEntity="Entity", inversedBy="parents") + * @JoinTable(name="entities_in_aggregator", * joinColumns={@JoinColumn(name="parent_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="child_id", referencedColumnName="id")} * ) */ protected $children = NULL; - /** - * @ManyToMany(targetEntity="Aggregator", mappedBy="children") - */ - protected $parents = NULL; - /** * Construct */ @@ -69,44 +57,65 @@ class Aggregator extends Entity { $this->channels = new ArrayCollection(); $this->children = new ArrayCollection(); - $this->parents = new ArrayCollection(); } /** - * Adds group as new child + * Adds entity as new child * - * @param Aggregator $child - * @todo check against endless recursion - * @todo check if the group is already member of the group - * @todo add bidirectional association + * @param Entity $child + * @todo check if the entity is already member of the group + * @todo add bidrectional association */ - public function addAggregator(Aggregator $child) { + public function addChild(Entity $child) { + if ($this->children->contains($child)) { + throw new \Exception('Entity is already a child of the group'); + } + + if ($child instanceof Aggregator && $child->contains($this)) { + throw new \Exception('Recursion detected! Can\'t group to itself'); + } + $this->children->add($child); } - public function removeAggregator(Aggregator $child) { - return $this->children->removeElement($child); + /** + * Checks if aggregator contains given entity + * + * @param Entity $entity + * @param boolean $recursive should we search recursivly? + */ + protected function contains(Entity $entity, $recursive = FALSE) { + if ($this->children->contains($entity)) { + return TRUE; + } + + if ($recursive) { + foreach ($this->children as $child) { + if ($child->contains($entity, $recursive)) { + return TRUE; + } + } + } + + return FALSE; } /** - * Adds channel as new child + * Remove child from group * - * @param Channel $child - * @todo check if the channel is already member of the group + * @param Entity $child + * @todo check if the entity is member of the group * @todo add bidrectional association */ - public function addChannel(Channel $child) { - $this->channels->add($child); - } - - public function removeChannel(Channel $child) { - return $this->channels->removeElement($child); + public function removeChild(Entity $child) { + if (!$this->children->removeElement($child)) { + throw new \Exception('This entity is not a child of this group'); + } } /* * Setter & getter */ - public function getChannels() { return $this->channels->toArray(); } public function getChildren() { return $this->children->toArray(); } } diff --git a/backend/lib/Model/Entity.php b/backend/lib/Model/Entity.php index da209bd..a50e889 100644 --- a/backend/lib/Model/Entity.php +++ b/backend/lib/Model/Entity.php @@ -69,6 +69,11 @@ abstract class Entity { */ protected $properties = NULL; + /** + * @ManyToMany(targetEntity="Aggregator", mappedBy="children") + */ + protected $parents = NULL; + /** * Constructor * @@ -84,6 +89,7 @@ abstract class Entity { $this->tokens = new Collections\ArrayCollection(); $this->properties = new Collections\ArrayCollection(); + $this->parents = new Collections\ArrayCollection(); } diff --git a/backend/lib/Model/Proxy/VolkszaehlerModelAggregatorProxy.php b/backend/lib/Model/Proxy/VolkszaehlerModelAggregatorProxy.php index 945f524..c7dd050 100644 --- a/backend/lib/Model/Proxy/VolkszaehlerModelAggregatorProxy.php +++ b/backend/lib/Model/Proxy/VolkszaehlerModelAggregatorProxy.php @@ -27,46 +27,34 @@ class VolkszaehlerModelAggregatorProxy extends \Volkszaehler\Model\Aggregator im } - public function addAggregator(\Volkszaehler\Model\Aggregator $child) + public function addChild(\Volkszaehler\Model\Entity $child) { $this->_load(); - return parent::addAggregator($child); + return parent::addChild($child); } - public function removeAggregator(\Volkszaehler\Model\Aggregator $child) + public function removeChild(\Volkszaehler\Model\Entity $child) { $this->_load(); - return parent::removeAggregator($child); + return parent::removeChild($child); } - public function addChannel(\Volkszaehler\Model\Channel $child) + public function getChildren() { $this->_load(); - return parent::addChannel($child); + return parent::getChildren(); } - public function removeChannel(\Volkszaehler\Model\Channel $child) + public function checkProperties() { $this->_load(); - return parent::removeChannel($child); + return parent::checkProperties(); } - public function getChannels() + public function getProperty($key) { $this->_load(); - return parent::getChannels(); - } - - public function checkPersist() - { - $this->_load(); - return parent::checkPersist(); - } - - public function getProperty($name) - { - $this->_load(); - return parent::getProperty($name); + return parent::getProperty($key); } public function getProperties($prefix = NULL) @@ -81,10 +69,10 @@ class VolkszaehlerModelAggregatorProxy extends \Volkszaehler\Model\Aggregator im return parent::setProperty($key, $value); } - public function unsetProperty($name, \Doctrine\ORM\EntityManager $em) + public function unsetProperty($key, \Doctrine\ORM\EntityManager $em) { $this->_load(); - return parent::unsetProperty($name, $em); + return parent::unsetProperty($key, $em); } public function getId() @@ -120,6 +108,6 @@ class VolkszaehlerModelAggregatorProxy extends \Volkszaehler\Model\Aggregator im public function __sleep() { - return array('__isInitialized__', 'id', 'uuid', 'type', 'tokens', 'properties', 'channels', 'children', 'parents'); + return array('__isInitialized__', 'id', 'uuid', 'type', 'tokens', 'properties', 'parents', 'children'); } } \ No newline at end of file diff --git a/backend/lib/Model/Proxy/VolkszaehlerModelChannelProxy.php b/backend/lib/Model/Proxy/VolkszaehlerModelChannelProxy.php index 0504523..8d8c664 100644 --- a/backend/lib/Model/Proxy/VolkszaehlerModelChannelProxy.php +++ b/backend/lib/Model/Proxy/VolkszaehlerModelChannelProxy.php @@ -33,16 +33,16 @@ class VolkszaehlerModelChannelProxy extends \Volkszaehler\Model\Channel implemen return parent::addData($data); } - public function checkPersist() + public function checkProperties() { $this->_load(); - return parent::checkPersist(); + return parent::checkProperties(); } - public function getProperty($name) + public function getProperty($key) { $this->_load(); - return parent::getProperty($name); + return parent::getProperty($key); } public function getProperties($prefix = NULL) @@ -57,10 +57,10 @@ class VolkszaehlerModelChannelProxy extends \Volkszaehler\Model\Channel implemen return parent::setProperty($key, $value); } - public function unsetProperty($name, \Doctrine\ORM\EntityManager $em) + public function unsetProperty($key, \Doctrine\ORM\EntityManager $em) { $this->_load(); - return parent::unsetProperty($name, $em); + return parent::unsetProperty($key, $em); } public function getId() @@ -96,6 +96,6 @@ class VolkszaehlerModelChannelProxy extends \Volkszaehler\Model\Channel implemen public function __sleep() { - return array('__isInitialized__', 'id', 'uuid', 'type', 'tokens', 'properties', 'data', 'aggregators'); + return array('__isInitialized__', 'id', 'uuid', 'type', 'tokens', 'properties', 'parents', 'data', 'aggregators'); } } \ No newline at end of file diff --git a/backend/lib/Model/Proxy/VolkszaehlerModelEntityProxy.php b/backend/lib/Model/Proxy/VolkszaehlerModelEntityProxy.php index 551c28c..83171ca 100644 --- a/backend/lib/Model/Proxy/VolkszaehlerModelEntityProxy.php +++ b/backend/lib/Model/Proxy/VolkszaehlerModelEntityProxy.php @@ -27,16 +27,16 @@ class VolkszaehlerModelEntityProxy extends \Volkszaehler\Model\Entity implements } - public function checkPersist() + public function checkProperties() { $this->_load(); - return parent::checkPersist(); + return parent::checkProperties(); } - public function getProperty($name) + public function getProperty($key) { $this->_load(); - return parent::getProperty($name); + return parent::getProperty($key); } public function getProperties($prefix = NULL) @@ -51,10 +51,10 @@ class VolkszaehlerModelEntityProxy extends \Volkszaehler\Model\Entity implements return parent::setProperty($key, $value); } - public function unsetProperty($name, \Doctrine\ORM\EntityManager $em) + public function unsetProperty($key, \Doctrine\ORM\EntityManager $em) { $this->_load(); - return parent::unsetProperty($name, $em); + return parent::unsetProperty($key, $em); } public function getId() @@ -90,6 +90,6 @@ class VolkszaehlerModelEntityProxy extends \Volkszaehler\Model\Entity implements public function __sleep() { - return array('__isInitialized__', 'id', 'uuid', 'type', 'tokens', 'properties'); + return array('__isInitialized__', 'id', 'uuid', 'type', 'tokens', 'properties', 'parents'); } } \ No newline at end of file diff --git a/backend/lib/Model/Proxy/VolkszaehlerModelPropertyProxy.php b/backend/lib/Model/Proxy/VolkszaehlerModelPropertyProxy.php index 5185445..ab7b929 100644 --- a/backend/lib/Model/Proxy/VolkszaehlerModelPropertyProxy.php +++ b/backend/lib/Model/Proxy/VolkszaehlerModelPropertyProxy.php @@ -39,6 +39,18 @@ class VolkszaehlerModelPropertyProxy extends \Volkszaehler\Model\Property implem return parent::validate(); } + public function checkRemove() + { + $this->_load(); + return parent::checkRemove(); + } + + public function checkPersist() + { + $this->_load(); + return parent::checkPersist(); + } + public function getKey() { $this->_load(); diff --git a/backend/lib/Router.php b/backend/lib/Router.php index cfdb731..a30b52c 100644 --- a/backend/lib/Router.php +++ b/backend/lib/Router.php @@ -74,8 +74,8 @@ class Router { protected static $controllerMapping = array( 'channel' => 'Volkszaehler\Controller\ChannelController', 'group' => 'Volkszaehler\Controller\AggregatorController', - 'token' => 'Volkszaehler\Controller\TokenController', - 'capabilities' => 'Volkszaehler\Controller\CapabilitiesController', + 'group' => 'Volkszaehler\Controller\AggregatorController', + 'entity' => 'Volkszaehler\Controller\EntityController', 'data' => 'Volkszaehler\Controller\DataController' ); @@ -150,12 +150,7 @@ class Router { $controller = new $class($this->view, $this->em); if (isset($pathInfo[1])) { - if (Util\UUID::validate($pathInfo[1], TRUE)) { // TODO make universal - $result = $controller->run($this->operation, explode('/', $pathInfo[1])); - } - else { - throw new \Exception('Invalid parameter: ' . $pathInfo[1]); - } + $result = $controller->run($this->operation, array_slice($pathInfo, 1)); } else { $result = $controller->run($this->operation); diff --git a/backend/lib/View/JSON.php b/backend/lib/View/JSON.php index 241cd19..629392b 100644 --- a/backend/lib/View/JSON.php +++ b/backend/lib/View/JSON.php @@ -172,12 +172,14 @@ class JSON extends View { protected static function convertAggregator(Model\Aggregator $aggregator) { $jsonAggregator = self::convertEntity($aggregator); - foreach ($aggregator->getChannels() as $channel) { - $jsonAggregator['channels'][] = self::convertEntity($channel); - } + foreach ($aggregator->getChildren() as $entity) { - foreach ($aggregator->getChildren() as $subAggregator) { - $jsonAggregator['groups'][] = self::convertAggregator($subAggregator); // recursion + if ($entity instanceof Model\Channel) { + $jsonAggregator['channels'][] = self::convertEntity($entity); + } + elseif ($entity instanceof Model\Aggregator) { + $jsonAggregator['groups'][] = self::convertAggregator($entity); + } } return $jsonAggregator; diff --git a/share/sql/mysql.sql b/share/sql/mysql.sql index e06bba4..7d167c3 100644 --- a/share/sql/mysql.sql +++ b/share/sql/mysql.sql @@ -1,13 +1,12 @@ -CREATE TABLE entities (id SMALLINT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, type VARCHAR(255) NOT NULL, class VARCHAR(255) NOT NULL, UNIQUE INDEX entities_uuid_uniq (uuid), PRIMARY KEY(id)) ENGINE = InnoDB; -CREATE TABLE groups_channel (group_id SMALLINT NOT NULL, channel_id SMALLINT NOT NULL, PRIMARY KEY(group_id, channel_id)) ENGINE = InnoDB; -CREATE TABLE groups_groups (parent_id SMALLINT NOT NULL, child_id SMALLINT NOT NULL, PRIMARY KEY(parent_id, child_id)) ENGINE = InnoDB; -CREATE TABLE data (id SMALLINT AUTO_INCREMENT NOT NULL, channel_id SMALLINT DEFAULT NULL, timestamp BIGINT NOT NULL, value NUMERIC(5, 2) NOT NULL, UNIQUE INDEX unique_timestamp (timestamp, channel_id), PRIMARY KEY(id)) ENGINE = InnoDB; -CREATE TABLE tokens (id SMALLINT AUTO_INCREMENT NOT NULL, entity_id SMALLINT DEFAULT NULL, token VARCHAR(255) NOT NULL, valid BIGINT NOT NULL, UNIQUE INDEX tokens_token_uniq (token), PRIMARY KEY(id)) ENGINE = InnoDB; -CREATE TABLE properties (id SMALLINT AUTO_INCREMENT NOT NULL, entity_id SMALLINT DEFAULT NULL, `key` VARCHAR(255) NOT NULL, value VARCHAR(255) NOT NULL, UNIQUE INDEX unique_keys (entity_id, `key`), PRIMARY KEY(id)) ENGINE = InnoDB; -ALTER TABLE groups_channel ADD FOREIGN KEY (group_id) REFERENCES entities(id); -ALTER TABLE groups_channel ADD FOREIGN KEY (channel_id) REFERENCES entities(id); -ALTER TABLE groups_groups ADD FOREIGN KEY (parent_id) REFERENCES entities(id); -ALTER TABLE groups_groups ADD FOREIGN KEY (child_id) REFERENCES entities(id); -ALTER TABLE data ADD FOREIGN KEY (channel_id) REFERENCES entities(id); -ALTER TABLE tokens ADD FOREIGN KEY (entity_id) REFERENCES entities(id); -ALTER TABLE properties ADD FOREIGN KEY (entity_id) REFERENCES entities(id) +Creating database schema... + + + + [PDOException] + SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'entities' already exists + + + +orm:schema-tool:create [--dump-sql] [-h|--help] [-q|--quiet] [-v|--verbose] [-V|--version] [-a|--ansi] [-n|--no-interaction] command + +