Skip to content

Commit

Permalink
Merge pull request #21 from matryoshka-model/develop
Browse files Browse the repository at this point in the history
v0.6.0
  • Loading branch information
leodido committed Feb 23, 2015
2 parents ed46b1a + 95d9cb3 commit d60b97c
Show file tree
Hide file tree
Showing 121 changed files with 2,242 additions and 704 deletions.
42 changes: 31 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
# Matryoshka [![Latest Stable Version](https://poser.pugx.org/matryoshka-model/matryoshka/v/stable.png)](https://packagist.org/packages/matryoshka-model/matryoshka) [![Dependency Status](https://www.versioneye.com/user/projects/5432e02184981fb412000144/badge.svg)](https://www.versioneye.com/user/projects/5432e02184981fb412000144) [![Total Downloads](https://poser.pugx.org/matryoshka-model/matryoshka/downloads.svg)](https://packagist.org/packages/matryoshka-model/matryoshka)
# Matryoshka

[![Latest Stable Version](http://img.shields.io/packagist/v/matryoshka-model/matryoshka.svg?style=flat-square)](https://packagist.org/packages/matryoshka-model/matryoshka) [![Total Downloads](https://img.shields.io/packagist/dt/matryoshka-model/matryoshka.svg?style=flat-square)](https://packagist.org/packages/matryoshka-model/matryoshka)

| Master | Develop |
|:-------------:|:-------------:|
| [![Build Status](https://secure.travis-ci.org/matryoshka-model/matryoshka.svg?branch=master)](https://travis-ci.org/matryoshka-model/matryoshka) | [![Build Status](https://secure.travis-ci.org/matryoshka-model/matryoshka.svg?branch=develop)](https://travis-ci.org/matryoshka-model/matryoshka) |
| [![Coverage Status](https://coveralls.io/repos/matryoshka-model/matryoshka/badge.png?branch=master)](https://coveralls.io/r/matryoshka-model/matryoshka) | [![Coverage Status](https://coveralls.io/repos/matryoshka-model/matryoshka/badge.png?branch=develop)](https://coveralls.io/r/matryoshka-model/matryoshka) |
| [![Build Status](https://img.shields.io/travis/matryoshka-model/matryoshka/master.svg?style=flat-square)](https://travis-ci.org/matryoshka-model/matryoshka) | [![Build Status](https://img.shields.io/travis/matryoshka-model/matryoshka/develop.svg?style=flat-square)](https://travis-ci.org/matryoshka-model/matryoshka) |
| [![Coveralls branch](https://img.shields.io/coveralls/matryoshka-model/matryoshka/master.svg?style=flat-square)](https://coveralls.io/r/matryoshka-model/matryoshka?branch=master) | [![Coveralls branch](https://img.shields.io/coveralls/matryoshka-model/matryoshka/develop.svg?style=flat-square)](https://coveralls.io/r/matryoshka-model/matryoshka?branch=develop) |

Matryoshka is a model [service layer](http://martinfowler.com/eaaCatalog/serviceLayer.html) that normalizes and standardizes your model's interface use, whether you are using Zend\Db, Mongo, Doctrine or anything else.
Matryoshka is a lightweight framework that provides a standard and easy way to implement a model [service layer](http://martinfowler.com/eaaCatalog/serviceLayer.html).
Its layered design aims to provide a strong seperation between the persistence and the rest of your application, whether the datagateway you need to use (i.e. Zend\Db, MongoCollection, an ORM, a REST client or anything else).
In order to work with Matryoshka, developers need just to setup services using its comprehensive configuration system and implement very simple [criteria](http://en.wikipedia.org/wiki/Criteria_Pattern) interfaces.
Furthermore, a set of [wrapper](http://en.wikipedia.org/wiki/Wrapper_library) for common persistence systems are provided as separeted libraries.

## Theory of operation

Matryoshka provides an handful API based on **criteria** interfaces. Think about criterias as a set of small objects.
Matryoshka doesn't provide a persistence layer implementation itself, but it can work with any third party implementation that acts as datagateway. Regardless of the datagateway you choose, Matryoshka provides to clients (i.e. your controller) the same set of handful API. To accomplish this goal Matryoshka uses criteria interfaces that developer have to implement.

Think a criteria as a small piece of code that tell to the datagateway how to perform an operation on your dataset: for example filter some rows. So, each criteria represent a simple task: different kind of criteria interfaces are defined for write, read and delete operations. Also, in concrete criteria classes, the developer can add methods to augment the "query interface" with domain specific logic.

The developer have to implement them: each criteria encapsulates a small piece of business logic and exposes a small interface.
A concrete criteria class acts both as a criterion defining a query interface and also as [mediator](http://en.wikipedia.org/wiki/Mediator_pattern) between model layer and datagateway. Only criteria classes performs operations against the datagateway interface, instead Matryoshka's components do not.

Criterias use the datagateway, instead Matryoshka's components do not use datagateway directly, so any kind of datagateway can be used.
So Matryoska is unawareness about the datagateway interface, that makes it working with any kind of third-party datagateway implementation.

Main layers:
Matryoshka dolls (layers):

* ModelManager
A dedicated service locator for your model service classes (i.e., model)

* Model
A service class representing a collection of entities that provides common features in a centralized way (e.g., CRUD, result set, paginating, hydrating, input filtering)
A service class representing a collection of entities that provides common features in a centralized way (e.g., CRUD, resultset, paginating, hydrating, input filtering)

* Criteria
An "user query intefarce" from an API point of view, acting as mediator between model and datagateway
An "user query intefarce" from an API point of view, also acting as mediator between model and datagateway

* Datagateway
Any kind of datagateway, like `Zend\Db\TableGateway` or `\MongoCollection`

Basic usage example:

```php
//Assiming MyModel an instance of a Matryoshka Model class registered in Matryoshka model manager
$myModel = $modelManager->get('MyModel');

//Assuming MyCriteria a class extending ReadableCriteriaInterface
$criteria = new MyCriteria();
$criteria->setMyCustomFilter('foo');

//Execute a query
$resultSet = $myModel->find($criteria);
```

## Installation

Expand All @@ -39,7 +59,7 @@ Add the following to your `composer.json` file:
```
"require": {
"php": ">=5.4",
"matryoshka-model/matryoshka": "~0.5.0",
"matryoshka-model/matryoshka": "~0.6.0",
}
```

Expand Down
17 changes: 7 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "matryoshka-model/matryoshka",
"description": "Model Service Layer that normalize and standardize your model's interface use",
"description": "A lightweight framework that provides a standard and easy way to implement a model service layer",
"homepage": "https://github.com/matryoshka-model/matryoshka",
"license": "BSD-2-Clause",
"require": {
Expand Down Expand Up @@ -39,13 +39,10 @@
}
],
"autoload": {
"psr-0": {
"Matryoshka": "library/",
"MatryoshkaTest": "tests/"
},
"classmap": [
"./"
]
"psr-4": {
"Matryoshka\\Model\\": "library/",
"MatryoshkaTest\\Model\\": "tests/"
}
},
"keywords": [
"model",
Expand All @@ -57,8 +54,8 @@
],
"extra": {
"branch-alias": {
"dev-master": "0.5.x-dev",
"dev-develop": "0.6.x-dev"
"dev-master": "0.6.x-dev",
"dev-develop": "0.7.x-dev"
}
},
"suggest": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ public function getObjectPrototype()
{
$resultSetPrototype = $this->getResultSetPrototype();
if ($resultSetPrototype && ($objectPrototype = $resultSetPrototype->getObjectPrototype())) {
if ($objectPrototype instanceof ModelAwareInterface) {
$objectPrototype->setModel($this);
}
return $objectPrototype;
}

Expand All @@ -127,6 +130,9 @@ public function create()
*/
public function find(ReadableCriteriaInterface $criteria)
{
// Ensure that object and resultset prototypes have been set
$this->getObjectPrototype();

$result = $criteria->apply($this);
$resultSet = clone $this->getResultSetPrototype();
$resultSet->initialize($result);
Expand All @@ -140,34 +146,48 @@ public function find(ReadableCriteriaInterface $criteria)
*
* @param WritableCriteriaInterface $criteria
* @param HydratorAwareInterface|object|array $dataOrObject
* @throws Exception\InvalidArgumentException
* @throws Exception\RuntimeException
* @return null|int
*/
public function save(WritableCriteriaInterface $criteria, $dataOrObject)
{

$isObject = is_object($dataOrObject);
$hydrator = $this->getHydrator();

if (!$hydrator && ($dataOrObject instanceof HydratorAwareInterface)) {
$hydrator = $dataOrObject->getHydrator();
if ($isObject) {
$objectPrototype = $this->getObjectPrototype();
if (!$dataOrObject instanceof $objectPrototype) {
throw new Exception\InvalidArgumentException(sprintf(
'$dataOrObject with type "%s" is not valid for this model, expected an instance of "%s"',
get_class($dataOrObject),
get_class($objectPrototype)
));
}

if ($dataOrObject instanceof ModelAwareInterface) {
$dataOrObject->setModel($this);
}

if (!$hydrator && ($dataOrObject instanceof HydratorAwareInterface)) {
$hydrator = $dataOrObject->getHydrator();
}
}

if ($hydrator && is_object($dataOrObject)) {
if ($hydrator && $isObject) {
$data = $hydrator->extract($dataOrObject);
} else {

if (is_array($dataOrObject)) {
$data = $dataOrObject;
} elseif (method_exists($dataOrObject, 'toArray')) {
$data = $dataOrObject->toArray();
} elseif (method_exists($dataOrObject, 'getArrayCopy')) {
$data = $dataOrObject->getArrayCopy();
} else {
throw new Exception\RuntimeException(
'dataOrObject with type ' .
gettype($dataOrObject) .
' cannot be casted to an array'
);
throw new Exception\InvalidArgumentException(sprintf(
'$dataOrObject with type "%s" cannot be casted to an array',
gettype($dataOrObject)
));
}

if ($hydrator) {
Expand All @@ -178,8 +198,9 @@ public function save(WritableCriteriaInterface $criteria, $dataOrObject)
);
}
$data = [];
$context = $isObject ? $dataOrObject : (object) $dataOrObject;
foreach ($dataOrObject as $key => $value) {
$data[$key] = $hydrator->extractValue($key, $value, $dataOrObject);
$data[$key] = $hydrator->extractValue($key, $value, $context);
}

}
Expand All @@ -191,7 +212,7 @@ public function save(WritableCriteriaInterface $criteria, $dataOrObject)
$result = null;
}

if ($result && $hydrator && is_object($dataOrObject)) {
if ($result && $hydrator && $isObject) {
$hydrator->hydrate($data, $dataOrObject);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/
namespace Matryoshka\Model\Criteria;

use Matryoshka\Model\Exception;
use Matryoshka\Model\ModelInterface;

/**
Expand All @@ -18,40 +17,65 @@ abstract class AbstractCriteria implements ReadableCriteriaInterface
{
/**
* Limit
*
* @var int
*/
protected $limit;

/**
* Offset
*
* @var int
*/
protected $offset;

/**
* Limit
* @param int $limit
* Set limit
*
* @param int|null $limit
* @return $this
*/
public function limit($limit)
public function setLimit($limit)
{
$this->limit = (int)$limit;
$this->limit = $limit === null ? null : (int) $limit;
return $this;
}

/**
* Offset
* @param int $offset
* Get limit
*
* @return int|null
*/
public function getLimit()
{
return $this->limit;
}

/**
* Set offset
*
* @param int|null $offset
* @return $this
*/
public function offset($offset)
public function setOffset($offset)
{
$this->offset = (int)$offset;
$this->offset = $offset === null ? null : (int) $offset;
return $this;
}

/**
* Get offset
*
* @return int|null
*/
public function getOffset()
{
return $this->offset;
}

/**
* Apply
*
* @param ModelInterface $model
* @return mixed
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@
/**
* Class AbstractCriteria
*
* A particular kind of CriteriaInterface in order to work with an Active Record object.
* A particular kind of CriteriaInterface in order to work with an ActiveRecord object.
* This criteria works always with just one object at time. For read and delete operations
* an id must be set using setId().
* @todo Define the applyWrite behavior releted to the id presence
*
*/
abstract class AbstractCriteria implements
ReadableCriteriaInterface,
WritableCriteriaInterface,
DeletableCriteriaInterface
{
/**
* @var mixed
*/
protected $id;

/**
Expand All @@ -39,11 +46,13 @@ public function setId($id)
/**
* Get Id
* @return mixed
*/
*/
public function getId()
{
if (!$this->id) {
throw new Exception\RuntimeException('In order to work with ActiveRecord criteria the id must be set');
throw new Exception\RuntimeException(
'getId(), apply() and applyDelete() require that an id must be present using a prior call to setId()'
);
}
return $this->id;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,37 @@
*/
namespace Matryoshka\Model\Criteria;

use Matryoshka\Model\Exception;
use Matryoshka\Model\ModelInterface;
use Matryoshka\Model\Exception;

/**
* Class CallableCriteria
* Class CallbackCriteria
*/
class CallableCriteria extends AbstractCriteria implements ReadableCriteriaInterface
class CallbackCriteria extends AbstractCriteria implements ReadableCriteriaInterface
{
/**
* Callable
* @var mixed
* @var callable
*/
protected $callable;
protected $callback;

/**
* Ctor
* @param $callable
* @param callable $callable
*/
public function __construct($callable)
public function __construct(callable $callback)
{
$this->callable = $callable;
$this->callback = $callback;
}

/**
* {@inheritdoc}
*/
public function apply(ModelInterface $model)
{
return call_user_func($this->callable, $model);
$callback = $this->callback;
if ($callback instanceof \Closure) {
$callback = $callback->bindTo($this);
}
return call_user_func($callback, $model);
}
}
File renamed without changes.

0 comments on commit d60b97c

Please sign in to comment.