<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Traits\ServiceAware;

use Velocity\Bundle\ApiBundle\Service\WorkflowService;

/**
 * WorkflowServiceAware trait.
 *
 * @author <NAME> <<EMAIL>>
 */
trait WorkflowServiceAwareTrait
{
    /**
     * @param string $key
     * @param mixed  $service
     *
     * @return $this
     */
    protected abstract function setService($key, $service);
    /**
     * @param string $key
     *
     * @return mixed
     */
    protected abstract function getService($key);
    /**
     * @return WorkflowService
     */
    public function getWorkflowService()
    {
        return $this->getService('workflow');
    }
    /**
     * @param WorkflowService $service
     *
     * @return $this
     */
    public function setWorkflowService(WorkflowService $service)
    {
        return $this->setService('workflow', $service);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Action\Base;

use Velocity\Core\Bag;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;

/**
 * @author <NAME> <<EMAIL>>
 */
abstract class AbstractLetterAction extends AbstractTextNotificationAction
{
    /**
     * @param string $type
     * @param Bag    $params
     * @param Bag    $context
     *
     * @throws \Exception
     */
    protected function sendLetterByType($type, Bag $params, Bag $context)
    {
        // @todo
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Validator\Constraints;

use PHPUnit_Framework_TestCase;
use Velocity\Bundle\ApiBundle\Validator\Constraints\MongoId;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group constraints
 * @group validator
 */
class MongoIdTest extends PHPUnit_Framework_TestCase
{
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $v = new MongoId();

        $this->assertNotNull($v);
        $this->assertEquals('velocity_mongo_id', $v->validatedBy());
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Validator\Constraints;

use PHPUnit_Framework_TestCase;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Velocity\Bundle\ApiBundle\Validator\Constraints\MongoId;
use Velocity\Bundle\ApiBundle\Validator\Constraints\MongoIdValidator;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group constraints
 * @group validator
 */
class MongoIdValidatorTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var ExecutionContextInterface|\PHPUnit_Framework_MockObject_MockObject $context
     */
    protected $context;
    /**
     * @var MongoIdValidator
     */
    protected $v;
    /**
     *
     */
    public function setUp()
    {
        $this->context = $this->getMock('Symfony\\Component\\Validator\\Context\\ExecutionContextInterface', [], [], '', false);
        $this->v       = new MongoIdValidator();

        $this->v->initialize($this->context);
    }
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $this->assertNotNull(new MongoIdValidator());
    }
    /**
     * @group unit
     */
    public function testValidateForNull()
    {
        $this->context->expects($this->once())->method('addViolation')->with('Not a valid Mongo ID');
        $this->v->validate(null, new MongoId());
    }
    /**
     * @group unit
     */
    public function testValidateForMalformed()
    {
        $this->context->expects($this->once())->method('addViolation')->with('Not a valid Mongo ID');

        $this->v->validate('abc12', new MongoId());
    }
    /**
     * @group unit
     */
    public function testValidate()
    {
        $this->context->expects($this->never())->method('addViolation');

        $this->v->validate('aaaaaaaaaaaaaaaaaaaa0000', new MongoId());
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Service;

use PHPUnit_Framework_TestCase;
use Zend\Code\Generator\MethodGenerator;
use Velocity\Bundle\ApiBundle\Service\CallableService;
use Velocity\Bundle\ApiBundle\Service\CodeGeneratorService;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group codeGenerator
 */
class CodeGeneratorServiceTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var CodeGeneratorService
     */
    protected $s;
    /**
     * @var CallableService|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $callableService;
    /**
     *
     */
    public function setUp()
    {
        $this->callableService = $this->getMock("Velocity\\Bundle\\ApiBundle\\Service\\CallableService", [], [], '', false);
        $this->s = new CodeGeneratorService($this->callableService);
    }
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $this->assertNotNull($this->s);
    }
    /**
     * @group unit
     */
    public function testCreateClassForEmptyClassReturnZendClassWithNoMethods()
    {
        $zClass = $this->s->createClass('MyTestClass');

        $this->assertEquals('MyTestClass', $zClass->getName());
        $this->assertEquals(0, count($zClass->getMethods()));
    }
    /**
     * @group unit
     */
    public function testCreateClassForOneMethodWithoutTypeReturnZendClassWithThisBasicMethod()
    {
        $zClass = $this->s->createClass(
            'MyTestClass',
            [
                'methods' => [
                    ['name' => 'method1'],
                ],
            ]
        );

        $this->assertEquals(1, count($zClass->getMethods()));
    }
    /**
     * @group integ
     */
    public function testCreateClassForMethodWithExistingTypeReturnZendClassWithBuiltMethod()
    {
        $this->s->setCallableService(new CallableService());

        $this->s->registerMethodType(
            'basic2',
            function (MethodGenerator $zMethod) {
                $zMethod->setBody("return 'this is generated';");
            }
        );

        $zClass = $this->s->createClass(
            'MyTestClass',
            [
                'methods' => [
                    'method1' => [],
                    'method2' => ['type' => 'basic2'],
                ],
            ]
        );

        $this->assertEquals(2, count($zClass->getMethods()));

        $zMethod = $zClass->getMethod('method2');

        $this->assertEquals("return 'this is generated';", $zMethod->getBody());
    }
    /**
     * @group integ
     */
    public function testCreateClassForMethodWithUnknownTypeThrowException()
    {
        $this->s->setCallableService(new CallableService());

        $this->setExpectedException('RuntimeException', "No 'iAmUnknown' in methodTypes list", 412);

        $this->s->createClass(
            'MyTestClass',
            [
                'methods' => [
                    'method1' => ['type' => 'iAmUnknown'],
                ],
            ]
        );
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Generator;

use Velocity\Core\Traits\ServiceTrait;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;

/**
 * String Generator Action.
 *
 * @author <NAME> <<EMAIL>>
 */
class StringGenerator
{
    use ServiceTrait;
    /**
     * @return string
     *
     * @Velocity\Generator("random_string")
     */
    public function generateString()
    {
        return rand(0, 1000).microtime(true).rand(rand(0, 100), 10000);
    }
    /**
     * @return string
     *
     * @Velocity\Generator("random_sha1")
     */
    public function generateRandomSha1String()
    {
        return $this->generateSha1String($this->generateRandomMd5String());
    }
    /**
     * @param string $string
     *
     * @return string
     *
     * @Velocity\Generator("sha1")
     */
    public function generateSha1String($string)
    {
        return sha1($string);
    }
    /**
     * @return string
     *
     * @Velocity\Generator("random_md5")
     */
    public function generateRandomMd5String()
    {
        return $this->generateMd5String($this->generateString());
    }
    /**
     * @param string $string
     *
     * @return string
     *
     * @Velocity\Generator("md5")
     */
    public function generateMd5String($string)
    {
        return md5($string);
    }
    /**
     * @param mixed $data
     *
     * @return string
     *
     * @Velocity\Generator("serialized")
     */
    public function generateSerializedString($data)
    {
        return serialize($data);
    }
    /**
     * @param mixed $data
     *
     * @return string
     *
     * @Velocity\Generator("fingerprint")
     */
    public function generateFingerPrintString($data)
    {
        if (is_string($data)) {
            return $this->generateMd5String($data);
        }

        return $this->generateMd5String($this->generateSerializedString($data));
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\CodeGenerator;

use Zend\Code\Generator\ValueGenerator;
use Zend\Code\Generator\MethodGenerator;
use Zend\Code\Generator\DocBlockGenerator;
use Zend\Code\Generator\ParameterGenerator;
use Zend\Code\Generator\DocBlock\Tag\ParamTag;
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
use Zend\Code\Generator\DocBlock\Tag\ThrowsTag;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;

/**
 * @author <NAME> <<EMAIL>>
 */
class SdkMethodsCodeGenerator
{
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("sdk.construct")
     */
    public function generateSdkConstructMethod(MethodGenerator $zMethod, array $definition = [])
    {
        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Construct a %s service', $definition['serviceName']),
            null,
            [
                new ParamTag('sdk', ['SdkInterface'], 'Underlying SDK'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('sdk', 'SdkInterface'),
        ]);
        $zMethod->setBody('$this->setSdk($sdk);');
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("sdk.service.test.testConstruct")
     */
    public function generateSdkServiceTestTestConstructMethod(MethodGenerator $zMethod, array $definition = [])
    {
        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Test constructor for service %s', $definition['serviceName']),
            null,
            []
        ));
        $zMethod->setParameters([]);
        $zMethod->setBody(
            '$this->sdk = $this->getMock(\'Phppro\\\\Sdk\\\\Sdk\', [], [], \'\', false);'."\n".sprintf('$this->assertNotNull(new %sService($this->sdk));', ucfirst($definition['serviceName']))
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.get")
     */
    public function generateCrudGetMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition['route'] = str_replace('{id}', '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Return the specified %s', $definition['type']),
            null,
            [
                new ParamTag('id', ['mixed'], sprintf('ID of the %s', $definition['type'])),
                new ParamTag('fields', ['array'], 'List of fields to retrieve'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('id', 'string'),
            new ParameterGenerator('fields', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->get(sprintf(\'%s\', $id), $fields, $options);', $definition['route'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.getBy")
     */
    public function generateCrudGetByMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition += ['field' => 'id'];
        $definition['route'] = str_replace('{'.$definition['field'].'}', '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Return the specified %s by %s', $definition['type'], $definition['field']),
            null,
            [
                new ParamTag($definition['field'], ['mixed'], sprintf(ucfirst($definition['field']).' of the %s', $definition['type'])),
                new ParamTag('fields', ['array'], 'List of fields to retrieve'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator($definition['field'], 'string'),
            new ParameterGenerator('fields', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->get(sprintf(\'%s\', $%s), $fields, $options);', $definition['route'], $definition['field'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.getPropertyBy")
     */
    public function generateCrudGetPropertyByMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition += ['field' => 'id', 'property' => 'id'];
        $definition['route'] = str_replace(['{'.$definition['field'].'}', '{'.$definition['property'].'}'], '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Return the specified %s %s by %s', $definition['type'], $definition['property'], $definition['field']),
            null,
            [
                new ParamTag($definition['field'], ['mixed'], sprintf(ucfirst($definition['field']).' of the %s', $definition['type'])),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['mixed']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator($definition['field'], 'string'),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->get(sprintf(\'%s\', $%s), [], $options);', $definition['route'], $definition['field'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.getPropertyPathBy")
     */
    public function generateCrudGetPropertyPathByMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition += ['field' => 'id', 'property' => 'id'];
        $definition['route'] = str_replace(['{'.$definition['field'].'}', '{'.$definition['property'].'}'], '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Return the local path of the file containing the specified %s %s by %s', $definition['type'], $definition['property'], $definition['field']),
            null,
            [
                new ParamTag($definition['field'], ['mixed'], sprintf(ucfirst($definition['field']).' of the %s', $definition['type'])),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['mixed']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator($definition['field'], 'string'),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->getPath(sprintf(\'%s\', $%s), [], [\'format\' => \'%s\'] + $options);', $definition['route'], $definition['field'], $definition['format'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.create")
     */
    public function generateCrudCreateMethod(MethodGenerator $zMethod, $definition = [])
    {
        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Create a new %s', $definition['type']),
            null,
            [
                new ParamTag('data', ['array'], 'Data to store'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('data', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->create(\'%s\', $data, $options);', $definition['route'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.sub.createBy")
     */
    public function generateCrudSubCreateByMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition += ['field' => 'id', 'subType' => 'subType'];
        $definition['route'] = str_replace('{'.$definition['field'].'}', '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Create a new %s %s by %s', $definition['type'], $definition['subType'], $definition['field']),
            null,
            [
                new ParamTag($definition['field'], ['mixed'], sprintf('%s of the %s', ucfirst($definition['field']), $definition['type'])),
                new ParamTag('data', ['array'], 'Data to store'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator($definition['field'], 'mixed'),
            new ParameterGenerator('data', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->create(sprintf(\'%s\', $%s), $data, $options);', $definition['route'], $definition['field'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.sub.getBy")
     */
    public function generateCrudSubGetByMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition += ['field' => 'id', 'subType' => 'subType'];
        $definition['route'] = str_replace(['{'.$definition['field'].'}', '{id}'], '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Return the specified %s %s by %s %s', $definition['type'], $definition['subType'], $definition['type'], $definition['field']),
            null,
            [
                new ParamTag($definition['field'], ['mixed'], sprintf('%s of the %s', ucfirst($definition['field']), $definition['type'])),
                new ParamTag('id', ['string'], 'The id'),
                new ParamTag('fields', ['array'], 'Data to store'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator($definition['field'], 'mixed'),
            new ParameterGenerator('id', 'string'),
            new ParameterGenerator('fields', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->get(sprintf(\'%s\', $%s, $id), $fields, $options);', $definition['route'], $definition['field'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.delete")
     */
    public function generateCrudDeleteMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition['route'] = str_replace('{id}', '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Delete the specified %s', $definition['type']),
            null,
            [
                new ParamTag('id', ['mixed'], sprintf('ID of the %s', $definition['type'])),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('id', 'string'),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->delete(sprintf(\'%s\', $id), $options);', $definition['route'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.purge")
     */
    public function generateCrudPurgeMethod(MethodGenerator $zMethod, $definition = [])
    {
        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Purge all %ss', $definition['type']),
            null,
            [
                new ParamTag('criteria', ['array'], 'Optional criteria to filter deleteds'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('criteria', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->purge(\'%s\', $criteria, $options);', $definition['route'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.update")
     */
    public function generateCrudUpdateMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition['route'] = str_replace('{id}', '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Update the specified %s', $definition['type']),
            null,
            [
                new ParamTag('id', ['mixed'], sprintf('ID of the %s', $definition['type'])),
                new ParamTag('data', ['array'], 'Data to update'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('id', 'string'),
            new ParameterGenerator('data', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->update(sprintf(\'%s\', $id), $data, $options);', $definition['route'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.sub.setPropertyBy")
     */
    public function generateCrudSubSetPropertyByMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition['route'] = str_replace(['{id}', '{'.$definition['key'].'}'], ['%s', '%s'], $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Set the %s of the %s %s to %s', $definition['property'], $definition['type'], $definition['subType'], $definition['value']),
            null,
            [
                new ParamTag($definition['key'], ['string'], sprintf($definition['key'].' of the %s', $definition['type'])),
                new ParamTag('id', ['mixed'], sprintf('ID of the %s %s', $definition['type'], $definition['subType'])),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator($definition['key'], 'string'),
            new ParameterGenerator('id', 'string'),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->update(sprintf(\'%s\', $%s, $id), [\'%s\' => %s], $options);', $definition['route'], $definition['key'], $definition['property'], var_export($definition['value'], true))
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.updateProperty")
     */
    public function generateCrudUpdatePropertyMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition['route'] = str_replace('{id}', '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Update %s of the specified %s', $definition['property'], $definition['type']),
            null,
            [
                new ParamTag('id', ['mixed'], sprintf('ID of the %s', $definition['type'])),
                new ParamTag('data', ['mixed'], sprintf('Value for the %s', $definition['property'])),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('id', 'string'),
            new ParameterGenerator('data', 'mixed'),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->update(sprintf(\'%s\', $id), $data, $options);', $definition['route'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.find")
     */
    public function generateCrudFindMethod(MethodGenerator $zMethod, $definition = [])
    {
        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Find %ss', $definition['type']),
            null,
            [
                new ParamTag('criteria', ['array'], sprintf('Optional criteria to filter %ss', $definition['type'])),
                new ParamTag('fields', ['array'], 'Optional fields to retrieve'),
                new ParamTag('limit', ['int'], 'Optional limit'),
                new ParamTag('offset', ['int'], 'Optional offset'),
                new ParamTag('sorts', ['array'], 'Optional sorts'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('criteria', 'array', []),
            new ParameterGenerator('fields', 'array', []),
            new ParameterGenerator('limit', 'int', new ValueGenerator(null)),
            new ParameterGenerator('offset', 'int', 0),
            new ParameterGenerator('sorts', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->find(\'%s\', $criteria, $fields, $limit, $offset, $sorts, $options);', $definition['route'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.sub.findBy")
     */
    public function generateCrudSubFindByMethod(MethodGenerator $zMethod, $definition = [])
    {
        $definition['route'] = str_replace('{'.$definition['field'].'}', '%s', $definition['route']);

        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Find %s %ss by %s', $definition['type'], $definition['subType'], $definition['field']),
            null,
            [
                new ParamTag($definition['field'], ['mixed'], sprintf(ucfirst($definition['field']).' of the %s', $definition['type'])),
                new ParamTag('criteria', ['array'], sprintf('Optional criteria to filter %ss', $definition['type'])),
                new ParamTag('fields', ['array'], 'Optional fields to retrieve'),
                new ParamTag('limit', ['int'], 'Optional limit'),
                new ParamTag('offset', ['int'], 'Optional offset'),
                new ParamTag('sorts', ['array'], 'Optional sorts'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator($definition['field'], 'mixed'),
            new ParameterGenerator('criteria', 'array', []),
            new ParameterGenerator('fields', 'array', []),
            new ParameterGenerator('limit', 'int', new ValueGenerator(null)),
            new ParameterGenerator('offset', 'int', 0),
            new ParameterGenerator('sorts', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->find(sprintf(\'%s\', $%s), $criteria, $fields, $limit, $offset, $sorts, $options);', $definition['route'], $definition['field'])
        );
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("crud.findPage")
     */
    public function generateCrudFindPageMethod(MethodGenerator $zMethod, $definition = [])
    {
        $zMethod->setDocBlock(new DocBlockGenerator(
            sprintf('Find a page of %ss', $definition['type']),
            null,
            [
                new ParamTag('criteria', ['array'], sprintf('Optional criteria to filter %ss', $definition['type'])),
                new ParamTag('fields', ['array'], 'Optional fields to retrieve'),
                new ParamTag('page', ['int'], 'Rank of the page to retrieve'),
                new ParamTag('size', ['int'], 'Size of pages'),
                new ParamTag('sorts', ['array'], 'Optional sorts'),
                new ParamTag('options', ['array'], 'Options'),
                new ReturnTag(['array']),
                new ThrowsTag(['\\Exception'], 'if an error occured'),
            ]
        ));
        $zMethod->setParameters([
            new ParameterGenerator('criteria', 'array', []),
            new ParameterGenerator('fields', 'array', []),
            new ParameterGenerator('page', 'int', 0),
            new ParameterGenerator('size', 'int', 10),
            new ParameterGenerator('sorts', 'array', []),
            new ParameterGenerator('options', 'array', []),
        ]);
        $zMethod->setBody(
            sprintf('return $this->getSdk()->findPage(\'%s\', $criteria, $fields, $page, $size, $sorts, $options);', $definition['route'])
        );
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Traits\ServiceAware;

use Velocity\Bundle\ApiBundle\Service\StripeService;

/**
 * StripeServiceAware trait.
 *
 * @author <NAME> <<EMAIL>>
 */
trait StripeServiceAwareTrait
{
    /**
     * @param string $key
     * @param mixed  $service
     *
     * @return $this
     */
    protected abstract function setService($key, $service);
    /**
     * @param string $key
     *
     * @return mixed
     */
    protected abstract function getService($key);
    /**
     * @return StripeService
     */
    public function getStripeService()
    {
        return $this->getService('stripe');
    }
    /**
     * @param StripeService $service
     *
     * @return $this
     */
    public function setStripeService(StripeService $service)
    {
        return $this->setService('stripe', $service);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Service;

use Symfony\Component\HttpFoundation\Request;
use Velocity\Core\Traits\ServiceTrait;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\PaymentProviderInterface;

/**
 * PaymentProvider Service.
 *
 * @author <NAME> <<EMAIL>>
 */
class PaymentProviderService
{
    use ServiceTrait;
    use ServiceAware\ExpressionServiceAwareTrait;
    /**
     * List of payment providers.
     *
     * @var PaymentProviderInterface[]
     */
    protected $providers;
    /**
     * @var array
     */
    protected $rules;
    /**
     * Construct a new service.
     *
     * @param ExpressionService          $expressionService
     * @param PaymentProviderInterface[] $providers
     * @param array                      $rules
     */
    public function __construct(ExpressionService $expressionService, $providers = [], $rules = [])
    {
        $this->setExpressionService($expressionService);

        foreach ($providers as $name => $provider) {
            $this->addProvider($provider, $name);
        }

        foreach ($rules as $condition => $providerName) {
            $this->addRule($condition, $providerName);
        }
    }
    /**
     * Return the list of registered providers.
     *
     * @return PaymentProviderInterface[]
     */
    public function getProviders()
    {
        return $this->providers;
    }
    /**
     * Add a new provider for the specified name (replace if exist).
     *
     * @param PaymentProviderInterface $provider
     * @param string                   $name
     *
     * @return $this
     */
    public function addProvider(PaymentProviderInterface $provider, $name)
    {
        $this->providers[$name] = $provider;

        return $this;
    }
    /**
     * Add a new rule
     *
     * @param string $condition
     * @param string $providerName
     *
     * @return $this
     *
     * @throws \Exception
     */
    public function addRule($condition, $providerName)
    {
        $this->rules[] = ['condition' => $condition, 'provider' => $providerName];

        return $this;
    }
    /**
     * Return the provider registered for the specified name.
     *
     * @param string $name
     *
     * @return PaymentProviderInterface
     *
     * @throws \Exception if no provider registered for this name
     */
    public function getProviderByName($name)
    {
        if (!isset($this->providers[$name])) {
            throw $this->createRequiredException(
                "No payment provider registered for name '%s'",
                $name
            );
        }

        return $this->providers[$name];
    }
    /**
     * @param array $data
     *
     * @return string
     *
     * @throws \Exception
     */
    public function selectProvider($data = [])
    {
        foreach ($this->rules as $rule) {
            if ($this->isValidCondition($rule['condition'], $data)) {
                return $rule['provider'];
            }
        }

        throw $this->createRequiredException("No payment provider available");
    }
    /**
     * @param string $condition
     * @param array  $data
     *
     * @return bool
     */
    public function isValidCondition($condition, $data = [])
    {
        return (bool) $this->getExpressionService()->evaluate($condition, $data);
    }
    /**
     * @param string  $provider
     * @param string  $callback
     * @param Request $request
     *
     * @return array
     *
     * @throws \Exception
     */
    public function parseCallbackRequest($provider, $callback, Request $request)
    {
        return $this->getProviderByName($provider)->parseCallbackRequest($callback, $request);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Traits\ServiceAware;

use Velocity\Bundle\ApiBundle\Service\PaymentProviderService;

/**
 * PaymentProviderServiceAware trait.
 *
 * @author <NAME> <<EMAIL>>
 */
trait PaymentProviderServiceAwareTrait
{
    /**
     * @param string $key
     * @param mixed  $service
     *
     * @return $this
     */
    protected abstract function setService($key, $service);
    /**
     * @param string $key
     *
     * @return mixed
     */
    protected abstract function getService($key);
    /**
     * @return PaymentProviderService
     */
    public function getPaymentProviderService()
    {
        return $this->getService('paymentProvider');
    }
    /**
     * @param PaymentProviderService $service
     *
     * @return $this
     */
    public function setPaymentProviderService(PaymentProviderService $service)
    {
        return $this->setService('paymentProvider', $service);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Twig;

use PHPUnit_Framework_TestCase;
use Velocity\Bundle\ApiBundle\Twig\ApiExtension;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group twig
 */
class ApiExtensionTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var ApiExtension
     */
    protected $e;
    /**
     *
     */
    public function setUp()
    {
        $this->e = new ApiExtension();
    }
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $e = new ApiExtension(['a' => 'b', 'c' => 'd']);

        $this->assertNotNull($e);

        $this->assertEquals(['velocity' => ['a' => 'b', 'c' => 'd']], $e->getGlobals());
    }
    /**
     * @group unit
     */
    public function testGetFilters()
    {
        $this->assertNotEquals(0, count($this->e->getFilters()));
    }
    /**
     * @group unit
     */
    public function testGetBase64EncodedString()
    {
        $this->assertEquals(base64_encode('test'), $this->e->getBase64EncodedString('test'));
    }
    /**
     * @group unit
     */
    public function testGetName()
    {
        $this->assertEquals('velocity_api', $this->e->getName());
    }
}
<file_sep>#!/bin/bash

while [ 1 ]; do
	clear
	bin/phpunit -c phpunit-basic.xml.dist $*
	sleep 3
done;
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Action;

use Velocity\Core\Bag;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;
use Velocity\Bundle\ApiBundle\Action\Base\AbstractMailAction;

/**
 * @author <NAME> <<EMAIL>>
 */
class MailAction extends AbstractMailAction
{
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("mail", description="send a mail")
     */
    public function sendMail(Bag $params, Bag $context)
    {
        $this->sendMailByType(null, $params, $context);
    }
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("mail_user", description="send a user mail")
     */
    public function sendUserMail(Bag $params, Bag $context)
    {
        $params->setDefault('sender', $this->getDefaultSenderByTypeAndNature('mail_user', $params->get('template')));
        $params->setDefault('_locale', $this->getCurrentLocale());
        $params->setDefault('_tenant', $this->getTenant());
        $this->sendMailByType('user', $params, $context);
    }
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("mail_admin", description="send an admin mail")
     */
    public function sendAdminMail(Bag $params, Bag $context)
    {
        $params->setDefault('recipients', $this->getDefaultRecipientsByTypeAndNature('mail_admins', $params->get('template')));
        $params->setDefault('sender', $this->getDefaultSenderByTypeAndNature('mail_admin', $params->get('template')));
        $params->setDefault('_tenant', $this->getTenant());
        $this->sendMailByType('admin', $params, $context);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Service;

use PHPUnit_Framework_TestCase;
use PHPUnit_Framework_MockObject_MockObject;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Templating\EngineInterface;
use Velocity\Bundle\ApiBundle\Service\ExpressionService;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group expression
 */
class ExpressionServiceTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var ExpressionService
     */
    protected $s;
    /**
     * @var EngineInterface|PHPUnit_Framework_MockObject_MockObject
     */
    protected $templatingService;
    /**
     * @var ExpressionLanguage|PHPUnit_Framework_MockObject_MockObject
     */
    protected $expressionLanguage;
    /**
     *
     */
    public function setUp()
    {
        $this->templatingService  = $this->getMock("Symfony\\Component\\Templating\\EngineInterface", [], [], '', false);
        $this->expressionLanguage = $this->getMock("Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage", [], [], '', false);
        $this->s = new ExpressionService($this->templatingService, $this->expressionLanguage);
    }
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $this->assertNotNull($this->s);
    }
    /**
     * @group integ
     */
    public function testEvaluateExpressionLanguage()
    {
        $this->s->setExpressionLanguage(new ExpressionLanguage());

        $vars = ['a' => [1, 2], 'b' => 5, 'c' => 2];

        $this->assertEquals([1, 2], $this->s->evaluate('$a', $vars));
        $this->assertEquals(5, $this->s->evaluate('$b', $vars));
        $this->assertEquals(2, $this->s->evaluate('$c', $vars));

        $this->assertEquals(2.5, $this->s->evaluate('$ (b + c - 2) / 2', $vars));
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\CodeGenerator;

use Zend\Code\Generator\MethodGenerator;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;

/**
 * @author <NAME> <<EMAIL>>
 */
class BasicMethodsCodeGenerator
{
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("basic")
     */
    public function generateBasicMethod(MethodGenerator $zMethod, array $definition = [])
    {
        unset($zMethod, $definition);
    }
    /**
     * @param MethodGenerator $zMethod
     * @param array           $definition
     *
     * @Velocity\CodeGeneratorMethodType("get")
     */
    public function generateGetMethod(MethodGenerator $zMethod, $definition = [])
    {
        unset($definition);

        $zMethod->setParameters([]);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Action;

use Velocity\Core\Bag;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;
use Velocity\Bundle\ApiBundle\Action\Base\AbstractLetterAction;

/**
 * @author <NAME> <<EMAIL>>
 */
class LetterAction extends AbstractLetterAction
{
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("letter", description="send a letter")
     */
    public function sendLetter(Bag $params, Bag $context)
    {
        $this->sendLetterByType(null, $params, $context);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\PaymentProvider;

use Symfony\Component\HttpFoundation\Request;
use Velocity\Core\Traits\ServiceTrait;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\Service\PayPalService;
use Velocity\Bundle\ApiBundle\PaymentProviderInterface;

/**
 * PayPal Payment Provider.
 *
 * @author <NAME> <<EMAIL>>
 */
class PayPalPaymentProvider implements PaymentProviderInterface
{
    use ServiceTrait;
    use ServiceAware\PayPalServiceAwareTrait;
    /**
     * PayPalPaymentProvider constructor.
     *
     * @param PayPalService $payPalService
     */
    public function __construct(PayPalService $payPalService)
    {
        $this->setPayPalService($payPalService);
    }
    /**
     * @param array $data
     * @param array $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function create($data = [], $options = [])
    {
        $order = [
            'successUrl'  => $data['succeedUrl'],
            'cancelUrl'   => $data['canceledUrl'],
            'currency'    => $data['currency'],
            'name'        => $data['name'],
            'amount'      => $data['amount'],
            'description' => $data['description'],
            'items'       => [
                [
                    'name'        => $data['name'],
                    'description' => $data['description'],
                    'amount'      => $data['amount'],
                    'currency'    => $data['currency'],
                ],
            ],
        ];

        $payPalOrder = $this->getPayPalService()->createOrder($order, $options);

        return [
            'url'         => $payPalOrder['paymentUrl'],
            'transaction' => $payPalOrder['token'],
            'correlation' => $payPalOrder['correlationId'],
        ];
    }
    /**
     * @param string|array $id
     * @param array        $data
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function confirm($id, $data = [], $options = [])
    {
        if (!isset($data['payerId'])) {
            throw $this->createRequiredException('Payer id is missing');
        }

        return $this->getPayPalService()->confirmOrder($id, ['payerId' => $data['payerId']], $options);
    }
    /**
     * @param string|array $id
     * @param array        $data
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function fail($id, $data = [], $options = [])
    {
        if (!isset($data['payerId'])) {
            throw $this->createRequiredException('Payer id is missing');
        }

        return $this->getPayPalService()->failOrder($id, ['payerId' => $data['payerId']], $options);
    }
    /**
     * @param string|array $id
     * @param array        $data
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function cancel($id, $data = [], $options = [])
    {
        if (!isset($data['payerId'])) {
            throw $this->createRequiredException('Payer id is missing');
        }

        return $this->getPayPalService()->cancelOrder($id, ['payerId' => $data['payerId']], $options);
    }
    /**
     * @param string|array $id
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function get($id, $options = [])
    {
        return $this->getPayPalService()->getOrder($id, $options);
    }
    /**
     * @param string  $callback
     * @param Request $request
     *
     * @return array
     *
     * @throws \Exception
     */
    public function parseCallbackRequest($callback, Request $request)
    {
        switch ($callback) {
            case 'paid':
                if (!$request->query->has('token')) {
                    throw $this->createMalformedException('Malformed callback parameters (#1)');
                }
                if (!$request->query->has('PayerID')) {
                    throw $this->createMalformedException('Malformed callback parameters (#2)');
                }

                return ['transaction' => $request->query->get('token'), 'payer' => $request->query->get('PayerID')];
            default:
                return [];
        }
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Listener;

use Velocity\Core\Traits\ServiceTrait;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

/**
 * Request Listener.
 *
 * @author <NAME> <<EMAIL>>
 */
class RequestListener
{
    use ServiceTrait;
    /**
     * @param array  $allowedLocales
     * @param string $defaultLocale
     */
    public function __construct(array $allowedLocales, $defaultLocale)
    {
        $this->allowLocales($allowedLocales);
        $this->setDefaultLocale($defaultLocale);
    }
    /**
     * @param string $defaultLocale
     *
     * @return $this
     */
    public function setDefaultLocale($defaultLocale)
    {
        return $this->setParameter('defaultLocale', $defaultLocale);
    }
    /**
     * @return string
     *
     * @throws \Exception
     */
    public function getDefaultLocale()
    {
        return $this->getParameter('defaultLocale');
    }
    /**
     * @param array $locales
     *
     * @return $this
     */
    public function allowLocales(array $locales)
    {
        foreach ($locales as $locale) {
            $this->pushArrayParameterKeyItem('allowedLocales', strtolower($locale), true);
        }

        return $this;
    }
    /**
     * @param string $locale
     *
     * @return bool
     */
    public function isAllowedLocale($locale)
    {
        return $this->hasArrayParameterKey('allowedLocales', strtolower($locale));
    }
    /**
     * Kernel request event callback.
     *
     * @param GetResponseEvent $event
     *
     * @return void
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        if ($event->isMasterRequest()) {
            $request->setLocale($this->getDefaultLocale());

            foreach ($request->getLanguages() as $locale) {
                if ($this->isAllowedLocale($locale)) {
                    $request->setLocale($locale);
                    break;
                }
            }
        }

        if ('json' !== $request->getContentType()) {
            return;
        }

        $data = json_decode($request->getContent(), true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            $event->setResponse(new JsonResponse(
                [
                    'code'    => 412,
                    'status'  => 'exception',
                    'message' => 'Malformed data (json syntax)',
                ],
                412
            ));
        } elseif (is_array($data)) {
            $request->request->replace($data);
        }
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Action;

use Velocity\Core\Bag;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;
use Velocity\Bundle\ApiBundle\Action\Base\AbstractSmsAction;

/**
 * @author <NAME> <<EMAIL>>
 */
class SmsAction extends AbstractSmsAction
{
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("sms", description="send a sms")
     */
    public function sendSms(Bag $params, Bag $context)
    {
        $this->sendSmsByType(null, $params, $context);
    }
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("sms_user", description="send a user sms")
     */
    public function sendUserSms(Bag $params, Bag $context)
    {
        $params->setDefault('sender', $this->getDefaultSenderByTypeAndNature('sms_user', $params->get('template')));
        $params->setDefault('_locale', $this->getCurrentLocale());
        $params->setDefault('_tenant', $this->getTenant());
        $this->sendSmsByType('user', $params, $context);
    }
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("sms_admin", description="send an admin sms")
     */
    public function sendAdminSms(Bag $params, Bag $context)
    {
        $params->setDefault('recipients', $this->getDefaultRecipientsByTypeAndNature('sms_admins', $params->get('template')));
        $params->setDefault('sender', $this->getDefaultSenderByTypeAndNature('sms_admin', $params->get('template')));
        $params->setDefault('_tenant', $this->getTenant());
        $this->sendSmsByType('admin', $params, $context);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\HttpKernel\KernelInterface;
use Velocity\Bundle\ApiBundle\Service\VelocityService;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Velocity\Bundle\ApiBundle\DependencyInjection\Security\Factory\ApiFactory;
use Velocity\Bundle\ApiBundle\DependencyInjection\Compiler\VelocityCompilerPass;

/**
 * Velocity Api Bundle.
 *
 * @author <NAME> <<EMAIL>>
 */
class VelocityApiBundle extends Bundle
{
    /**
     * @var KernelInterface
     */
    protected $kernel;
    /**
     * @var ApiFactory
     */
    protected $apiFactory;
    /**
     * @var VelocityService
     */
    protected $velocityService;
    /**
     * @param KernelInterface $kernel
     * @param ApiFactory      $apiFactory
     * @param VelocityService $velocityService
     */
    public function __construct(KernelInterface $kernel, $apiFactory = null, $velocityService = null)
    {
        if (null === $apiFactory) {
            $apiFactory = new ApiFactory();
        }

        if (null === $velocityService) {
            $velocityService = new VelocityService(new AnnotationReader());
        }

        $this->kernel          = $kernel;
        $this->apiFactory      = $apiFactory;
        $this->velocityService = $velocityService;
    }
    /**
     * @return ApiFactory
     */
    public function getApiFactory()
    {
        return $this->apiFactory;
    }
    /**
     * @return KernelInterface
     */
    public function getKernel()
    {
        return $this->kernel;
    }
    /**
     * @return VelocityService
     */
    public function getVelocityService()
    {
        return $this->velocityService;
    }
    /**
     * @param ContainerBuilder $container
     */
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        if ($container->hasExtension('security')) {
            $extension = $container->getExtension('security');
            /** @var SecurityExtension $extension */
            $extension->addSecurityListenerFactory($this->getApiFactory());
        }

        $container->addCompilerPass(new VelocityCompilerPass($this->getKernel(), $this->getVelocityService()));
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Action;

use Velocity\Core\Bag;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\Annotation as Velocity;
use Velocity\Bundle\ApiBundle\Action\Base\AbstractFaxAction;

/**
 * @author <NAME> <<EMAIL>>
 */
class FaxAction extends AbstractFaxAction
{
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("fax", description="send a fax")
     */
    public function sendFax(Bag $params, Bag $context)
    {
        $this->sendFaxByType(null, $params, $context);
    }
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("fax_user", description="send a user fax")
     */
    public function sendUserFax(Bag $params, Bag $context)
    {
        $params->setDefault('sender', $this->getDefaultSenderByTypeAndNature('fax_user', $params->get('template')));
        $params->setDefault('_locale', $this->getCurrentLocale());
        $params->setDefault('_tenant', $this->getTenant());
        $this->sendFaxByType('user', $params, $context);
    }
    /**
     * @param Bag $params
     * @param Bag $context
     *
     * @Velocity\Action("fax_admin", description="send an admin fax")
     */
    public function sendAdminFax(Bag $params, Bag $context)
    {
        $params->setDefault('recipients', $this->getDefaultRecipientsByTypeAndNature('fax_admins', $params->get('template')));
        $params->setDefault('sender', $this->getDefaultSenderByTypeAndNature('fax_admin', $params->get('template')));
        $params->setDefault('_tenant', $this->getTenant());
        $this->sendFaxByType('admin', $params, $context);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Mock;

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Mock for easy velocity definition creation.
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 */
class ContainerBuilderMock extends ContainerBuilder
{
    /**
     * @param string $id
     *
     * @return Definition
     *
     * @see \Symfony\Component\DependencyInjection\ContainerBuilder::getDefinition()
     */
    public function getDefinition($id)
    {
        $id = strtolower($id);
        if (!$this->hasDefinition($id) && 'velocity' === strstr($id, '.', true)) {
            $this->setDefinition($id, new Definition());
        }

        parent::getDefinition($id);
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Service;

use PHPUnit_Framework_TestCase;
use Velocity\Bundle\ApiBundle\Service\BusinessRuleService;
use Velocity\Bundle\ApiBundle\WorkflowInterface;
use Velocity\Bundle\ApiBundle\Service\WorkflowService;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group workflow
 */
class WorkflowServiceTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var WorkflowService
     */
    protected $s;
    /**
     * @var BusinessRuleService|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $businessRule;
    /**
     *
     */
    public function setUp()
    {
        $this->businessRule = $this->getMock('Velocity\\Bundle\\ApiBundle\\Service\\BusinessRuleService', [], [], '', false);
        $this->s            = new WorkflowService($this->businessRule);
    }
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $this->assertNotNull($this->s);
    }
    /**
     * @group unit
     */
    public function testRegisterFromDefinition()
    {
        $this->assertFalse($this->s->has('workflow1'));
        $this->s->registerFromDefinition('workflow1', []);
        $this->assertTrue($this->s->has('workflow1'));

        $workflow = $this->s->get('workflow1');

        $this->assertTrue($workflow instanceof WorkflowInterface);
    }
    /**
     * @group unit
     */
    public function testHasTransition()
    {
        $this->s->registerFromDefinition('w', ['steps' => ['s1', 's2', 's3'], 'transitions' => ['s1' => ['s2'], 's2' => ['s3', 's1']]]);

        $this->assertTrue($this->s->hasTransition('w', 's1', 's2'));
        $this->assertFalse($this->s->hasTransition('w', 's1', 's3'));
        $this->assertFalse($this->s->hasTransition('w', 's1', 'sX'));
        $this->assertTrue($this->s->hasTransition('w', 's2', 's3'));
        $this->assertTrue($this->s->hasTransition('w', 's2', 's1'));
        $this->assertFalse($this->s->hasTransition('w', 's3', 's1'));
    }
    /**
     * @group unit
     */
    public function testTransition()
    {
        $this->businessRule->expects($this->exactly(2))->method('executeBusinessRulesForModelOperation');

        $this->s->registerFromDefinition('w', ['steps' => ['s1', 's2', 's3'], 'transitions' => ['s1' => ['s2'], 's2' => ['s3', 's1']]]);

        $docBefore = new \stdClass();
        $docBefore->status = 's1';

        $doc = new \stdClass();
        $doc->status = 's2';

        $this->s->transitionModelProperty('m', $doc, 'status', $docBefore, 'w');
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Service;

use PHPUnit_Framework_TestCase;
use Velocity\Bundle\ApiBundle\GenericDocument;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group document
 */
class GenericDocumentTest extends PHPUnit_Framework_TestCase
{
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $d = new GenericDocument('<a></a>', 'xml');

        $this->assertNotNull($d);
        $this->assertEquals('<a></a>', $d->getContent());
        $this->assertEquals('application/xml', $d->getContentType());
    }
    /**
     * @group unit
     */
    public function testConstructForUnknownFormatLoadDefaultFormat()
    {
        $d = new GenericDocument('xyz', 'xyz');

        $this->assertEquals('application/octet-stream', $d->getContentType());
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests;

use PHPUnit_Framework_TestCase;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Velocity\Bundle\ApiBundle\VelocityApiBundle;
use Symfony\Component\HttpKernel\KernelInterface;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group velocityapibundle
 */
class VelocityApiBundleTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var VelocityApiBundle
     */
    protected $b;
    /**
     * @var KernelInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $kernel;
    /**
     *
     */
    public function setUp()
    {
        $this->kernel = $this->getMock('Symfony\\Component\\HttpKernel\\KernelInterface', [], [], '', false);
        $this->b      = new VelocityApiBundle($this->kernel);
    }
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $b = new VelocityApiBundle($this->kernel);

        $this->assertNotNull($b);
    }
    /**
     * @group integ
     */
    public function testBuild()
    {
        $containerBuilder = new ContainerBuilder();

        /** @var SecurityExtension|\PHPUnit_Framework_MockObject_MockObject $extension */
        $extension = $this->getMock('Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\SecurityExtension', ['addSecurityListenerFactory', 'getAlias'], [], '', false);

        $extension->expects($this->once())->method('getAlias')->will($this->returnValue('security'));
        $extension->expects($this->once())->method('addSecurityListenerFactory')->with($this->b->getApiFactory());

        $containerBuilder->registerExtension($extension);
        $previousPassCount = count($containerBuilder->getCompilerPassConfig()->getPasses());

        $this->b->build($containerBuilder);

        $this->assertEquals($previousPassCount + 1, count($containerBuilder->getCompilerPassConfig()->getPasses()));
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Generator;

use PHPUnit_Framework_TestCase;
use Velocity\Bundle\ApiBundle\Generator\StringGenerator;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group generator
 */
class StringGeneratorTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var StringGenerator
     */
    protected $g;
    /**
     *
     */
    public function setUp()
    {
        $this->g = new StringGenerator();
    }
    /**
     * @group unit
     */
    public function testGenerateMd5()
    {
        $this->assertEquals(32, strlen($this->g->generateRandomMd5String()));
        $this->assertTrue(0 < preg_match('/^[a-f0-9]+$/', $g = $this->g->generateRandomMd5String()), sprintf("string '%s' is not valid md5 string", $g));
    }
    /**
     * @group unit
     */
    public function testGenerateSha1()
    {
        $this->assertEquals(40, strlen($this->g->generateRandomSha1String()));
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Service;

use PHPUnit_Framework_TestCase;
use Velocity\Bundle\ApiBundle\Document;

/**
 * @author <NAME> <<EMAIL>>
 *
 * @group document
 */
class DocumentTest extends PHPUnit_Framework_TestCase
{
    /**
     * @group unit
     */
    public function testConstruct()
    {
        $d = new Document('<a></a>', 'application/xml', 'x.xml');

        $this->assertNotNull($d);
        $this->assertEquals('<a></a>', $d->getContent());
        $this->assertEquals('application/xml', $d->getContentType());
        $this->assertEquals('x.xml', $d->getFileName());
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Service;

use MongoId;
use Exception;
use MongoClient;
use MongoCollection;
use Velocity\Core\Traits\ServiceTrait;
use Velocity\Bundle\ApiBundle\Event\DatabaseQueryEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Database Service.
 *
 * @author <NAME> <<EMAIL>>
 */
class DatabaseService
{
    use ServiceTrait;
    /**
     * @var \DateTime[]
     */
    protected $timers;
    /**
     * Constructs a new service
     *
     * @param MongoClient              $mongoClient
     * @param EventDispatcherInterface $eventDispatcher
     * @param string                   $databaseName
     * @param bool                     $randomDatabaseName
     *
     * @throws \Exception
     */
    public function __construct(MongoClient $mongoClient, EventDispatcherInterface $eventDispatcher, $databaseName, $randomDatabaseName = false)
    {
        $this->timers = [];

        $this->setEventDispatcher($eventDispatcher);
        $this->setMongoClient($mongoClient);

        if (true === $randomDatabaseName) {
            $databaseName .= '_'.((int) microtime(true)).'_'.substr(md5(rand(0, 10000)), -8);
        }

        if (64 <= strlen($databaseName)) {
            throw $this->createMalformedException(
                "Database name is too long, maximum is 64 characters (found: %d)",
                strlen($databaseName)
            );
        }

        $this->setDatabaseName($databaseName);
    }
    /**
     * Return the underlying database name.
     *
     * @return string
     */
    public function getDatabaseName()
    {
        return $this->getParameter('databaseName');
    }
    /**
     * Set the underlying database name.
     *
     * @param string $databaseName
     *
     * @return $this
     */
    public function setDatabaseName($databaseName)
    {
        return $this->setParameter('databaseName', $databaseName);
    }
    /**
     * Return the underlying Mongo Client (connection).
     *
     * @return MongoClient
     */
    public function getMongoClient()
    {
        return $this->getService('mongoClient');
    }
    /**
     * Set the underlying Mongo Client (connection).
     *
     * @param MongoClient $service
     *
     * @return $this
     */
    public function setMongoClient(MongoClient $service)
    {
        return $this->setService('mongoClient', $service);
    }
    /**
     * Drop the current (or the specified) database.
     *
     * @param string $name
     *
     * @return $this
     */
    public function drop($name = null)
    {
        $this->getMongoClient()
            ->selectDB(null === $name ? $this->getDatabaseName() : $name)
            ->drop()
        ;

        return $this;
    }
    /**
     * Returns the specified Mongo Collection.
     *
     * @param string $name
     * @param array  $options
     *
     * @return MongoCollection
     */
    public function getCollection($name, $options = [])
    {
        return $this->getMongoClient()->selectCollection(
            isset($options['db']) ? $options['db'] : $this->getDatabaseName(),
            $name
        );
    }
    /**
     * Update (the first matching criteria) a document of the specified collection.
     *
     * @param string $collection
     * @param array  $criteria
     * @param array  $data
     * @param array  $options
     *
     * @return bool
     *
     * @throws \Exception
     */
    public function update($collection, $criteria = [], $data = [], $options = [])
    {
        $this->start();

        try {
            $builtCriteria = $this->buildCriteria($criteria);
            $builtData     = $this->buildData($data);

            $result = $this->getCollection($collection, $options)->update(
                $builtCriteria,
                $builtData,
                $options
            );

            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('update', 'db.'.$collection.'.update('.json_encode($builtCriteria).', '.json_encode($builtData).')', ['collection' => $collection, 'criteria' => $builtCriteria, 'data' => $builtData, 'options' => $options], $startDate, $endDate, $result));

            return $result;
        } catch (\Exception $e) {
            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('update', 'db.'.$collection.'.update('.json_encode($criteria).', '.json_encode($data).')', ['rawCriteria' => $criteria, 'rawData' => $data, 'options' => $options], $startDate, $endDate, null, $e));
            throw $e;
        }
    }
    /**
     * Remove (the first matching criteria) a document of the specified collection.
     *
     * @param string $collection
     * @param array  $criteria
     * @param array  $options
     * @return array|bool
     */
    public function remove($collection, $criteria = [], $options = [])
    {
        $this->start();

        try {
            $builtCriteria = $this->buildCriteria($criteria);

            $result = $this->getCollection($collection, $options)->remove(
                $builtCriteria,
                $options
            );

            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('remove', 'db.'.$collection.'.remove('.json_encode($builtCriteria).')', ['collection' => $collection, 'criteria' => $builtCriteria, 'options' => $options], $startDate, $endDate, $result));

            return $result;
        } catch (\Exception $e) {
            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('remove', 'db.'.$collection.'.remove('.json_encode($criteria).')', ['rawCriteria' => $criteria, 'options' => $options], $startDate, $endDate, null, $e));
            throw $e;
        }
    }
    /**
     * Insert a single document into the specified collection.
     *
     * @param string $collection
     * @param array  $data
     * @param array  $options
     *
     * @return array|bool
     *
     * @throws \Exception
     */
    public function insert($collection, $data = [], $options = [])
    {
        $this->start();

        try {
            $builtData = $this->buildData($data);

            $result = $this->getCollection($collection, $options)->insert(
                $builtData,
                $options
            );

            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('insert', 'db.'.$collection.'.insert('.json_encode($builtData).')', ['data' => $builtData, 'options' => $options], $startDate, $endDate, $result));

            return $result;
        } catch (\Exception $e) {
            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('insert', 'db.'.$collection.'.insert('.json_encode($data).')', ['rawData' => $data, 'options' => $options], $startDate, $endDate, null, $e));
            throw $e;
        }
    }
    /**
     * Insert a list of documents into the specified collection.
     *
     * @param string $collection
     * @param array  $bulkData
     * @param array  $options
     *
     * @return mixed
     */
    public function bulkInsert($collection, $bulkData = [], $options = [])
    {
        $this->start();

        try {
            $builtData = $this->buildBulkData($bulkData);

            $result = $this->getCollection($collection, $options)->batchInsert(
                $builtData,
                $options
            );

            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('bulkInsert', 'db.'.$collection.'.bulkInsert('.json_encode($builtData).')', ['data' => $builtData, 'options' => $options], $startDate, $endDate, $result));

            return $result;
        } catch (\Exception $e) {
            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('bulkInsert', 'db.'.$collection.'.bulkIinsert('.json_encode($bulkData).')', ['rawData' => $bulkData, 'options' => $options], $startDate, $endDate, null, $e));
            throw $e;
        }
    }
    /**
     * Retrieve (if match criteria) a list of documents from the specified collection.
     *
     * @param string   $collection
     * @param array    $criteria
     * @param array    $fields
     * @param null|int $limit
     * @param int      $offset
     * @param array    $sorts
     * @param array    $options
     *
     * @return \MongoCursor
     *
     * @throws \Exception
     */
    public function find($collection, $criteria = [], $fields = [], $limit = null, $offset = 0, $sorts = [], $options = [])
    {
        $this->start();

        try {
            $builtCriteria = $this->buildCriteria($criteria);
            $builtFields   = $this->buildFields($fields);
            $builtSorts    = $this->buildSorts($sorts);

            $cursor = $this->getCollection($collection, $options)->find($builtCriteria);

            if (count($builtFields)) {
                $cursor->fields($builtFields);
            }
            if (count($builtSorts)) {
                $cursor->sort($builtSorts);
            }
            if (is_numeric($offset) && $offset > 0) {
                $cursor->skip($offset);
            }
            if (is_numeric($limit) && $limit > 0) {
                $cursor->limit($limit);
            }

            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('find', 'db.'.$collection.'.find('.json_encode($builtCriteria).', '.json_encode($builtFields).')', ['criteria' => $builtCriteria, 'fields' => $builtFields, 'limit' => $limit, 'sort' => $builtSorts, 'skip' => $offset, 'options' => $options], $startDate, $endDate, $cursor));

            return $cursor;
        } catch (\Exception $e) {
            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('find', 'db.'.$collection.'.find('.json_encode($criteria).', '.json_encode($fields).')', ['rawCriteria' => $criteria, 'rawFields' => $fields, 'limit' => $limit, 'rawSort' => $sorts, 'skip' => $offset, 'options' => $options], $startDate, $endDate, null, $e));
            throw $e;
        }
    }
    /**
     * Retrieve (if match criteria) one document from the specified collection.
     *
     * @param string $collection
     * @param array  $criteria
     * @param array  $fields
     * @param array  $options
     *
     * @return array|null
     *
     * @throws \Exception
     */
    public function findOne($collection, $criteria = [], $fields = [], $options = [])
    {
        $this->start();

        try {
            $builtCriteria = $this->buildCriteria($criteria);
            $builtFields   = $this->buildFields($fields);

            $result = $this->getCollection($collection, $options)->findOne(
                $builtCriteria,
                $builtFields
            );

            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('findOne', 'db.'.$collection.'.findOne('.json_encode($builtCriteria).', '.json_encode($builtFields).')', ['criteria' => $builtCriteria, 'fields' => $builtFields, 'options' => $options], $startDate, $endDate, $result));

            return $result;
        } catch (\Exception $e) {
            list ($startDate, $endDate) = $this->stop();
            $this->dispatch('database.query.executed', new DatabaseQueryEvent('findOne', 'db.'.$collection.'.findOne('.json_encode($criteria).', '.json_encode($fields).')', ['rawCriteria' => $criteria, 'rawFields' => $fields, 'options' => $options], $startDate, $endDate, null, $e));
            throw $e;
        }
    }
    /**
     * Count the documents matching the criteria.
     *
     * @param string $collection
     * @param array  $criteria
     * @param array  $options
     *
     * @return int
     */
    public function count($collection, $criteria = [], $options = [])
    {
        return $this->find($collection, $criteria, ['_id'], null, 0, [], $options)
            ->count(true);
    }
    /**
     * Ensures the specified index is present on the specified fields of the collection.
     *
     * @param string       $collection
     * @param string|array $fields
     * @param mixed        $index
     * @param array        $options
     *
     * @return bool
     */
    public function ensureIndex($collection, $fields, $index, $options = [])
    {
        return $this->getCollection($collection, $options)->createIndex(
            is_array($fields) ? $fields : [$fields => true],
            $index
        );
    }
    /**
     * @param string|null $name
     *
     * @return array
     */
    public function getStatistics($name = null)
    {
        return $this->getMongoClient()
            ->selectDB(null === $name ? $this->getDatabaseName() : $name)
            ->command(['dbStats' => true])
        ;
    }
    /**
     * Ensure specified id is a MongoId (convert to MongoId if is string).
     *
     * @param string $id
     *
     * @return \MongoId
     *
     * @throws Exception if malformed
     */
    protected function ensureMongoId($id)
    {
        if (is_object($id) && $id instanceof MongoId) {
            return $id;
        }

        if (!preg_match('/^[a-f0-9]{24}$/', $id)) {
            throw $this->createMalformedException('Malformed id');
        }

        return new MongoId($id);
    }
    /**
     * Ensure criteria are well formed (array).
     *
     * @param array $criteria
     *
     * @return array
     */
    protected function buildCriteria($criteria)
    {
        if (!is_array($criteria)) {
            return [];
        }

        if (isset($criteria['_id'])) {
            $criteria['_id'] = $this->ensureMongoId($criteria['_id']);
        }

        if (isset($criteria['$or']) && is_array($criteria['$or'])) {
            foreach ($criteria['$or'] as $a => $b) {
                if (isset($b['_id'])) {
                    $criteria['$or'][$a]['_id'] = $this->ensureMongoId($b['_id']);
                }
            }
        }

        foreach ($criteria as $k => $v) {
            if ('*notempty*' === $v) {
                $criteria[$k] = ['$exists' => true];
            }
            if ('*empty*' === $v) {
                $criteria[$k] = ['$exists' => false];
            }
        }

        return $criteria;
    }
    /**
     * Ensure fields are well formed (array).
     *
     * @param array|mixed $fields
     *
     * @return array
     */
    protected function buildFields($fields)
    {
        $cleanedFields = [];

        if (!is_array($fields)) {
            return $cleanedFields;
        }

        foreach ($fields as $k => $v) {
            if (is_numeric($k)) {
                if (!is_string($v)) {
                    $v = (string) $v;
                }
                $cleanedFields[$v] = true;
            } else {
                if (!is_bool($v)) {
                    $v = (bool) $v;
                }
                $cleanedFields[$k] = $v;
            }
        }

        return $cleanedFields;
    }
    /**
     * Ensure sorts are well formed (array).
     *
     * @param array|mixed $sorts
     *
     * @return array
     */
    protected function buildSorts($sorts)
    {
        $cleanedSorts = [];

        if (!is_array($sorts)) {
            return $cleanedSorts;
        }

        foreach ($sorts as $k => $v) {
            if (is_numeric($k)) {
                if (!is_string($v)) {
                    $v = (string) $v;
                }
                $cleanedSorts[$v] = 1;
            } else {
                $v = ((int) $v) === -1 ? -1 : 1;
                $cleanedSorts[$k] = $v;
            }
        }

        return $cleanedSorts;
    }
    /**
     * Ensure document data are well formed (array).
     *
     * @param array $data
     *
     * @return array
     */
    protected function buildData($data)
    {
        if (!is_array($data) || !count($data)) {
            return [];
        }

        if (isset($data['_id'])) {
            $data['_id'] = $this->ensureMongoId($data['_id']);
        }

        return $data;
    }
    /**
     * Ensure documents data are well formed (array).
     *
     * @param array $bulkData
     *
     * @return array
     */
    protected function buildBulkData($bulkData)
    {
        if (!is_array($bulkData) || !count($bulkData)) {
            return [];
        }

        foreach ($bulkData as $a => $b) {
            $bulkData[$a] = $this->buildData($b);
        }

        return $bulkData;
    }
    /**
     * @return float
     */
    protected function start()
    {
        $now = microtime(true);

        $this->timers[] = $now;

        return $now;
    }
    /**
     * @return float[]
     *
     * @throws \Exception
     */
    protected function stop()
    {
        if (!count($this->timers)) {
            $this->start();
            //throw $this->createRequiredException('No timer started'.(new \Exception("toto"))->getTraceAsString());
        }

        $endDate = microtime(true);

        return [array_pop($this->timers), $endDate];
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\PaymentProvider;

use Symfony\Component\HttpFoundation\Request;
use Velocity\Bundle\ApiBundle\Service\MangoPayService;
use Velocity\Core\Traits\ServiceTrait;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;
use Velocity\Bundle\ApiBundle\PaymentProviderInterface;

/**
 * MangoPay Payment Provider.
 *
 * @author <NAME> <<EMAIL>>
 */
class MangoPayPaymentProvider implements PaymentProviderInterface
{
    use ServiceTrait;
    use ServiceAware\MangoPayServiceAwareTrait;
    /**
     * MangoPayPaymentProvider constructor.
     *
     * @param MangoPayService $mangoPayService
     */
    public function __construct(MangoPayService $mangoPayService)
    {
        $this->setMangoPayService($mangoPayService);
    }

    /**
     * @param array $data
     * @param array $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function create($data = [], $options = [])
    {
        throw $this->createNotYetImplementedException('MangoPay payment provider not yet implemented');
    }
    /**
     * @param string|array $id
     * @param array        $data
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function confirm($id, $data = [], $options = [])
    {
        throw $this->createNotYetImplementedException('MangoPay payment provider not yet implemented');
    }
    /**
     * @param string|array $id
     * @param array        $data
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function fail($id, $data = [], $options = [])
    {
        throw $this->createNotYetImplementedException('MangoPay payment provider not yet implemented');
    }
    /**
     * @param string|array $id
     * @param array        $data
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function cancel($id, $data = [], $options = [])
    {
        throw $this->createNotYetImplementedException('MangoPay payment provider not yet implemented');
    }
    /**
     * @param string|array $id
     * @param array        $options
     *
     * @return array
     *
     * @throws \Exception
     */
    public function get($id, $options = [])
    {
        throw $this->createNotYetImplementedException('MangoPay payment provider not yet implemented');
    }
    /**
     * @param string  $callback
     * @param Request $request
     *
     * @return array
     */
    public function parseCallbackRequest($callback, Request $request)
    {
        switch ($callback) {
            default:
                return [];
        }
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Model;

use Velocity\Bundle\ApiBundle\Annotation as Velocity;
use /** @noinspection PhpUnusedAliasInspection */ Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

/**
 * @author <NAME> <<EMAIL>>
 * @Velocity\Model("modelWithAnnotatedMethods")
 */
class ModelWithAnnotatedMethods
{
    /**
     * @Route("/test-route")
     *
     * @Velocity\Sdk
     */
    public function sdkMethod()
    {
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Service;

use Velocity\Bundle\ApiBundle\RepositoryInterface;
use Velocity\Core\Traits\ServiceTrait;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess;
use Velocity\Bundle\ApiBundle\Traits\ServiceAware;

/**
 * MetaData Service.
 *
 * @author <NAME> <<EMAIL>>
 */
class MetaDataService
{
    use ServiceTrait;
    use ServiceAware\StorageServiceAwareTrait;
    use ServiceAware\WorkflowServiceAwareTrait;
    use ServiceAware\GeneratorServiceAwareTrait;
    /**
     * @var array
     */
    protected $callbacks = [];
    /**
     * @var array
     */
    protected $models = [];
    /**
     * @var array
     */
    protected $modelIds = [];
    /**
     * @var array
     */
    protected $sdk = ['services' => []];
    /**
     * @param string                                                                                    $name
     * @param DocumentServiceInterface|SubDocumentServiceInterface|SubSubDocumentServiceInterface|mixed $service
     *
     * @return $this
     */
    public function addCrudService($name, $service)
    {
        return $this->setArrayParameterKey('crudServices', $name, $service);
    }
    /**
     * @param string $name
     *
     * @return DocumentServiceInterface|SubDocumentServiceInterface|SubSubDocumentServiceInterface|mixed
     *
     * @throws \Exception
     */
    public function getCrudService($name)
    {
        return $this->getArrayParameterKey('crudServices', $name);
    }
    /**
     * @return DocumentServiceInterface[]|SubDocumentServiceInterface[]|SubSubDocumentServiceInterface[]|array
     */
    public function getCrudServices()
    {
        return $this->getArrayParameter('crudServices');
    }
    /**
     * @param StorageService   $storageService
     * @param GeneratorService $generatorService
     * @param WorkflowService  $workflowService
     */
    public function __construct(StorageService $storageService, GeneratorService $generatorService, WorkflowService $workflowService)
    {
        $this->setStorageService($storageService);
        $this->setGeneratorService($generatorService);
        $this->setWorkflowService($workflowService);
    }
    /**
     * @param string $class
     *
     * @return $this
     *
     * @throws \Exception
     */
    public function checkModel($class)
    {
        if (!$this->isModel($class)) {
            throw $this->createUnexpectedException("Class '%s' is not registered as a model", $class);
        }

        return $this;
    }
    /**
     * @param string $class
     * @param array  $definition
     *
     * @return $this
     */
    public function addModel($class, $definition)
    {
        if (!isset($this->models[$class])) {
            $this->models[$class] = [
                'embeddedReferences'     => [],
                'embeddedReferenceLists' => [],
                'referenceLists'         => [],
                'refreshes'              => [],
                'generateds'             => [],
                'storages'               => [],
                'ids'                    => [],
                'types'                  => [],
                'fingerPrints'           => [],
                'workflows'              => [],
                'triggers'               => [],
                'cachedLists'            => [],
            ];
        }

        $this->models[$class] += $definition;

        $this->modelIds[strtolower($definition['id'])] = $class;

        return $this;
    }
    /**
     * @param $modelClass
     *
     * @return mixed|DocumentServiceInterface|SubDocumentServiceInterface|SubSubDocumentServiceInterface
     */
    public function getCrudServiceByModelClass($modelClass)
    {
        return $this->getCrudService($this->getModel($modelClass)['id']);
    }
    /**
     * @param string $type
     * @param mixed  $callback
     *
     * @return $this
     */
    public function addCallback($type, $callback)
    {
        if (!isset($this->callbacks[$type])) {
            $this->callbacks[$type] = [];
        }
        $this->callbacks[$type][] = $callback;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyEmbeddedReference($class, $property, $definition)
    {
        $this->checkModel($class);

        $this->models[$class]['embeddedReferences'][$property] = $definition;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyEmbeddedReferenceList($class, $property, $definition)
    {
        $this->checkModel($class);

        $this->models[$class]['embeddedReferenceLists'][$property] = $definition;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyCachedList($class, $property, $definition)
    {
        $this->checkModel($class);

        $this->models[$class]['cachedLists'][$property] = $definition;

        $modelId = $this->models[$class]['id'];

        foreach ($definition['triggers'] as $triggerName => $trigger) {
            $sourceClass = $this->getModelClassForId($trigger['model']);
            unset($trigger['model']);
            $this->checkModel($sourceClass);
            if (!isset($this->models[$sourceClass]['triggers'])) {
                $this->models[$sourceClass]['triggers'] = [];
            }
            $this->models[$sourceClass]['triggers'][$modelId.'.'.$triggerName] = ['targetDocType' => $modelId, 'targetDocProperty' => $property] + $trigger;
        }

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyReferenceList($class, $property, $definition)
    {
        $this->checkModel($class);

        $this->models[$class]['referenceLists'][$property] = $definition;

        $values = $definition['value'];
        if (!is_array($values)) {
            $values = [];
        }

        if (!isset($this->models[$class]['types'][$property])) {
            $this->models[$class]['types'][$property] = [];
        }

        $this->models[$class]['types'][$property]['type'] = 'referenceList';
        $this->models[$class]['types'][$property]['values'] = $values;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyRefresh($class, $property, $definition)
    {
        $this->checkModel($class);

        $operations = $definition['value'];
        if (!is_array($operations)) {
            $operations = [$operations];
        }

        foreach ($operations as $operation) {
            if (!isset($this->models[$class]['refreshes'][$operation])) {
                $this->models[$class]['refreshes'][$operation] = [];
            }

            $this->models[$class]['refreshes'][$operation][$property] = true;
        }

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyEnum($class, $property, $definition)
    {
        $this->checkModel($class);

        $values = $definition['value'];
        if (!is_array($values)) {
            $values = [];
        }

        if (!isset($this->models[$class]['types'][$property])) {
            $this->models[$class]['types'][$property] = [];
        }

        $this->models[$class]['types'][$property]['type'] = 'enum';
        $this->models[$class]['types'][$property]['values'] = $values;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyDefaultValue($class, $property, $definition)
    {
        $this->checkModel($class);

        $value = isset($definition['value']) ? $definition['value'] : null;

        if (!isset($this->models[$class]['types'][$property])) {
            $this->models[$class]['types'][$property] = [];
        }

        $this->models[$class]['types'][$property]['default'] = $value;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyGenerated($class, $property, $definition)
    {
        $this->checkModel($class);

        $definition['type'] = $definition['value'];
        unset($definition['value']);

        $this->models[$class]['generateds'][$property] = $definition;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyFingerPrint($class, $property, $definition)
    {
        $this->checkModel($class);

        unset($definition['value']);

        if (!isset($definition['of'])) {
            $definition['of'] = [];
        }

        if (!is_array($definition['of'])) {
            $definition['of'] = [$definition['of']];
        }

        $this->models[$class]['fingerPrints'][$property] = $definition;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyStorage($class, $property, $definition)
    {
        $this->checkModel($class);

        $definition['key'] = $definition['value'];
        unset($definition['value']);

        $this->models[$class]['storages'][$property] = $definition;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyWorkflow($class, $property, $definition)
    {
        $this->checkModel($class);

        $this->models[$class]['workflows'][$property] = $definition;

        if (!isset($this->models[$class]['types'][$property])) {
            $this->models[$class]['types'][$property] = [];
        }

        $this->models[$class]['types'][$property]['type'] = 'workflow';
        $this->models[$class]['types'][$property]['values'] = isset($definition['steps']) ? $definition['steps'] : [];

        $this->getWorkflowService()->registerFromDefinition($definition['id'], $definition);

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function addModelPropertyId($class, $property, $definition)
    {
        $this->checkModel($class);

        $definition['name'] = isset($definition['name']) ? $definition['name'] : '_id';
        $definition['property'] = $property;
        unset($definition['value']);

        $this->models[$class]['ids'] = $definition;

        return $this;
    }
    /**
     * @param string $class
     * @param string $property
     * @param array  $definition
     *
     * @return $this
     */
    public function setModelPropertyType($class, $property, $definition)
    {
        $this->checkModel($class);

        if (!isset($this->models[$class]['types'][$property])) {
            $this->models[$class]['types'][$property] = [];
        }

        $this->models[$class]['types'][$property]['type'] = $definition['name'];

        return $this;
    }
    /**
     * @param string $sourceClass
     * @param string $sourceMethod
     * @param string $route
     * @param string $service
     * @param string $method
     * @param string $type
     * @param array  $params
     * @param array  $options
     *
     * @return $this
     *
     * @throws \Exception
     */
    public function addSdkMethod($sourceClass, $sourceMethod, $route, $service, $method, $type, $params = [], $options = [])
    {
        if (!isset($this->sdk['services'][$service])) {
            $this->sdk['services'][$service] = ['methods' => []];
        }
        if (isset($this->sdk['services'][$service]['methods'][$method])) {
            throw $this->createDuplicatedException("SDK Method '%s' already registered for service '%s'", $method, $service);
        }

        $this->sdk['services'][$service]['methods'][$method] = [
            'sourceClass'  => $sourceClass,
            'sourceMethod' => $sourceMethod,
            'type'         => $type,
            'route'        => $route,
            'params'       => $params,
            'options'      => $options,
        ];

        return $this;
    }
    /**
     * @return array
     */
    public function getSdkServices()
    {
        return $this->sdk['services'];
    }
    /**
     * @param string $id
     *
     * @return string
     *
     * @throws \Exception
     */
    public function getModelClassForId($id)
    {
        if (!isset($this->modelIds[strtolower($id)])) {
            throw $this->createRequiredException("Unknown model '%s'", $id);
        }

        return $this->modelIds[strtolower($id)];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelEmbeddedReferences($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['embeddedReferences'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelReferenceLists($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['referenceLists'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelTriggers($class, array $options = [])
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        unset($options);

        return $this->models[$class]['triggers'];
    }
    /**
     * @param string|Object $class
     * @param string        $property
     *
     * @return array
     *
     * @throws \Exception
     */
    public function getModelReferenceListByProperty($class, $property)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        if (!isset($this->models[$class]['referenceLists'][$property])) {
            throw $this->createRequiredException("Property '%s' is a not a reference list", $property);
        }

        return $this->models[$class]['referenceLists'][$property];
    }
    /**
     * @return array
     */
    public function getModels()
    {
        return $this->models;
    }
    /**
     * @param string|object $class
     *
     * @return bool
     */
    public function isModel($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        return true === isset($this->models[$class]);
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelTypes($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['types'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelDefaults($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        $defaults = [];

        foreach ($this->models[$class]['types'] as $property => $type) {
            if (!isset($type['default'])) {
                continue;
            }
            $defaults[$property] = $type['default'];
        }

        return $defaults;
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelGenerateds($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['generateds'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelFingerPrints($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['fingerPrints'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelStorages($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['storages'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelWorkflows($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['workflows'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelEmbeddedReferenceLists($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['embeddedReferenceLists'];
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModelCachedLists($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['cachedLists'];
    }
    /**
     * @param string|Object $class
     *
     * @return array|null
     */
    public function getModelIdProperty($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return $this->models[$class]['ids'];
    }
    /**
     * @param string|Object $class
     * @param string        $operation
     *
     * @return array
     */
    public function getModelRefreshablePropertiesByOperation($class, $operation)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return isset($this->models[$class]['refreshes'][$operation])
            ? array_keys($this->models[$class]['refreshes'][$operation])
            : []
        ;
    }
    /**
     * @param string|Object $class
     * @param string        $property
     *
     * @return null|string
     */
    public function getModelPropertyType($class, $property)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $this->checkModel($class);

        return isset($this->models[$class]['types'][$property])
            ? $this->models[$class]['types'][$property]
            : null;
    }
    /**
     * @param string $class
     * @param string $property
     *
     * @return TypeGuess
     */
    public function getModelPropertyTypeGuess($class, $property)
    {
        $propertyType = $this->getModelPropertyType($class, $property);

        if (null === $propertyType) {
            return new TypeGuess(null, [], Guess::LOW_CONFIDENCE);
        }

        switch ($propertyType['type']) {
            case 'enum':
                return new TypeGuess('choice', ['choices' => $propertyType['values']], Guess::HIGH_CONFIDENCE);
            case 'referenceList':
                $referenceList = $this->getModelReferenceListByProperty($class, $property);
                $choices = [];
                foreach ($this->getCrudService($referenceList['type'])->find([], []) as $choice) {
                    $choice = (array) $choice;
                    $choices[$choice[$referenceList['key']]] = $choice[$referenceList['labelKey']];
                }

                return new TypeGuess('choice', ['multiple' => true, 'choices' => $choices], Guess::HIGH_CONFIDENCE);
            case 'workflow':
                return new TypeGuess('choice', ['choices' => $propertyType['steps']], Guess::HIGH_CONFIDENCE);
            default:
                return new TypeGuess(null, [], Guess::LOW_CONFIDENCE);
        }
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    public function refresh($doc, $options = [])
    {
        $doc = $this->convertScalarProperties($doc, $options);
        $doc = $this->fetchEmbeddedReferences($doc, $options);
        $doc = $this->fetchEmbeddedReferenceLists($doc, $options);
        $doc = $this->triggerRefreshes($doc, $options);
        $doc = $this->buildGenerateds($doc, $options);
        $doc = $this->computeFingerPrints($doc, $options);
        $doc = $this->saveStorages($doc, $options);
        $doc = $this->loadDefaultValues($doc, $options);

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    public function clean($doc, $options = [])
    {
        $doc = $this->populateStorages($doc, $options);

        $this->refreshCached($doc, $options);

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     */
    protected function refreshCached($doc, array $options = [])
    {
        $triggers = $this->getModelTriggers($doc, $options);

        $options += ['operation' => null];


        foreach ($triggers as $triggerName => $trigger) {
            $targetId = $this->replaceProperty($doc, $trigger['targetId']);
            $updateData = $this->replaceProperty($doc, $trigger['targetData']);
            $requiredFields = [];
            foreach ($trigger['targetData'] as $kkk => $vvv) {
                if ('@' === $vvv{0}) {
                    $requiredFields[substr($vvv, 1)] = true;
                }
            }
            $requiredFields = array_values($requiredFields);
            $createData = $updateData;
            $updateCriteria = [];
            $createCriteria = [];
            $deleteCriteria = [];
            $skip = true;
            $list = isset($trigger['joinFieldType']) && 'list' === $trigger['joinFieldType'];
            $processNew = false;
            $processUpdated = false;
            $processDeleted = false;
            switch ($options['operation']) {
                case 'create':
                    if ($list) {
                        if (count($doc->{$trigger['joinField']})) {
                            $createCriteria['$or'] = [];
                            foreach ($doc->{$trigger['joinField']} as $kk => $vv) {
                                $createCriteria['$or'][] = ['_id' => $vv->id];
                            }
                            $processNew = true;
                            $skip = false;
                        }
                    } else {
                        $createCriteria['_id'] = $doc->{$trigger['joinField']}->id;
                        $processNew = true;
                        $skip = false;
                    }
                    break;
                case 'update':
                    $updateData = array_filter($updateData, function ($v) { return null !== $v;});
                    if ($list) {
                        $expectedIds = [];
                        $existingIds = [];
                        if (count($doc->{$trigger['joinField']})) {
                            foreach ($doc->{$trigger['joinField']} as $kk => $vv) {
                                $expectedIds[$vv->id] = true;
                            }
                        }

                        $criteria = [
                            $trigger['targetDocProperty'] . '.' . $targetId => '*notempty*',
                        ];
                        $docs = $this->getCrudService($trigger['targetDocType'])->find($criteria, ['id']);

                        if (count($docs)) {
                            foreach ($docs as $_doc) {
                                $existingIds[$_doc->id] = true;
                            }
                        }

                        $newIds = array_diff_key($expectedIds, $existingIds);
                        $deletedIds = array_diff_key($existingIds, $expectedIds);
                        $updatedIds = array_intersect_key($existingIds, $expectedIds);

                        if (count($newIds)) {
                            $createCriteria['$or'] = [];
                            foreach(array_keys($newIds) as $newId) {
                                $createCriteria['$or'][] = ['_id' => $newId];
                            }
                            $realDoc = $this->getCrudService($this->getModel($doc)['id'])->get($doc->id, $requiredFields);
                            foreach($requiredFields as $requiredField) {
                                if (isset($realDoc->$requiredField) && !isset($doc->$requiredField)) {
                                    $doc->$requiredField = $realDoc->$requiredField;
                                }
                            }
                            $skip = false;
                            $processNew = true;
                        }
                        if (count($deletedIds)) {
                            $deleteCriteria['$or'] = [];
                            foreach(array_keys($deletedIds) as $deletedId) {
                                $deleteCriteria['$or'][] = ['_id' => $deletedId];
                            }
                            $skip = false;
                            $processDeleted = true;
                        }
                        if (count($updatedIds)) {
                            $updateCriteria['$or'] = [];
                            foreach(array_keys($updatedIds) as $updatedId) {
                                $updateCriteria['$or'][] = ['_id' => $updatedId];
                            }
                            $skip = false;
                            $processUpdated = true;
                        }
                    } else {
                        $criteria = [
                            $trigger['targetDocProperty'] . '.' . $targetId => '*notempty*',
                        ];
                        $docs = $this->getCrudService($trigger['targetDocType'])->find($criteria, ['id']);

                        if (count($docs)) {
                            $updateCriteria['$or'] = [];
                            foreach ($docs as $_doc) {
                                $updateCriteria['$or'][] = ['_id' => $_doc->id];
                            }
                            $processUpdated = true;
                            $skip = false;
                        }
                    }
                    break;
                case 'delete':
                    $criteria = [
                        $trigger['targetDocProperty'].'.'.$targetId => '*notempty*',
                    ];
                    $docs = $this->getCrudService($trigger['targetDocType'])->find($criteria, ['id']);

                    if (count($docs)) {
                        $deleteCriteria['$or'] = [];
                        foreach ($docs as $_doc) {
                            $deleteCriteria['$or'][] = ['_id' => $_doc->id];
                        }
                        $skip = false;
                        $processDeleted = true;
                    }
                    break;
            }
            if (!$skip) {
                /** @var RepositoryInterface $repo */
                $repo = $this->getCrudService($trigger['targetDocType'])->getRepository();
                if ($processDeleted) {
                    $repo->unsetProperty($deleteCriteria, $trigger['targetDocProperty'] . '.' . $targetId);
                }
                if ($processNew) {
                    $repo->alter($createCriteria, ['$set' => [$trigger['targetDocProperty'].'.'.$targetId => $createData]], ['multiple' => true]);
                }
                if ($processUpdated) {
                    $updates = [];
                    foreach ($updateData as $k => $v) {
                        $updates[$trigger['targetDocProperty'].'.'.$targetId.'.'.$k] = $v;
                    }
                    if (count($updates)) {
                        $repo->alter($updateCriteria, ['$set' => $updates], ['multiple' => true]);
                    }
                }
            }
        }
    }
    /**
     * @param mixed $doc
     * @param mixed $value
     *
     * @return mixed
     */
    protected function replaceProperty($doc, $value)
    {
        if (is_array($value)) {
            foreach ($value as $k => $v) {
                $value[$k] = $this->replaceProperty($doc, $v);
            }

            return $value;
        }

        if ('@' !== $value{0}) {
            return $value;
        }

        $key = <EMAIL>($value, 1);

        if (!property_exists($doc, $key)) {
            return null;
        }

        return $doc->$key;
    }
    /**
     * Process the registered callbacks.
     *
     * @param string $type
     * @param mixed  $subject
     * @param array  $options
     *
     * @return mixed
     */
    public function callback($type, $subject = null, $options = [])
    {
        if (!isset($this->callbacks[$type]) || !count($this->callbacks[$type])) {
            return $subject;
        }

        foreach ($this->callbacks[$type] as $callback) {
            $r = call_user_func_array($callback, [$subject, $options]);

            if (null !== $r) {
                $subject = $r;
            }
        }

        return $subject;
    }
    /**
     * @param string|Object $class
     *
     * @return array
     */
    public function getModel($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        if ('stdClass' === $class) {
            // bypass check if stdClass (used for array to object cast)
            return [];
        }
        $this->checkModel($class);

        return $this->models[$class];
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     *
     * @throws \Exception
     */
    public function convertObjectToArray($doc, $options = [])
    {
        $options += ['removeNulls' => true];

        if (!is_object($doc)) {
            throw $this->createMalformedException('Not a valid object');
        }

        $removeNulls = true === $options['removeNulls'];

        $meta = $this->getModel($doc);
        $data = get_object_vars($doc);

        $globalObjectCast = false;
        if (get_class($doc) === 'stdClass') {
            $globalObjectCast = true;
        }
        foreach ($data as $k => $v) {
            if ($removeNulls && null === $v) {
                unset($data[$k]);
                continue;
            }
            if (isset($meta['types'][$k]['type'])) {
                switch (true) {
                    case 'DateTime' === substr($meta['types'][$k]['type'], 0, 8):
                        $data = $this->convertDataDateTimeFieldToMongoDateWithTimeZone($data, $k);
                        continue 2;
                }
            }
            if (is_object($v)) {
                $objectCast = false;
                if ('stdClass' === get_class($v)) {
                    $objectCast = true;
                }
                $v = $this->convertObjectToArray($v, $options);
                if (true === $objectCast) {
                    $v = (object) $v;
                }
            }
            $data[$k] = $v;
        }

        if (true === $globalObjectCast) {
            $data = (object) $data;
        }

        return $data;
    }
    /**
     * @param mixed $doc
     * @param array $data
     * @param array $options
     *
     * @return mixed
     */
    public function populateObject($doc, $data = [], $options = [])
    {
        if (isset($data['_id']) && !isset($data['id'])) {
            $data['id'] = (string) $data['_id'];
            unset($data['_id']);
        } else {
            if (isset($data['id'])) {
                $data['id'] = (string) $data['id'];
            }
        }

        $doc = $this->mutateArrayToObject($data, $doc, $options);

        return $doc;
    }
    /**
     * @param mixed  $doc
     * @param mixed  $data
     * @param string $propertyName
     * @param array  $options
     *
     * @return mixed
     *
     * @throws \Exception
     */
    public function populateObjectProperty($doc, $data, $propertyName, $options = [])
    {
        if (!property_exists($doc, $propertyName)) {
            throw $this->createRequiredException("Property '%s' does not exist on %s", $propertyName, get_class($doc));
        }

        $doc->$propertyName = $data;

        $this->populateStorages($doc, $options);

        return $doc->$propertyName;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    protected function fetchEmbeddedReferences($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        foreach ($this->getModelEmbeddedReferences($doc) as $property => $embeddedReference) {
            if (!$this->isPopulableModelProperty($doc, $property, $options)) {
                continue;
            }
            $type = $this->getModelPropertyType($doc, $property);
            $doc->$property = $this->convertIdToObject($doc->$property, isset($embeddedReference['class']) ? $embeddedReference['class'] : ($type ? $type['type'] : null), $embeddedReference['type']);
        }

        return $doc;
    }
    /**
     * @param mixed  $doc
     * @param string $property
     * @param array  $options
     *
     * @return bool
     */
    protected function isPopulableModelProperty($doc, $property, array $options = [])
    {
        return property_exists($doc, $property) && (!isset($options['populateNulls']) || (false === $options['populateNulls'] && null !== $doc->$property));
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     *
     * @throws \Exception
     */
    protected function fetchEmbeddedReferenceLists($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        foreach ($this->getModelEmbeddedReferenceLists($doc) as $property => $embeddedReferenceList) {
            if (!$this->isPopulableModelProperty($doc, $property, $options)) {
                continue;
            }
            $type = $this->getModelPropertyType($doc, $property);
            if (!is_array($doc->$property)) {
                $doc->$property = [];
            }
            $subDocs = [];
            foreach ($doc->$property as $kk => $subDocKey) {
                $tt = isset($embeddedReferenceList['class']) ? $embeddedReferenceList['class'] : ($type ? $type['type'] : null);
                if (null !== $tt) {
                    $tt = preg_replace('/^array<([^>]+)>$/', '\\1', $tt);
                }
                $subDoc = $this->convertIdToObject($subDocKey, $tt, $embeddedReferenceList['type']);
                unset($doc->$property[$kk]);
                if (!isset($subDoc->id)) {
                    throw $this->createRequiredException('Property id is empty for embedded reference', 500);
                }
                $subDocs[$subDoc->id] = $subDoc;
            }
            $doc->$property = (object) $subDocs;
        }

        return $doc;
    }
    /**
     * @param string $id
     * @param string $class
     * @param string $type
     *
     * @return Object
     */
    protected function convertIdToObject($id, $class, $type)
    {
        $model = $this->createModelInstance(['model' => $class]);
        $fields = array_keys(get_object_vars($model));

        return $this->getCrudService($type)->get($id, $fields, ['model' => $model]);
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    protected function buildGenerateds($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        $generateds = $this->getModelGenerateds($doc);

        foreach ($generateds as $k => $v) {
            $generate = false;
            if (isset($v['trigger'])) {
                if (isset($doc->{$v['trigger']})) {
                    $generate = true;
                }
            } else {
                if ($this->isPopulableModelProperty($doc, $k, $options)) {
                    $generate = true;
                }
            }
            if (true === $generate) {
                $doc->$k = $this->generateValue($v, $doc);
            }
        }

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    protected function loadDefaultValues($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        if (!isset($options['operation']) || 'create' !== $options['operation']) {
            return $doc;
        }

        $defaults = $this->getModelDefaults($doc);

        foreach ($defaults as $k => $v) {
            if (!$this->isPopulableModelProperty($doc, $k, $options)) {
                continue;
            }
            $doc->$k = $v;
        }

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    protected function computeFingerPrints($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        $fingerPrints = $this->getModelFingerPrints($doc);

        foreach ($fingerPrints as $k => $v) {
            $values = [];

            $found = false;

            foreach ($v['of'] as $p) {
                if (!isset($doc->$p)) {
                    $values[$p] = null;
                    continue;
                } else {
                    $values[$p] = $doc->$p;
                    $found = true;
                }
            }

            unset($v['of']);

            if (true === $found) {
                $doc->$k = $this->generateValue(['type' => 'fingerprint'], count($values) > 1 ? $values : array_shift($values));
            }
        }

        unset($options);

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    protected function saveStorages($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        $vars = ((array) $doc);

        foreach ($options as $k => $v) {
            if (!isset($vars[$k])) {
                $vars[$k] = $v;
            }
        }

        $storages = $this->getModelStorages($doc);

        foreach ($storages as $k => $definition) {
            if (!$this->isPopulableModelProperty($doc, $k, $options)) {
                continue;
            }
            $doc->$k = $this->saveStorageValue($doc->$k, $definition, $vars);
        }

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    protected function populateStorages($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        unset($options);

        $storages = $this->getModelStorages($doc);

        foreach ($storages as $k => $definition) {
            if (isset($doc->$k)) {
                $doc->$k = $this->readStorageValue($doc->$k);
            }
            unset($definition);
        }

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     *
     * @throws \Exception
     */
    protected function triggerRefreshes($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        if (!isset($options['operation'])) {
            return $doc;
        }

        foreach ($this->getModelRefreshablePropertiesByOperation($doc, $options['operation']) as $property) {
            $type = $this->getModelPropertyType($doc, $property);
            switch ($type['type']) {
                case "DateTime<'c'>":
                    $doc->$property = new \DateTime();
                    break;
                default:
                    throw $this->createUnexpectedException("Unable to refresh model property '%s': unsupported type '%s'", $property, $type);
            }
        }

        return $doc;
    }
    /**
     * @param mixed $doc
     * @param array $options
     *
     * @return mixed
     */
    protected function convertScalarProperties($doc, $options = [])
    {
        if (!is_object($doc)) {
            return $doc;
        }

        $types = $this->getModelTypes($doc);

        foreach ($types as $property => $type) {
            if (!$this->isPopulableModelProperty($doc, $property, ['populateNulls' => false] + $options)) {
                continue;
            }
            switch ($type['type']) {
                case "DateTime<'c'>":
                    if ('' === $doc->$property) {
                        $doc->$property = null;
                    }
                    break;
            }
        }

        return $doc;
    }
    /**
     * @param array $definition
     * @param mixed $data
     *
     * @return string
     *
     * @throws \Exception
     */
    protected function generateValue($definition, $data)
    {
        return $this->getGeneratorService()->generate($definition['type'], is_object($data) ? (array) $data : $data);
    }
    /**
     * @param mixed $value
     * @param array $definition
     * @param mixed $vars
     *
     * @return string
     *
     * @throws \Exception
     */
    protected function saveStorageValue($value, $definition, $vars)
    {
        $key = $definition['key'];

        if (0 < preg_match_all('/\{([^\}]+)\}/', $key, $matches)) {
            foreach ($matches[1] as $i => $match) {
                $key = str_replace($matches[0][$i], isset($vars[$match]) ? $vars[$match] : null, $key);
            }
        }

        $this->getStorageService()->save($key, $value);

        return $key;
    }
    /**
     * @param mixed $key
     *
     * @return mixed
     *
     * @throws \Exception
     */
    protected function readStorageValue($key)
    {
        return $this->getStorageService()->read($key);
    }
    /**
     * @param array $options
     *
     * @return object
     */
    protected function createModelInstance(array $options)
    {
        $class = $options['model'];

        return new $class();
    }
    /**
     * @param array $data
     * @param mixed $doc
     * @param array $options
     *
     * @return Object
     */
    protected function mutateArrayToObject($data, $doc, array $options = [])
    {
        $embeddedReferences = $this->getModelEmbeddedReferences($doc);
        $embeddedReferenceLists = $this->getModelEmbeddedReferenceLists($doc);
        $cachedLists = $this->getModelCachedLists($doc);
        $types = $this->getModelTypes($doc);

        foreach ($data as $k => $v) {
            if (!property_exists($doc, $k)) {
                continue;
            }
            if (isset($embeddedReferences[$k])) {
                $v = $this->mutateArrayToObject($v, $this->createModelInstance(['model' => $embeddedReferences[$k]['class']]), $options);
            }
            if (isset($embeddedReferenceLists[$k])) {
                $tt = isset($embeddedReferenceLists[$k]['class']) ? $embeddedReferenceLists[$k]['class'] : (isset($types[$k]) ? $types[$k]['type'] : null);
                if (null !== $tt) {
                    $tt = preg_replace('/^array<([^>]+)>$/', '\\1', $tt);
                }
                if (!is_array($v)) {
                    $v = [];
                }
                $subDocs = [];
                foreach ($v as $kk => $vv) {
                    $subDocs[$kk] = $this->mutateArrayToObject($vv, $this->createModelInstance(['model' => $tt]), $options);
                }
                $v = $subDocs;
            }
            if (isset($cachedLists[$k])) {
                $tt = isset($cachedLists[$k]['class']) ? $cachedLists[$k]['class'] : (isset($types[$k]) ? $types[$k]['type'] : null);
                if (null !== $tt) {
                    $tt = preg_replace('/^array<([^>]+)>$/', '\\1', $tt);
                }
                if (!is_array($v)) {
                    $v = [];
                }
                $subDocs = [];
                foreach ($v as $kk => $vv) {
                    $subDocs[$kk] = $this->mutateArrayToObject($vv, $this->createModelInstance(['model' => $tt]), $options);
                }
                $v = $subDocs;
            }
            if (isset($types[$k])) {
                switch (true) {
                    case 'DateTime' === substr($types[$k]['type'], 0, 8):
                        $data = $this->revertDocumentMongoDateWithTimeZoneFieldToDateTime($data, $k);
                        $v = $data[$k];
                }
                $doc->$k = $v;
            } else {
                $doc->$k = $v;
            }
        }

        $doc = $this->populateStorages($doc);

        return $doc;
    }
    /**
     * @param array  $data
     * @param string $fieldName
     *
     * @return array
     *
     * @throws \Exception
     */
    protected function convertDataDateTimeFieldToMongoDateWithTimeZone($data, $fieldName)
    {
        if (!isset($data[$fieldName])) {
            throw $this->createRequiredException("Missing date time field '%s'", $fieldName);
        }

        if (null !== $data[$fieldName] && !$data[$fieldName] instanceof \DateTime) {
            throw $this->createRequiredException("Field '%s' must be a valid DateTime", $fieldName);
        }

        /** @var \DateTime $date */
        $date = $data[$fieldName];

        $data[$fieldName] = new \MongoDate($date->getTimestamp());
        $data[sprintf('%s_tz', $fieldName)] = $date->getTimezone()->getName();

        return $data;
    }
    /**
     * @param array  $doc
     * @param string $fieldName
     *
     * @return array
     *
     * @throws \Exception
     */
    protected function revertDocumentMongoDateWithTimeZoneFieldToDateTime($doc, $fieldName)
    {
        if (!isset($doc[$fieldName])) {
            throw $this->createRequiredException("Missing mongo date field '%s'", $fieldName);
        }

        if (!isset($doc[sprintf('%s_tz', $fieldName)])) {
            $doc[sprintf('%s_tz', $fieldName)] = date_default_timezone_get();
        }

        if (!$doc[$fieldName] instanceof \MongoDate) {
            throw $this->createMalformedException("Field '%s' must be a valid MongoDate", $fieldName);
        }

        /** @var \MongoDate $mongoDate */
        $mongoDate = $doc[$fieldName];

        $date = new \DateTime(sprintf('@%d', $mongoDate->sec), new \DateTimeZone('UTC'));

        $doc[$fieldName] = date_create_from_format(
            \DateTime::ISO8601,
            preg_replace('/(Z$|\+0000$)/', $doc[sprintf('%s_tz', $fieldName)], $date->format(\DateTime::ISO8601))
        );

        unset($doc[sprintf('%s_tz', $fieldName)]);

        return $doc;
    }
}
<file_sep><?php

/*
 * This file is part of the VELOCITY package.
 *
 * (c) PHPPRO <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Velocity\Bundle\ApiBundle\Tests\Service;

use PHPUnit_Framework_TestCase;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\DependencyInjection\Definition;
use Velocity\Bundle\ApiBundle\Service\VelocityService;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Velocity\Bundle\ApiBundle\Tests\Mock\ContainerBuilderMock;

/**
 * @author <NAME> <<EMAIL>>
 */
class VelocityServiceTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var VelocityService
     */
    protected $s;
    /**
     * @var ContainerBuilder
     */
    protected $containerMock;
    /**
     *
     */
    public function setUp()
    {
        $this->s = new VelocityService(new AnnotationReader());
    }
    /**
     * @group unit
     * @group velocity
     */
    public function testSimpleModelClasses()
    {
        $d = new Definition();

        $this->s->loadClassesMetaData([
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model1',
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model2',
        ], $d);

        $this->assertEquals(
            [
                ['addModel', ['Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model1', ['id' => 'm1']]],
                ['addModel', ['Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model2', ['id' => 'm2']]],
            ],
            $d->getMethodCalls()
        );
    }
    /**
     * @group unit
     * @group velocity
     */
    public function testModelClassesWithAnnotatedMethods()
    {
        $d = new Definition();

        $this->s->loadClassesMetaData([
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\ModelWithAnnotatedMethods',
        ], $d);
        $addedMethods = $d->getMethodCalls();
        $this->assertCount(2, $addedMethods);
        $this->assertEquals(
            ['addModel', ['Velocity\\Bundle\\ApiBundle\\Tests\\Model\\ModelWithAnnotatedMethods', ['id' => 'modelWithAnnotatedMethods']]],
            $addedMethods[0]
        );
        list($sourceClass, $sourceMethod) = $addedMethods[1];
        $this->assertEquals('addSdkMethod', $sourceClass);
        $this->assertContains('sdkMethod', $sourceMethod);
        $this->assertContains('Velocity\\Bundle\\ApiBundle\\Tests\\Model\\ModelWithAnnotatedMethods', $sourceMethod);
    }
    /**
     * @group unit
     * @group velocity
     */
    public function testModelClassesWithAnnotatedProperties()
    {
        $d = new Definition();

        $this->s->loadClassesMetaData([
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\ModelWithAnnotatedProperties',
        ], $d);
        $addedMethods = $d->getMethodCalls();
        $this->assertCount(3, $addedMethods);
        $this->assertEquals(
            ['addModel', ['Velocity\\Bundle\\ApiBundle\\Tests\\Model\\ModelWithAnnotatedProperties', ['id' => 'modelWithAnnotatedProperties']]],
            $addedMethods[0]
        );
        list($sourceClass, $sourceProperty) = $addedMethods[1];
        $this->assertEquals('addModelPropertyEmbeddedReference', $sourceClass);
        $this->assertContains('embeddedReference', $sourceProperty);

        list($sourceClass, $sourceProperty) = $addedMethods[2];
        $this->assertEquals('addModelPropertyId', $sourceClass);
        $this->assertContains('id', $sourceProperty);
    }
    /**
     * @group unit
     */
    public function testIsVelocityAnnotatedClass()
    {
        $this->assertTrue($this->s->isVelocityAnnotatedClass('Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model1'));
        $this->assertTrue($this->s->isVelocityAnnotatedClass('Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model2'));
        $this->assertFalse($this->s->isVelocityAnnotatedClass(__CLASS__));
    }
    /**
     * @group unit
     */
    public function testFindVelocityAnnotatedClassesInDirectory()
    {
        $classes = $this->s->findVelocityAnnotatedClassesInDirectory(__DIR__.'/../Model');

        $expected = [
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model1',
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\Model2',
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\ModelWithAnnotatedMethods',
            'Velocity\\Bundle\\ApiBundle\\Tests\\Model\\ModelWithAnnotatedProperties',
        ];

        $this->assertEquals($expected, $classes);
        $this->assertEquals(count($expected), count($classes));
    }
    /**
     * @group unit
     */
    public function testAnalyzeTags()
    {
        $container = new ContainerBuilderMock();
        $this->s->analyzeTags($container);
        $this->assertGreaterThan(10, count($container->getDefinitions()));
    }
}
