Commit 727897b9 authored by Karamel's avatar Karamel
Browse files

Add RecordNotFoundException and InvalidFieldException with association values

on fromStruct.
parent 350fe5b7
......@@ -33,6 +33,8 @@ class InvalidFieldException extends \Exception implements PastequeException
/** The field is a reference to an other record but this record
* was not found. */
const CSTR_ASSOCIATION_NOT_FOUND = 'association_not_found';
/** The field is null but it cannot be. */
const CSTR_NOT_NULL = 'not_null';
/** The field is a reference to a cash session but this session
* must be opened. */
const CSTR_OPENED_CASH = 'opened_cash_required';
......
<?php
// Pastèque API
//
// Copyright (C) 2012-2015 Scil (http://scil.coop)
// Cédric Houbart, Philippe Pary
//
// This file is part of Pastèque.
//
// Pastèque is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Pastèque is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Pastèque. If not, see <http://www.gnu.org/licenses/>.
namespace Pasteque\Server\Exception;
/**
* A record was requested through it's id but nothing was found.
*/
class RecordNotFoundException extends \Exception implements PastequeException
{
private $class;
private $id;
public function __construct($class, $id) {
$this->class = $class;
$this->id = $id;
$msg = sprintf("%s with id %s was not found",
$this->class, $this->getJsonableId());
parent::__construct($msg);
}
public function getClass() {
return $this->class;
}
public function getId() {
return $this->id;
}
public function getJsonableId() {
if (gettype($this->id) == 'resource') {
return '<resource>';
} else {
return $this->id;
}
}
public function toStruct() {
return [
'error' => 'RecordNotFoundException',
'class' => $this->class,
'id' => $this->getJsonableId(),
'message' => $this->message,
];
}
}
......@@ -21,6 +21,8 @@
namespace Pasteque\Server\System\DAO;
use \Pasteque\Server\Exception\RecordNotFoundException;
/** Parent class of all models to use with Doctrine. */
abstract class DoctrineModel
{
......@@ -149,17 +151,24 @@ abstract class DoctrineModel
return $methodName;
}
/** Build an instance from struct. If id is set, it will ask the DAO to set
/*
* Build an instance from struct. If id is set, it will ask the DAO to set
* the current values. If not a new instance is created and returned.
* @throws \UnexpectedValueException If id is set but no data is found. */
protected static function instanceFromStruct($struct, $dao, $parent = null) {
* @param $struct The record as associative array.
* @param $dao The DAO to use.
* @throws RecordNotFoundException If id is set but no data is found,
* or if an associated record was not found with the given id.
*/
protected static function instanceFromStruct($struct, $dao,
$parent = null) {
// $parent is only for recursive internal calls.
$model = null;
if (isset($struct['id'])) {
$model = $dao->read(static::class, $struct['id']);
if ($model === null && $parent === null) {
// Embedded classes don't have their own id, so it's ok
// not to find them: it's a new instance.
throw new \UnexpectedValueException('No data found with this Id');
throw new RecordNotFoundException(static::class, $struct['id']);
}
} else {
/* Maybe the id was not set, because that doesn't make sense
......@@ -215,6 +224,7 @@ abstract class DoctrineModel
}
return $model;
}
/** Set the primitive typed fields from struct. */
protected function directFieldsFromStruct($struct, $dao) {
foreach (static::getDirectFieldNames() as $field) {
......@@ -231,6 +241,16 @@ abstract class DoctrineModel
}
}
/**
* Get an associated record to link it to it's parent.
* @param $struct The parent data as associative array.
* @param $dao The DAO to use.
* @param $field The field description of the association (from
* getAssociationFields).
* @return The associated record or an array of associated records.
* @throws RecordNotFoundException when an associated record cannot be
* found with the given id (only for non-embedded records).
*/
protected function readAssociationValue($struct, $dao, $field) {
if (!empty($field['array'])) {
$submodels = new \Doctrine\Common\Collections\ArrayCollection();
......@@ -241,7 +261,8 @@ abstract class DoctrineModel
} else {
$submodel = $dao->read($field['class'], $content['id']);
if ($submodel === null) {
throw new \UnexpectedValueException(sprintf('%s was not found', $field['name']));
throw new RecordNotFoundException($field['class'],
$struct[$field['name']]);
}
}
$submodels->add($submodel);
......@@ -255,13 +276,16 @@ abstract class DoctrineModel
} else {
$submodel = $dao->read($field['class'], $struct[$field['name']]);
if ($submodel === null) {
throw new \UnexpectedValueException(sprintf('%s was not found', $field['name']));
throw new RecordNotFoundException($field['class'],
$struct[$field['name']]);
}
}
return $submodel;
}
}
/** Build a model linked to the DAO from raw data.
/**
* Build a model linked to the DAO from raw data.
* @param $struct An associative array containing the data
* (hasImage is a special field and is ignored from the struct).
* WARNING: Use to/fromStruct only once and do never switch back and forth
......@@ -269,8 +293,11 @@ abstract class DoctrineModel
* ids and duplicate or delete some records.
* @param $dao The DAO to link to.
* @param $parent Only for recursive calls, don't set it.
* @throw \UnexpectedValueException If Id is set but not data is found.
* Or when an non nullable field is not set. */
* @throws RecordNotFoundException If Id is set but not data is found,
* either for this record or one of it's associated records.
* @throws InvalidFieldException When an non nullable association field
* is not set (NOT_NULL).
*/
public static function fromStruct($struct, $dao, $parent = null) {
$model = static::instanceFromStruct($struct, $dao, $parent);
$model->directFieldsFromStruct($struct, $dao);
......@@ -285,7 +312,9 @@ abstract class DoctrineModel
if (empty($field['null'])
&& (!array_key_exists($fieldName, $struct) || $struct[$fieldName] === null)) {
if (empty($field['array'])) {
throw new \UnexpectedValueException(sprintf('%s is required', $fieldName));
throw new InvalidFieldException(InvalidFieldException::NOT_NULL,
static::class, $field['name'],
$struct['id'], null);
} else {
// Allow omitting an array for keeping its value
// or setting it to null to clear it.
......
......@@ -22,6 +22,7 @@
namespace Pasteque\Server;
use \Pasteque\Server\AppContext;
use \Pasteque\Server\Exception\RecordNotFoundException;
use \Pasteque\Server\Model\Category;
use \Pasteque\Server\System\DAO\DAOCondition;
use \Pasteque\Server\System\DAO\DBException;
......@@ -365,4 +366,77 @@ class DoctrineDAOTest extends TestCase
$search = $dao->search(static::MODEL_NAME, [new DAOCondition('reference', '=', 'A'), new DAOCondition('label', '=', 'b')]);
$this->assertEquals(0, count($search));
}
public function testFromStructNoRecord() {
global $dbInfo;
$dao = new DoctrineDAO($dbInfo);
$struct = ['reference' => 'ref', 'label' => 'lbl', 'parent' => null,
'dispOrder' => 2];
$cat = Category::fromStruct($struct, $dao);
$this->assertNull($cat->getId());
$this->assertEquals('ref', $cat->getReference());
$this->assertEquals('lbl', $cat->getLabel());
$this->assertEquals(2, $cat->getDispOrder());
}
/** @depends testReadSnapshot
* @depends testCount
*/
public function testFromStructWrite() {
global $dbInfo;
$dao = new DoctrineDAO($dbInfo);
$a = $this->buildCat('a');
$dao->write($a);
$dao->commit();
$struct = ['id' => $a->getId(), 'reference' => 'editedRef',
'label' => 'editedLabel', 'parent' => null,
'dispOrder' => 2];
$cat = Category::fromStruct($struct, $dao);
$dao->write($cat);
$dao->commit();
$this->assertEquals(1, $dao->count(Category::class));
$readSnap = $dao->readSnapshot(Category::class, $a->getId());
$this->assertEquals('editedRef', $readSnap->getReference());
$this->assertEquals('editedLabel', $readSnap->getLabel());
$this->assertEquals(2, $readSnap->getDispOrder());
}
public function testFromStructWrongId() {
global $dbInfo;
$dao = new DoctrineDAO($dbInfo);
$struct = ['id' => 1, 'reference' => 'ref', 'label' => 'lbl',
'parent' => null, 'dispOrder' => '2'];
$exceptionThrown = false;
try {
$cat = Category::fromStruct($struct, $dao);
} catch (RecordNotFoundException $e) {
$exceptionThrown = true;
$this->assertEquals(Category::class, $e->getClass());
$this->assertEquals(1, $e->getId());
}
$this->assertTrue($exceptionThrown,
'Expecting RecordNotFoundException.');
}
/** @depends testWCR */
public function testFromStructWrongChildId() {
global $dbInfo;
$dao = new DoctrineDAO($dbInfo);
$a = $this->buildCat('a');
$dao->write($a);
$dao->commit();
$struct = ['id' => $a->getId(), 'reference' => 'editedRef',
'label' => 'editedLabel', 'parent' => $a->getId() + 2,
'dispOrder' => 2];
$exceptionThrown = false;
try {
$cat = Category::fromStruct($struct, $dao);
} catch (RecordNotFoundException $e) {
$exceptionThrown = true;
$this->assertEquals(static::MODEL_NAME, $e->getClass());
$this->assertEquals($a->getId() + 2, $e->getId());
}
$this->assertTrue($exceptionThrown,
'Expecting RecordNotFoundException.');
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment