Commit f7c5bdca authored by Tomas Lang's avatar Tomas Lang

Init commit

parents
.gitattributes export-ignore
.gitignore export-ignore
tests export-ignore
vendor
composer.lock
\ No newline at end of file
{
"name": "leanmapper-workflow",
"type": "library",
"description": "Workflow for LeanMapper ORM",
"authors": [
{
"name": "Tomas Lang",
"email": "tomas.lang@smsbrana.cz"
},
{
"name": "Jan Dvorak",
"email": "jan.dvorak@neogenia.cz"
}
],
"require": {
"nette/nette": "~2",
"tharos/leanmapper": "dev-develop"
},
"require-dev": {
"nette/tester": "@dev"
},
"autoload": {
"psr-0": {
"LeanMapperWorkflow": "src/"
}
}
}
<?php
namespace LeanMapperWorkflow;
abstract class AbstractRepository extends \LeanMapper\Repository
{
/**
* @return Restriction
*/
abstract public function createRestriction();
/**
* @param \DibiFluent $statement
* @param Restriction|null $restriction
* @param string|null $flag
*
* @throws \InvalidArgumentException
*/
protected function applyRestriction(\DibiFluent $statement, $restriction, $flag = NULL)
{
if ($restriction === NULL) {
$restriction = $this->createRestriction();
}
if ($restriction instanceof Restriction)
{
$restriction->apply($statement, $this, $flag);
} else {
throw new \InvalidArgumentException('Parameter "restriction" (' . get_class($restriction) . ') must be instance of ' . __NAMESPACE__ . '\\Restriction.');
}
}
}
<?php
namespace LeanMapperWorkflow;
abstract class AbstractRestriction
{
const FLAG_COUNT = 'count';
protected $properties = array();
private $values = array();
protected $mapper;
public function __construct(\LeanMapper\IMapper $mapper)
{
$this->mapper = $mapper;
$element = new \Nette\Reflection\ClassType(get_class($this));
do
{
$annotations = $element->getAnnotations('property');
if (isset($annotations['property']))
foreach ($annotations['property'] as $property)
if (($parsedProperty = $this->parseProperty($property)) !== NULL && !isset($this->properties[$parsedProperty['variable']]))
$this->properties[$parsedProperty['variable']] = $parsedProperty['type'];
if ($element instanceof \Nette\Reflection\ClassType)
$element = $element->getParentClass();
else
$element = NULL;
} while ($element !== NULL);
$this->initDefaults();
}
protected function initDefaults()
{
}
/**
* @param string $property
*
* @return array|null
*/
protected function parseProperty($property)
{
$res = \Nette\Utils\Strings::match($property, '#^\s*(?P<type>[a-z\\\|]+)\s+\$(?P<variable>[a-z0-9_-]+)\s*$#i');
return $res === NULL ? $res : array(
'type' => explode('|', $res['type']),
'variable' => $res['variable']
);
}
/**
* @param string $key
*
* @return boolean
*/
public function __isset($key)
{
return array_key_exists($key, $this->values);
}
/**
* @param string $key
*/
public function __unset($key)
{
if (isset($this->{$key}))
unset($this->values[$key]);
}
/**
* @param mixed $key
*
* @throws \InvalidArgumentException if $key is not defined, or value is unexpected
*
* @todo tady to jeste nejak lepe poresit
*/
public function __set($key, $value)
{
if (!isset($this->properties[$key]))
throw new \InvalidArgumentException("'{$key}' is not defined.");
$types = $this->properties[$key];
if (is_object($value))
{
$found = FALSE;
foreach ($types as $class)
if (class_exists($class) && $value instanceof $class)
$found = TRUE;
if (!$found)
throw new \InvalidArgumentException('Unexpected class.');
} elseif (is_array($value) && in_array('array', $types))
{
} elseif ($value === NULL && in_array('null', $types))
{
} elseif (in_array('int', $types) && is_numeric($value))
{
$value = (int) $value;
} elseif (in_array('bool', $types) || in_array('boolean', $types))
{
$value = (bool) $value;
} elseif (in_array('string', $types))
{
$value = (string) $value;
} else
{
throw new \InvalidArgumentException('Unexpected value.');
}
$this->values[$key] = $value;
}
/**
* @param string $key
*
* @return mixed
*
* @throws \InvalidArgumentException if $key is not filles or defined
*/
public function &__get($key)
{
if (!array_key_exists($key, $this->values))
throw new \InvalidArgumentException("'{$key}' is not filled or defined.");
return $this->values[$key];
}
/**
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* @param \DibiFluent $statement
* @param string|null $flag
*/
abstract public function apply(\DibiFluent $statement, Repository $repository, $flag = NULL);
}
<?php
namespace LeanMapperWorkflow;
abstract class Entity extends \LeanMapper\Entity
{
}
<?php
namespace LeanMapperWorkflow;
class EntityFactory implements \LeanMapper\IEntityFactory
{
/*
* @inheritdoc
*/
public function createEntity($entityClass, $arg = null)
{
return new $entityClass($arg);
}
/*
* @inheritdoc
*/
public function createCollection(array $entities, $class = NULL)
{
if (!(class_exists($class) && is_subclass_of($class, 'FakturacniSystem\\Collections\\Collection')))
$class = 'FakturacniSystem\\Collections\\Collection';
return new $class($entities);
}
}
<?php
namespace LeanMapperWorkflow;
/**
* Class Mapper
*/
class Mapper extends \LeanMapper\DefaultMapper
{
const STANDARD_NAMESPACE = 'App\\Model';
/** @var array */
protected $mapping;
/**
* @param array $tableMapping
*/
public function __construct($tableMapping = []) {
$this->tableMapping = (array) $tableMapping;
}
/**
* Vyhledá název třídy podle názvu tabulky
*
* @param string $tableName
*
* @return string - název třídy
*
* @throws \InvalidArgumentException
* @throws \Exception
*/
public function getNamespaceFromTable($tableName)
{
if (empty($tableName))
throw new \InvalidArgumentException('Method getNamespaceFromTable received invalid (empty) parameter.');
if (isset($this->tableMapping[$tableName]))
return $this->tableMapping[$tableName];
else {
return self::STANDARD_NAMESPACE . '\\' . implode('\\', array_map('ucfirst', explode('_', $tableName)));
}
}
/**
* Vyhledá název tabulky podle názvu třídy entity
*
* @param string $class
*
* @return string - název tabulky
*
* @throws \InvalidArgumentException
* @throws \Exception
*/
public function getTableFromClass($class)
{
$namespace = self::getNamespaceFromClass($class);
if (empty($namespace))
throw new \InvalidArgumentException('Method getTableFromClass received invalid (empty) parameter.');
$table = array_search($namespace, $this->tableMapping);
if ($table !== FALSE)
return $table;
else {
if (strpos($namespace, self::STANDARD_NAMESPACE) === 0) {
$namespace = str_replace(self::STANDARD_NAMESPACE, '', $namespace);
} else {
throw new \InvalidArgumentException('Unable to get table name for class "' . $class . '". Class need to be in "' . self::STANDARD_NAMESPACE . '" namespace');
}
return strtolower($this->decamelize(str_replace('\\', '_', ltrim($namespace, '\\'))));
}
}
/**
* Vrátí namespace bez názvů tříd, které jsou definovány polem
*
* @param string $className
*
* @return string
*
* @todo doplnit pole tříd o další názvy
*/
public static function getNamespaceFromClass($className)
{
return preg_replace('~\\\\(Entity|Repository|Filter|Restriction)$~', '', $className);
}
/**
* @inheritdoc
*/
public function getTable($entityClass)
{
return $this->getTableFromClass($entityClass);
}
/**
* @inheritdoc
*/
public function getEntityClass($table, \LeanMapper\Row $row = null)
{
return $this->getNamespaceFromTable($table) . '\\Entity';
}
/**
* @param string $entityClass
* @param string $field
*
* @return string
*/
public function getColumn($entityClass, $field)
{
return self::decamelize($field);
}
/**
* @inheritdoc
*/
public function getEntityField($table, $column)
{
return parent::getEntityField($table, self::camelize($column));
}
/**
* @inheritdoc
*/
public function getTableByRepositoryClass($repositoryClass)
{
return $this->getTableFromClass($repositoryClass);
}
/**
* Converts camel-case to underscore
*
* @param string $word
*
* @return string
*/
static public function decamelize($word) {
return preg_replace_callback('/(^|[a-z])([A-Z0-9])/', function($matches) {
return strtolower(strlen($matches[1]) ? ($matches[1] . '_' . $matches[2]) : $matches[2]);
}, $word);
}
/**
* Converts underscore to camel-case
*
* @param string $word
*
* @return string
*/
static public function camelize($word) {
return lcfirst(preg_replace_callback('/(^|_)([a-z0-9])/', function($matches) {
return strtoupper($matches[2]);
}, $word));
}
}
<?php
namespace LeanMapperWorkflow;
use InvalidArgumentException;
/**
* Class Repository
*/
class Repository extends AbstractRepository
{
/**
* @param int|Restriction $id
*
* @return Entity
*
* @throws InvalidArgumentException
*/
public function find($id)
{
if (is_numeric($id)) {
$restriction = $this->createRestriction();
$restriction->id = $id;
} elseif ($id instanceof Restriction) {
$restriction = $id;
} else {
throw new InvalidArgumentException('Parameter of the method "find" in '.get_class($this).' is not numeric and is not instance of Restriction either. It is instace of "'.(gettype($id) === 'object' ? get_class($id) : gettype($id)).'".');
}
$statement = $this->connection->select('%n.*', $this->getTable())->from($this->getTable());
$this->applyRestriction($statement, $restriction);
$row = $statement->fetch();
if ($row === FALSE) {
throw new \InvalidArgumentException('Not found: ' . var_export($id, TRUE));
}
return $this->createEntity($row);
}
/**
* @param Restriction|int[]|null $param
*
* @return Entity[]
*
* @throws InvalidArgumentException
*/
public function findAll($param = NULL)
{
if (is_array($param)) {
if (count($param) === 0) {
return $this->createEntities([]);
}
$restriction = $this->createRestriction();
$restriction->id = array_map('intval', $param);
} elseif ($param === NULL) {
$restriction = $this->createRestriction();
} elseif ($param instanceof Restriction) {
$restriction = $param;
} else {
throw new InvalidArgumentException('Parameter of the method "findAll" in ' . get_class($this) . ' is not numeric and is not instance of Restriction and is not NULL either. It is instace of "' . get_class($idOrRestriction) . '".');
}
$statement = $this->connection->select('%n.*', $this->getTable())->from($this->getTable());
$this->applyRestriction($statement, $restriction);
return $this->createEntities($statement->fetchAll());
}
/**
* @param Restriction|null $param
*
* @return int
*
* @throws InvalidArgumentException
*/
public function count($param = NULL)
{
if ($param === NULL) {
$restriction = $this->createRestriction();
} elseif ($param instanceof Restriction) {
$restriction = $param;
} else {
throw new InvalidArgumentException('@todo');
}
$restriction->limit = 1;
$statement = $this->connection->select('COUNT(*)')->from($this->getTable());
$this->applyRestriction($statement, $restriction, Restriction::FLAG_COUNT);
return (int) $statement->fetchSingle();
}
/**
* @param string $key
* @param string $value
* @param Restriction|null $param
*
* @return array
*
* @throws InvalidArgumentException
*/
public function fetchPairs($key, $value, $param = NULL)
{
if ($param === NULL) {
$restriction = $this->createRestriction();
} elseif ($param instanceof Restriction) {
$restriction = $param;
} else {
throw new InvalidArgumentException('@todo');
}
$statement = $this->connection->select('%n.%n, %n.%n', $this->getTable(), $key, $this->getTable(), $value)->from($this->getTable());
$this->applyRestriction($statement, $restriction);
return $statement->fetchPairs($key, $value);
}
/**
* @return Restriction
*/
public function createRestriction()
{
$restriction = Mapper::getNamespaceFromClass(get_class($this)) . '\\Restriction';
if (!class_exists($restriction))
$restriction = 'FakturacniSystem\\LeanMapperWorkflow\\Restriction';
return new $restriction($this->mapper);
}
}
<?php
namespace LeanMapperWorkflow;
/**
* @property array|int $id
* @property int $limit
* @property int $offset
*/
class Restriction extends AbstractRestriction
{
protected $joins = [];
/**
* @param \DibiFluent $statement
* @param Repository $repository
* @param string|null $flag
*/
public function apply(\DibiFluent $statement, Repository $repository, $flag = NULL)
{
if(isset($this->id)) {
if (is_array($this->id)) {
if (count($this->id) === 0) {
throw new \UnexpectedValueException('There must be provided at least one id');
}
$statement->where('%n.[id] IN %l', $this->getTable($repository), $this->id);
} else {
$statement->where('%n.[id] = %i', $this->getTable($repository), $this->id);
}
}
if (isset($this->limit) && $flag !== self::FLAG_COUNT)
$statement->limit($this->limit);
if (isset($this->offset) && $flag !== self::FLAG_COUNT)
$statement->offset($this->offset);
}
/**
* @param Repository $repository
* @return string
*/
protected function getTable(Repository $repository)
{
return $this->mapper->getTableByRepositoryClass(get_class($repository));
}
public function refreshJoins()
{
foreach ($this->joins as &$join) {
$join['used'] = FALSE;
}
}
/**
* @param string $name
* @param callback $callback callback($statement)
* @param \DibiFluent $statement if set is automaticallly applied
*/
protected function addJoin($name, $callback, \DibiFluent $statement = NULL)
{
if (!isset($this->joins[$name])) {
$this->joins[$name] = ['callback' => $callback, 'used' => FALSE];
}
if ($statement !== NULL && !$this->joins[$name]['used']) {
$this->joins[$name]['callback']($statement);
$this->joins[$name]['used'] = TRUE;
}
}
/**
* [applyJoins description]
* @param [type] $statement [description]
* @return [type] [description]
*/
protected function applyJoins(\DibiFluent $statement)
{
foreach ($this->joins as &$join) {
if (!$join['used']) {
$join['callback']($statement);
$join['used'] = TRUE;
}
}
}
}
<?php
namespace LeanMapperWorkflow;
class RestrictionWithOrdering extends Restriction
{
const ORDER_ID = 'id';
/** @var array(self::ORDER_* => "ASC"|"DESC"|mixed) */
protected $orderBys = [];
/**
* @param \DibiFluent $statement
* @param Repository $repository
* @param string|null $flag
*/
public function apply(\DibiFluent $statement, Repository $repository, $flag = NULL)
{
parent::apply($statement, $repository, $flag);
if (count($this->orderBys)) {
foreach ($this->orderBys as $order => $direction) {
$this->applyOrderBy($order, $direction, $statement, $repository, $flag);
}
}
}
/**
* @param array $orderBys
*/
public function setOrderBys(array $orderBys)
{
$this->orderBys = $orderBys;
}
/**
* @param string $order self::ORDER_*
* @param string $direction "ASC"|"DESC"|mixed
*/
public function addOrderBy($order, $direction)
{
$this->orderBys[$order] = $direction;
}
/**
* @param string $order self::ORDER_*
* @param string $direction "ASC"|"DESC"|mixed
* @param \DibiFluent $statement
* @param Repository $repository
* @param string|null $flag
*/
protected function applyOrderBy($order, $direction, \DibiFluent $statement, Repository $repository, $flag = NULL)
{
if ($order === self::ORDER_ID) {
$statement->orderBy('%n.[id] %sql', $direction);
}
}
}
tmp
output
test.log
php-win.ini
RunTests.bat
coverage.dat
db/library.sq3
LeanMapper/dev*.phpt
<?php
use Tester\Assert;
$container = require __DIR__ . '/../bootstrap.php';
$mapper = new \LeanMapperWorkflow\Mapper(array(
'table' => 'My\\Super\\Namespace'
));
// get namespace from table
Assert::same('My\\Super\\Namespace', $mapper->getNamespaceFromTable('table'));
Assert::same('App\\Model\\Other\\Table', $mapper->getNamespaceFromTable('other_table'));
Assert::exception(function() use ($mapper) {
$mapper->getNamespaceFromTable('');
}, 'InvalidArgumentException', 'Method getNamespaceFromTable received invalid (empty) parameter.');
// get table from class
Assert::same('table', $mapper->getTableFromClass('My\\Super\\Namespace'));
Assert::same('table', $mapper->getTableFromClass('My\\Super\\Namespace\\Entity'));
Assert::same('my_another_class', $mapper->getTableFromClass('App\\Model\\My\\Another\\Class'));
Assert::exception(function() use ($mapper) {
$mapper->getTableFromClass('My\\Another\\Class');
}, 'InvalidArgumentException', 'Unable to get table name for class "My\\Another\\Class". Class need to be in "' . \LeanMapperWorkflow\Mapper::STANDARD_NAMESPACE . '" namespace');
Assert::exception(function() use ($mapper) {
$mapper->getTableFromClass('');
}, 'InvalidArgumentException', 'Method getTableFromClass received invalid (empty) parameter.');
// get namespace from class
Assert::same($mapper::getNamespaceFromClass('My\\Super\\Entity'), 'My\\Super');
Assert::same($mapper::getNamespaceFromClass('My\\Super\\Repository'), 'My\\Super');
Assert::same($mapper::getNamespaceFromClass('My\\Super\\Filter'), 'My\\Super');
Assert::same($mapper::getNamespaceFromClass('My\\Super\\Restriction'), 'My\\Super');
Assert::same($mapper::getNamespaceFromClass('My\\Super\\Namespace'), 'My\\Super\\Namespace');
Assert::same($mapper::getNamespaceFromClass('My\\Super\\Entity\\Namespace'), 'My\\Super\\Entity\\Namespace');
<?php
use Tester\Assert;
$container = require __DIR__ . '/../bootstrap.php';
/**
* @property string $string
* @property int $int
* @property bool $bool
* @property \DateTime $dateTime
* @property string|null $stringOrNull
* @property \DateTime|\stdClass $dateTimeOrStdClass
*/
class MyRestriction extends \LeanMapperWorkflow\AbstractRestriction
{
public function apply(\DibiFluent $statement, \LeanMapperWorkflow\Repository $repository, $flag = NULL)
{
}
}
/**
* @property int $string
* @property string $extended
*/
class ExtendedRestriction extends MyRestriction
{
}
$mapper = new \LeanMapper\DefaultMapper;
// read values
$restriction = new MyRestriction($mapper);
Assert::same($restriction->getValues(), array());
$restriction->string = '123';
$restriction->int = 123;
Assert::same($restriction->getValues(), array(
'string' => '123',
'int' => 123
));
// read only set parameters
$restriction = new MyRestriction($mapper);
Assert::false(isset($restriction->string));
Assert::exception(function() use ($restriction) {
$value = $restriction->string;
}, 'InvalidArgumentException');
$restriction->string = '123';
Assert::true(isset($restriction->string));
Assert::same($restriction->string, '123');
unset($restriction->string);
Assert::false(isset($restriction->string));
Assert::exception(function() use ($restriction) {
$value = $restriction->string;
}, 'InvalidArgumentException');
// parameter conversions
$restriction = new MyRestriction($mapper);
$restriction->string = 123;
Assert::same($restriction->string, '123');
$restriction->int = '123';
Assert::same($restriction->int, 123);
$restriction->bool = '123';
Assert::same($restriction->bool, TRUE);
// non-exists parameter
$restriction = new MyRestriction($mapper);
Assert::exception(function() use ($restriction) {
$restriction->iAmNotExists = true;
}, 'InvalidArgumentException');
// parameter extending and overwriting
$restriction = new ExtendedRestriction($mapper);
$restriction->string = '123';
Assert::same($restriction->string, 123); // $string je v ExtendedRestriction typu int
$restriction->int = '123';
Assert::same($restriction->int, 123); // $int je v ExtendedRestriction zdeden od MyRestriction
$restriction->extended = '123';
Assert::same($restriction->extended, '123'); // $extended je vlastni promenna ExtendedRestriction
<?php
if (@!include __DIR__ . '/../vendor/autoload.php') {
echo 'Install Nette Tester using `composer update --dev`';
exit(1);
}
// configure environment
Tester\Environment::setup();
class_alias('Tester\Assert', 'Assert');
date_default_timezone_set('Europe/Prague');
$_SERVER = array_intersect_key($_SERVER, array_flip(array('PHP_SELF', 'SCRIPT_NAME', 'SERVER_ADDR', 'SERVER_SOFTWARE', 'HTTP_HOST', 'DOCUMENT_ROOT', 'OS', 'argc', 'argv')));
$_SERVER['REQUEST_TIME'] = 1234567890;
$_ENV = $_GET = $_POST = array();
if (extension_loaded('xdebug')) {
xdebug_disable();
Tester\CodeCoverage\Collector::start(__DIR__ . '/coverage.dat');
}
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