zend framework 2 doctrine hydration strategy - hydrate method never called - php

I am currently working on a project involving Zend Framework 2 and Doctrine.
I am implementing a Zend\Form and using the DoctrineORMModule\Stdlib\Hydrator\DoctrineEntity to extract and hydrate data from/to the database.
In much of the tutorials I have read, it is ideal to implement the Zend\Stdlib\Hydrator\Strategy\StrategyInterface when you need to convert a specific value before hydrating an object (in my example a date time value string which is an issue when using Doctrine). However, despite my best efforts to implement this, it seems only that the extract() method in my hydration strategy is called, never the hydrate() method. EVER!
To provide a code example, this is what I am doing - I have shortened some aspects of the code for brevity;
// Service
public function getProposerForm() {
// get required classes from service manager
$proposerEntity = $this->getServiceManager()->get('tourers_entity_proposer');
$entityManager = $this->getServiceManager()->get('Doctrine\ORM\EntityManager');
$formManager = $this->getServiceManager()->get('FormElementManager');
$proposerFieldset = $formManager->get('tourers_form_proposer_fieldset');
$proposerForm = $formManager->get('tourers_form_proposal');
$proposerFieldset->setUseAsBaseFieldset(true);
$proposerForm->add($proposerFieldset);
$proposerForm->get('submit')->setValue('Continue');
$proposerForm->bind($proposerEntity);
return $proposerForm;
}
.
// Controller
public function proposerAction() {
// grab the form from the form service
$formService = $this->getServiceLocator()->get('tourers_service_forms');
$form = $formService->getProposerForm();
if (true === $this->getRequest()->isPost()) {
$form->setData($this->getRequest()->getPost());
if (true === $form->isValid()) {
$proposerEntity = $form->getData();
$encryptedPolicyId = $formService->saveProposerForm($proposerEntity, $policyId);
return $this->redirect()->toRoute('tourers/proposal/caravan',array('policyid' => $encryptedPolicyId));
} else {
$errors = $form->getMessages();
var_dump($errors);
}
}
// view
return new ViewModel(array(
'form' => $form
,'policyid' => $policyId
)
);
}
.
// Form
class ProposerFieldset extends Fieldset implements InputFilterProviderInterface, ObjectManagerAwareInterface
{
/**
* #var Doctrine\ORM\EntityManager
*/
private $objectManager;
/**
* #return Zend\Form\Fieldset
*/
public function init()
{
// set name
parent::__construct('Proposer');
// set the hydrator to the domain object
$hydrator = new DoctrineEntity($this->objectManager,true);
$hydrator->addStrategy('proposerDateOfBirth',new DateStrategy);
$this->setHydrator($hydrator);
// other form elements below here including proposerDateOfBirth
$minDate = date('dd\/mm\/yyyy',strtotime('-100 years'));
$maxDate = date('dd\/mm\/yyyy',strtotime('-16 years'));
$this->add(array(
'name' => 'proposerDateOfBirth'
,'type' => 'Zend\Form\Element\Date'
,'attributes' => array(
'class' => 'form-control'
,'id' => 'proposerDateOfBirth'
,'placeholder' => 'dd/mm/yyyy'
,'min' => $minDate
,'max' => $maxDate
,'data-date-format' => 'dd/mm/yyyy'
),
'options' => array(
'label' => 'Date of Birth',
)
));
}
}
.
// Hydrator Strategy
namespace Tourers\Hydrator\Strategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class DateStrategy implements StrategyInterface {
/**
* (non-PHPdoc)
* #see Zend\Stdlib\Hydrator\Strategy.StrategyInterface::extract()
*/
public function extract($value) {
var_dump($value . ' extracted'); // GETS CALLED
return $value;
}
/**
* (non-PHPdoc)
* #see Zend\Stdlib\Hydrator\Strategy.StrategyInterface::hydrate()
*/
public function hydrate($value) {
var_dump($value . ' hydrated'); // NEVER CALLED
return $value;
}
}

I also discovered this behaviour. As yet I am still lost as to why my custom strategy was not being called.
The Strategies are applied inside the hydrateValue() and extractValue() functions, so its necessary for these functions to be called by the hydrate() and extract() functions in order for Strategies or Custom Strategies to be applied.
My problem was evident in the hydrateByReference() function inside DoctrineModule\Stdlib\Hydrator\DoctrineObject.
It seems that $this->hydrateValue(...) is only called the field has "associations". I do not yet know what these "associations" are, or what I am doing wrong for them to not exist in my data.
When I compared it to the extractByReference() function, I noticed that it always calls $this->extractValue() and does not require any "associations".
In my application, I had already implemented a custom Hydrator class. This means that when I create a form, the Hydrator and Strategies are automatically applied.
Inside my Form's init() function, I assign the custom Hydrator.
Inside my Custom Hydrator's __construct(), I add the strategies, and Custom Strategies.
So all I needed to do was to override hydrateByReference(...) in my Custom Hydrator to solve the problem. The example is below.
NOTES:
You may also need to override hydrateByValue(...) if you use "by value" hydration.
My solution may break the functionality of "associations".
My Custom Hydrator class:
class maintenance
extends DoctrineHydrator
{
/**
* Constructor
*
* #param ObjectManager $objectManager The ObjectManager to use
*/
public function __construct($objectManager)
{
/*
* Just call the parent class.
*/
parent::__construct($objectManager,
'Maintenance\Entity\maintenance', // The FQCN of the hydrated/extracted object
false // If set to true, hydrator will always use entity's public API
);
/*
* Now set up our strategies, and attach them
* to the appropriate fields.
*/
$this->addStrategy('purchasedate', new TIGDateStrategy());
$this->addStrategy('maintexpirydate', new TIGDateStrategy());
}
/**
* SF Modification, to ensure we call this->hydrateValue on
* all values before doing anything else with the data.
* This way, we convert the data first, before trying to
* store it in a DoctrineEntity.
*
* Hydrate the object using a by-reference logic (this means that values are modified directly without
* using the public API, in this case setters, and hence override any logic that could be done in those
* setters)
*
* #param array $data
* #param object $object
* #return object
*/
protected function hydrateByReference(array $data, $object)
{
$object = $this->tryConvertArrayToObject($data, $object);
$metadata = $this->metadata;
$refl = $metadata->getReflectionClass();
foreach ($data as $field => $value) {
// Ignore unknown fields
if (!$refl->hasProperty($field)) {
continue;
}
// SF Mod
$value = $this->hydrateValue($field, $value);
// End SF Mod
$value = $this->handleTypeConversions($value, $metadata->getTypeOfField($field));
$reflProperty = $refl->getProperty($field);
$reflProperty->setAccessible(true);
if ($metadata->hasAssociation($field)) {
$target = $metadata->getAssociationTargetClass($field);
if ($metadata->isSingleValuedAssociation($field)) {
$value = $this->toOne($target, $this->hydrateValue($field, $value));
$reflProperty->setValue($object, $value);
} elseif ($metadata->isCollectionValuedAssociation($field)) {
$this->toMany($object, $field, $target, $value);
}
} else {
$reflProperty->setValue($object, $value);
}
}
return $object;
}
}

Related

Mockery\Exception\BadMethodCallException: Method Mockery_0__does not exist on this mock object

When running phpunit I get the following error:
Mockery\Exception\BadMethodCallException: Method Mockery_0__Tests_Unit_App_Utils_Blueprints_BlueprintRuleUtil::executeActiveBlueprintRules() does not exist on this mock object
The test method that is failing is:
public function testExecuteActiveBlueprintRulesShouldExecuteDefaultOutsourceCostRule()
{
Strategy::fake();
Strategy::create(['id' => 1]);
$createdStrategy = Strategy::getCreatedModel();
/**
* We can use a partial mock for BlueprintRuleUtil so we can mock specific methods
* later on using shouldReceive while still being able to call regular methods on the class
* including the one we are testing in this test case.
*/
$blueprint_rule_util_partial_mock = \Mockery::mock(App\Utils\Blueprints\BlueprintRuleUtil::class)->makePartial();
/**
* For method chaining with the DB facade
* we want to use mockery to mock each chained method
* and use andReturnSelf() to return an instance of the
* returned value until we reach the last method call in the chain.
*
* Then we can return the final result that we should be expecting
* which is likely a stdClass object as a query result.
*/
$active_blueprint_rule = new stdClass();
$active_blueprint_rule->id = 1001;
DB::shouldReceive("table")
->with('blueprint_rules')
->once()->andReturnSelf();
DB::shouldReceive("where")
->with('is_active', true)
->andReturnSelf();
DB::shouldReceive("get")
->andReturn([$active_blueprint_rule]);
$blueprint_rule_util_partial_mock->shouldReceive('addDefaultOutsourceCostToStrategy')
->withAnyArgs();
$blueprint_rule_util_partial_mock->shouldReceive('shouldAddDefaultOutsourceCost')->withAnyArgs()->andReturn(true);
$blueprint_rule_util_partial_mock->executeActiveBlueprintRules($createdStrategy);
Strategy::stopFaking();
}
Here is the full test class I am using:
namespace Tests\Unit;
/**
* Need to use built in laravel testcase instead of PHPUnit testcase to have access to instance variables like app for mocking
*/
use Tests\TestCase;
use App\Strategy;
use App\Pillar;
use App\Utils\Blueprints\BlueprintRuleUtil;
use DB;
use stdClass;
class BlueprintRuleUtilTest extends TestCase
{
/**
* Test to make sure outsource cost will not be added
* with a certain blueprint type and pillars.
*/
public function testShouldAddDefaultSnowflakeOutsourceCostWillReturnFalse()
{
Strategy::fake();
Pillar::fake();
Strategy::create(['id' => 1]);
// test core business first
Pillar::create(['id' => 1, 'slug' => 'core-new-business']);
$createdStrategy = Strategy::getCreatedModel();
// set the pillar to be the created model so it can grab it's pillar property later on using a relation
$createdStrategy->pillar = Pillar::getCreatedModel();
$blueprint_rule_util = new BlueprintRuleUtil();
$this->assertFalse($blueprint_rule_util->shouldAddDefaultOutsourceCost($createdStrategy));
// test service expansion by updating the pillar
$createdStrategy->pillar->slug = 'service-expansion';
$this->assertFalse($blueprint_rule_util->shouldAddDefaultOutsourceCost($createdStrategy));
// test with blueprint type set to project and valid pillar slug
$createdStrategy->pillar->slug = 'resign';
$createdStrategy->type = 'project';
$this->assertFalse($blueprint_rule_util->shouldAddDefaultOutsourceCost($createdStrategy));
Strategy::stopFaking();
Pillar::stopFaking();
}
/**
* Test given a valid slug and project type that the
* Snowflake default outsource cost is added
*/
public function testShouldAddDefaultSnowflakeOutsourceCostWillReturnTrue()
{
Strategy::fake();
Pillar::fake();
Strategy::create(['id' => 1]);
$blueprint_rule_util = new BlueprintRuleUtil();
// test resign which is a valid pillar
Pillar::create(['id' => 1, 'slug' => 'resign']);
$createdStrategy = Strategy::getCreatedModel();
// test with valid type retainer
$createdStrategy->type = 'retainer';
// set the pillar to be the created model so it can grab it's pillar property later on using a relation
$createdStrategy->pillar = Pillar::getCreatedModel();
$this->assertTrue($blueprint_rule_util->shouldAddDefaultOutsourceCost($createdStrategy));
Strategy::stopFaking();
Pillar::stopFaking();
}
public function testExecuteActiveBlueprintRulesShouldExecuteDefaultOutsourceCostRule()
{
Strategy::fake();
Strategy::create(['id' => 1]);
$createdStrategy = Strategy::getCreatedModel();
/**
* We can use a partial mock for BlueprintRuleUtil so we can mock specific methods
* later on using shouldReceive while still being able to call regular methods on the class
* including the one we are testing in this test case.
*/
$blueprint_rule_util_partial_mock = \Mockery::mock(App\Utils\Blueprints\BlueprintRuleUtil::class)->makePartial();
/**
* For method chaining with the DB facade
* we want to use mockery to mock each chained method
* and use andReturnSelf() to return an instance of the
* returned value until we reach the last method call in the chain.
*
* Then we can return the final result that we should be expecting
* which is likely a stdClass object as a query result.
*/
$active_blueprint_rule = new stdClass();
$active_blueprint_rule->id = 1001;
DB::shouldReceive("table")
->with('blueprint_rules')
->once()->andReturnSelf();
DB::shouldReceive("where")
->with('is_active', true)
->andReturnSelf();
DB::shouldReceive("get")
->andReturn([$active_blueprint_rule]);
$blueprint_rule_util_partial_mock->shouldReceive('addDefaultOutsourceCostToStrategy')
->withAnyArgs();
$blueprint_rule_util_partial_mock->shouldReceive('shouldAddDefaultOutsourceCost')->withAnyArgs()->andReturn(true);
$blueprint_rule_util_partial_mock->executeActiveBlueprintRules($createdStrategy);
Strategy::stopFaking();
}
}
Here is the class I am testing against:
namespace App\Utils\Blueprints;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Strategy;
use App\StrategyCost;
class BlueprintRuleUtil
{
const DEFAULT_OUTSOURCE_COST_RULE_ID = 1001;
/**
* Iterate through the active blueprint rules and execute them based on their name
*
* #param Strategy $strategy
*
*/
public function executeActiveBlueprintRules(Strategy $strategy)
{
if (!is_null($strategy)) {
$active_blueprint_rules = DB::table('blueprint_rules')
->where('is_active', true)
->get();
foreach ($active_blueprint_rules as $active_blueprint_rule) {
if (!is_null($active_blueprint_rule) && $active_blueprint_rule->id === self::DEFAULT_OUTSOURCE_COST_RULE_ID) {
BlueprintRuleUtil::addDefaultOutsourceCostToStrategy($strategy, $active_blueprint_rule);
}
}
}
}
/**
* Given a strategy (blueprint)
*
* Return true if we should add the default outsource cost to it
* false if not based on the blueprint type and pillar
*
* #param Strategy $strategy
*
* #return bool
*/
public function shouldAddDefaultOutsourceCost(Strategy $strategy)
{
$pillar_slugs_to_exclude_for_outsource_costs = [
'core-new-business',
'service-expansion'
];
if (
in_array($strategy->pillar->slug, $pillar_slugs_to_exclude_for_outsource_costs)
|| $strategy->type === 'project'
) {
return false;
}
return true;
}
/**
* Create a StrategyCost entry in the db for the provided strategy
* using the amount from the active blueprint rule to add the default outsource cost.
*
* #param Strategy $strategy
* #param object $active_blueprint_rule
*/
public function addDefaultOutsourceCostToStrategy(Strategy $strategy, object $active_blueprint_rule)
{
if (self::shouldAddDefaultOutsourceCost($strategy)) {
$department_slug = 'data-intelligence';
$department = DB::table('departments')
->where('slug', $department_slug)
->first();
if (!is_null($department)) {
$strategy_cost = new StrategyCost;
$strategy_cost->name = 'Snowflake';
$strategy_cost->department_id = $department->id;
$strategy_cost->strategy_id = $strategy->id;
// set first month to outsource cost
$strategy_cost->month_1 = $active_blueprint_rule->amount;
$strategy_cost->save();
$strategy_cost->strategy->recalculate();
} else {
Log::debug("BlueprintRuleUtil: department $department_slug not found, did not add default outsource cost");
}
}
}
}
I have tried using the full class name path with the namespace as the argument when creating the partial mock. I thought that might fix it because it may not be loading the right object. However that didn't work.
The method name is in the class that I am mocking. I'm not sure why I continue to get this error.

symfony dynamic entity instantiation with php eval

I have this set of entities that we call nomenclators, which basically have an id field and a text-based field. The CRUD operations for these entities are virtually the same, just that in some of them the text field is called state while in others is area... and so on.
Given that, I created this base Controller
class NomenclatorsController extends Controller
{
use ValidatorTrait;
protected function deleteENTITYAction(Request $req, $entityName)
{
$id = $req->request->get('id');
$spService = $this->get('spam_helper');
$resp = $spService->deleteEntitySpam("AplicacionBaseBundle:$entityName", $id);
if ($resp == false)
return new JsonResponse("error.$entityName.stillreferenced", Response::HTTP_FORBIDDEN);
return new JsonResponse('', Response::HTTP_ACCEPTED);
}
protected function listENTITYAction(Request $req, $entityName)
{
$size = $req->query->get('limit');
$page = $req->query->get('page');
$spService = $this->get('spam_helper');
$objectResp = $spService->allSpam("AplicacionBaseBundle:$entityName", $size, $page);
$arrayResp = $spService->spamsToArray($objectResp);
return new JsonResponse($arrayResp, Response::HTTP_ACCEPTED);
}
protected function updateENTITYAction(Request $req, $entityName)
{
$id = $req->request->get('id');
$entity = null;
if (is_numeric($id)) {
$entity = $this->getDoctrine()->getRepository("AplicacionBaseBundle:$entityName")->find($id);
} else if (!is_numeric($id) || $id == null) {
//here comes the evil
eval('$entity=new \\AplicacionBaseBundle\\Entity\\' . $entityName . '();');
$entity->setEliminado(false);
$entity->setEmpresa($this->getUser()->getEmpresa());
}
$this->populateEntity($req->request, $entity);
$errors = $this->validate($entity);
if ($errors)
return new Response(json_encode($errors), Response::HTTP_BAD_REQUEST);
$spamService = $this->get('spam_helper');
$spamService->saveEntitySpam($entity);
}
//Override in children
protected function populateEntity($req, $entity)
{
}
}
So, each time I need to write a controller for one of these nomenclators I extend this NomenclatorsController and works like a charm.
The thing is in the updateENTITYAction I use eval for dynamic instantiation as you can see, but given all I have readed about how bad is eval I am confused now, and even when there is no user interaction in my case I want to know if there is a better way of doing this than eval and if there is any noticiable performance issue when using eval like this.
By the way I am working in a web json api with symfony and extend.js, which means no view is generated in the server,my controllers match a route and receive a sort of request params and do the work.
I've done something similar in the past. Since you are extending a base class using specific classes for each entity you can instance your entity from the controller that extends NomenclatorsController.
If one of your entities is called Foo you will have a FooController that extends NomenclatorsController. Just overwrite updateENTITYAction and pass back needed variables.
An example:
<?php
use AplicacionBaseBundle\Entity\Foo as Item;
class FooController extends NomenclatorsController
{
/**
* Displays a form to edit an existing item entity.
*
* #Route("/{id}/edit")
* #Method({"GET", "POST"})
* #Template()
* #param Request $request
* #param Item $item
* #return array|bool|\Symfony\Component\HttpFoundation\RedirectResponse
*/
public function updateENTITYAction(Request $request, Item $item)
{
return parent::updateENTITYAction($request, $item);
}
}
This way you are sending directly the entity to NomenclatorController and you don't even need to know the entityName.
Humm I'll me too advise you to avoid the eval function. It's slow and a bad practice.
What you want here is the factory pattern,
You could define a service to create the entites for you
#app/config/services.yml
app.factory.nomenclators:
class: YourNamespace\To\NomenclatorsFactory
And your factory might be like this
namespace YourNamespace\To;
use YourNamespace\To\Entity as Entites;
class NomenclatorsFactory {
// Populate this array with all your Nomenclators class names with constants OR with reflection if you have many
private $allowedNomemclators = [];
/**
* #param $entityName
* #return NomenclatorsInterface|false
*/
public function getEntity($entityName)
{
if(!is_string($entityName) || !in_array($entityName, $this->allowedNomemclators)) {
// Throw exception or exit false
return false;
}
return new $entityName;
}
}
Then you have to create the NomenclatorsInterface and define in it all the common methods between all your entities. Moreover define one more method getSomeGoodName, the job of this method is to return the good property (area or state)
With this structure your controller can only instances the Nomenclators entities and don't use anymore the eval evil method haha
Moreover you don't have to worry about about the state and area property
Ask if something isn't clear :D
I hope it help !

Options for "AutoMapping" from a DTO / Domain Entity with protected properties to "View Model" with public properties in PHP 5.x

Looking for "AutoMapper"-like feature/framework/pattern for taking data from a Doctrine 2 domain entity / DTO and mapping the protected properties on that entity to matching public properties on a View Model.
$userEntity = $this-em->find(User::class, 1);
$userViewModel = AutoMapper->Map($userEntity, new UserViewModel());
Where the only significant difference between User and UserViewModel is that User contains get/set accessors with protected backing fields (per doctrine's instructions), whereas UserViewModel contains public properties that match in name [a subset of] the protected backing fields on User.
An thoughts on how to accomplish this? (preferably without reflection)
Note that the domain entity has public get accessor, so the solution can leverage those accessors.
Came up with my own crude, yet effective, bare-bones implementation of AutoMapper for PHP to solve this problem for me. This method will map from public properties or public getters (convention based naming) to public properties on the target entity.
Hope this helps someone out:
class Mapper
{
/**
* This method will attempt to source all public property values on $target from $source.
*
* By convention, it'll look for properties on source with the same name,
* .. and will fallback camel-cased get/set accessors to use.
*
* Note that underscores in properties will be translated to capital letters in camel-cased getters.
*
* #param $source object
* #param $target object
* #return object
* #throws Exception
*/
public static function Map($source, $target)
{
$targetProperties = get_object_vars($target);
$sourceProperties = get_object_vars($source);
foreach ($targetProperties as $name => $value)
{
//
// match properties
//
$matchingSourcePropertyExists = array_key_exists($name, $sourceProperties);
if ($matchingSourcePropertyExists)
{
$target->{$name} = $source->{$name};
continue;
}
//
// fall back on matching by convention-based get accessors
//
$sourceMethods = get_class_methods(get_class($source));
$getterName = "get" . self::convertToPascalCase($name);
$matchingGetAccessorExists = in_array($getterName, $sourceMethods);
if ($matchingGetAccessorExists)
{
$target->{$name} = $source->{$getterName}();
continue;
}
//
// if we ever fail to map an entity on the target, throw
//
$className = get_class($target);
throw new Exception("Could not auto-map property $name on $className.");
}
return $target;
}
/**
* Converts this_kind_of_string into ThisKindOfString.
* #param $value string
* #return string
*/
private static function convertToPascalCase($value)
{
$value[0] = strtoupper($value[0]);
$func = create_function('$c', 'return strtoupper($c[1]);');
return preg_replace_callback('/_([a-z])/', $func, $value);
}
}
My implementation of automapping using Symfony's components
<?php
declare(strict_types=1);
namespace App\ApiResource\Utils;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
class GenericMapper
{
public static function map(object $source, object $target): void
{
$reflectionExtractor = new ReflectionExtractor();
$sourceProperties = $reflectionExtractor->getProperties($source::class);
$propertyAccessor = PropertyAccess::createPropertyAccessor();
foreach ($sourceProperties as $propertyName) {
if ($propertyAccessor->isWritable($target, $propertyName) && $propertyAccessor->isReadable($source, $propertyName)) {
$propertyAccessor->setValue($target, $propertyName, $propertyAccessor->getValue($source, $propertyName));
}
}
}
}

Callback on serializer Symfony

I'm running Symfony 2.7 and I'm trying output an object (Doctrine entity) as JSON.
When I'm normalizing the object I want to convert some of it's values. To do this I found the "setCallbacks" method in the documentation but I'm kinda stumped on how to apply it to my case.
Is there any way to call the "setCallbacks" method on the normalizer that is set when calling Symfonys serializer service?
Here is a short example of what I'm trying to achieve:
//ExampleController.php
public function getJSONOrderByIdAction($id) {
$serializer = $this->get('serializer');
$normalizer = $serializer->getNormalizer(); // <- This is what I'm unable to do
$dateTimeToString = function ($dateTime) {
return $dateTime instanceof \DateTime ? $dateTime->format(\DateTime::ISO8601) : '';
};
$normalizer->setCallbacks(['time' => $dateTimeToString]);
$order = $this->getDoctrine()->find("AppBundle:Order", $id);
return new JsonResponse(["order" => $serializer->normalize($order, null, ["groups" => ["public"]])]);
}
I'm aware that most people have switched to the JMS serializer. It just seems as if the built in serializer should be able to handle what I'm trying to achieve.
The default Serializer service is created during dependency injection phase, and the Serializer interface do not allow editing of (full) retrieval of normalizers.
I think you have (at least) three choice here:
add your custom normalizer to the default Serializer service
add NormalizableInterface to your entities
create a new Serializer service (or a local object as suggested by the docs) as you were trying to do.
I think in your scenario, case 1 is preferred (since 2 becomes boring pretty fast).
I would do something like this; first create a custom Normalizer
<?php
namespace AppBundle;
class DateTimeNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
{
/**
* {#inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
return $object->format(\DateTime::ISO8601);
}
/**
* {#inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
return new $class($data);
}
/**
* Checks if the given class is a DateTime.
*
* #param mixed $data Data to normalize.
* #param string $format The format being (de-)serialized from or into.
*
* #return bool
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \DateTime;
}
/**
* Checks if the given class is a DateTime.
*
* #param mixed $data Data to denormalize from.
* #param string $type The class to which the data should be denormalized.
* #param string $format The format being deserialized from.
*
* #return bool
*/
public function supportsDenormalization($data, $type, $format = null)
{
$class = new \ReflectionClass($type);
return $class->isSubclassOf('\DateTime');
}
}
Then register it to your services:
# app/config/services.yml
services:
datetime_normalizer:
class: AppBundle\DateTimeNormalizer
tags:
- { name: serializer.normalizer }
My own solution
Following the advice from giosh94mhz I tried switching to JMS Serializer but ended up going back to Symfonys serializer.
JMS Serializer presented it's own issues and while searching for answers for those I stumbled upon a blog post by Thomas Jarrand that did an excellent job explaining how to make and implement your own normalizers in Symfony.
You can use callback normalizer of K. Dunglas component.
You can see that in ObjectNormalizer (in normalize method)
if (isset($this->callbacks[$attribute])) {
$attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue);
}
This mean that you must use in callback array key the name of property you wan't to normalize.
For example in my entity I have field named "name" of type "pgarray" (like array for postgresql). I wan't to normalize this data. Instead array I want a string.
/**
* $object represent the property "name" because callback is attached to name property (setCallback)
*/
$nameCallback = function ($object, $outerObject = null) {
return $object[0];
};
$this->normalizer->setCallbacks(['name' => $dateCallback]);
Just remember since Symfony 4.2 you must use $context in DI to use callback.
In my opinion, you seem to be trying to over-complicate things. Here's the approach I've taken when I needed to serialize my entities as JSON:
PHP 2.5 and above allows you to implement the jsonSerialize method on your objects and just call json_encode directly on your object.
If you are still using PHP 2.4, you just need to manually call jsonSerialize() on your objects.
For example:
/**
* #ORM\Entity
*/
class MyEntity {
...
public function jsonSerialize() {
$data = array("foo" => $this->bar());
// add other data here ...
return $data
}
}
And then in calling code:
// for PHP 2.5 and up:
$normalized = json_encode($myEntityInstance);
// for PHP 2.4 and below
$normalized = json_encode($myEntityInstance->jsonSerialize());

How to use dependency injection in Zend Framework?

Currently I am trying to learn the Zend Framework and therefore I bought the book "Zend Framework in Action".
In chapter 3, a basic model and controller is introduced along with unit tests for both of them. The basic controller looks like this:
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$this->view->title = 'Welcome';
$placesFinder = new Places();
$this->view->places = $placesFinder->fetchLatest();
}
}
Places is the model class that fetches the latest places from the database. What bugs me here: how should I test the IndexController in isolation? As the reference to the Places class is "hardcoded", I cant inject any stubs or mocks in IndexController.
What I would rather like to have is something like this:
class IndexController extends Zend_Controller_Action
{
private $placesFinder;
// Here I can inject anything: mock, stub, the real instance
public function setPlacesFinder($places)
{
$this->placesFinder = $places;
}
public function indexAction()
{
$this->view->title = 'Welcome';
$this->view->places = $this->placesFinder->fetchLatest();
}
}
The first code sample I posted is most definately NOT unit test friendly as IndexController cannot be tested in isolation. The second one is much better. Now I just need some way to inject the model instances into the controller objects.
I know that the Zend Framework per se has no component for dependency injection. But there are some good frameworks out there for PHP, can any be used together with Zend Framework? Or is there some other way to do this in Zend Framework?
Logic to models
First of all, it's worth to mention, that controllers should need only functional tests, though all the logic belongs to models.
My implementation
Here is an excerpt from my Action Controller implementation, which solves the following problems:
allows inject any dependency to actions
validates the action parameters, e.g. you may not pass array in $_GET when integer is expected
My full code allows also to generate canonical URL (for SEO or unique page hash for stats) based or required or handled action params. For this, I use this abstract Action Controller and custom Request object, but this is not the case we discuss here.
Obviously, I use Reflections to automatically determine action parameters and dependency objects.
This is a huge advantage and simplifies the code, but also has an impact in performance (minimal and not important in case of my app and server), but you may implement some caching to speed it up. Calculate the benefits and the drawbacks, then decide.
DocBlock annotations are becoming a pretty well known industry standard, and parsing it for evaluation purposes becomes more popular (e.g. Doctrine 2). I used this technique for many apps and it worked nicely.
Writing this class I was inspired by Actions, now with params! and Jani Hartikainen's blog post.
So, here is the code:
<?php
/**
* Enchanced action controller
*
* Map request parameters to action method
*
* Important:
* When you declare optional arguments with default parameters,
* they may not be perceded by optional arguments,
* e.g.
* #example
* indexAction($username = 'tom', $pageid); // wrong
* indexAction($pageid, $username = 'tom'); // OK
*
* Each argument must have #param DocBlock
* Order of #param DocBlocks *is* important
*
* Allows to inject object dependency on actions:
* #example
* * #param int $pageid
* * #param Default_Form_Test $form
* public function indexAction($pageid, Default_Form_Test $form = null)
*
*/
abstract class Your_Controller_Action extends Zend_Controller_Action
{
/**
*
* #var array
*/
protected $_basicTypes = array(
'int', 'integer', 'bool', 'boolean',
'string', 'array', 'object',
'double', 'float'
);
/**
* Detect whether dispatched action exists
*
* #param string $action
* #return bool
*/
protected function _hasAction($action)
{
if ($this->getInvokeArg('useCaseSensitiveActions')) {
trigger_error(
'Using case sensitive actions without word separators' .
'is deprecated; please do not rely on this "feature"'
);
return true;
}
if (method_exists($this, $action)) {
return true;
}
return false;
}
/**
*
* #param string $action
* #return array of Zend_Reflection_Parameter objects
*/
protected function _actionReflectionParams($action)
{
$reflMethod = new Zend_Reflection_Method($this, $action);
$parameters = $reflMethod->getParameters();
return $parameters;
}
/**
*
* #param Zend_Reflection_Parameter $parameter
* #return string
* #throws Your_Controller_Action_Exception when required #param is missing
*/
protected function _getParameterType(Zend_Reflection_Parameter $parameter)
{
// get parameter type
$reflClass = $parameter->getClass();
if ($reflClass instanceof Zend_Reflection_Class) {
$type = $reflClass->getName();
} else if ($parameter->isArray()) {
$type = 'array';
} else {
$type = $parameter->getType();
}
if (null === $type) {
throw new Your_Controller_Action_Exception(
sprintf(
"Required #param DocBlock not found for '%s'", $parameter->getName()
)
);
}
return $type;
}
/**
*
* #param Zend_Reflection_Parameter $parameter
* #return mixed
* #throws Your_Controller_Action_Exception when required argument is missing
*/
protected function _getParameterValue(Zend_Reflection_Parameter $parameter)
{
$name = $parameter->getName();
$requestValue = $this->getRequest()->getParam($name);
if (null !== $requestValue) {
$value = $requestValue;
} else if ($parameter->isDefaultValueAvailable()) {
$value = $parameter->getDefaultValue();
} else {
if (!$parameter->isOptional()) {
throw new Your_Controller_Action_Exception(
sprintf("Missing required value for argument: '%s'", $name));
}
$value = null;
}
return $value;
}
/**
*
* #param mixed $value
*/
protected function _fixValueType($value, $type)
{
if (in_array($type, $this->_basicTypes)) {
settype($value, $type);
}
return $value;
}
/**
* Dispatch the requested action
*
* #param string $action Method name of action
* #return void
*/
public function dispatch($action)
{
$request = $this->getRequest();
// Notify helpers of action preDispatch state
$this->_helper->notifyPreDispatch();
$this->preDispatch();
if ($request->isDispatched()) {
// preDispatch() didn't change the action, so we can continue
if ($this->_hasAction($action)) {
$requestArgs = array();
$dependencyObjects = array();
$requiredArgs = array();
foreach ($this->_actionReflectionParams($action) as $parameter) {
$type = $this->_getParameterType($parameter);
$name = $parameter->getName();
$value = $this->_getParameterValue($parameter);
if (!in_array($type, $this->_basicTypes)) {
if (!is_object($value)) {
$value = new $type($value);
}
$dependencyObjects[$name] = $value;
} else {
$value = $this->_fixValueType($value, $type);
$requestArgs[$name] = $value;
}
if (!$parameter->isOptional()) {
$requiredArgs[$name] = $value;
}
}
// handle canonical URLs here
$allArgs = array_merge($requestArgs, $dependencyObjects);
// dispatch the action with arguments
call_user_func_array(array($this, $action), $allArgs);
} else {
$this->__call($action, array());
}
$this->postDispatch();
}
$this->_helper->notifyPostDispatch();
}
}
To use this, just:
Your_FineController extends Your_Controller_Action {}
and provide annotations to actions, as usual (at least you already should ;).
e.g.
/**
* #param int $id Mandatory parameter
* #param string $sorting Not required parameter
* #param Your_Model_Name $model Optional dependency object
*/
public function indexAction($id, $sorting = null, Your_Model_Name $model = null)
{
// model has been already automatically instantiated if null
$entry = $model->getOneById($id, $sorting);
}
(DocBlock is required, however I use Netbeans IDE, so the DocBlock is automatically generated based on action arguments)
Ok, this is how I did it:
As IoC Framework I used this component of the symfony framework (but I didnt download the latest version, I used an older one I used on projects before... keep that in mind!). I added its classes under /library/ioc/lib/.
I added these init function in my Bootstrap.php in order to register the autoloader of the IoC framework:
protected function _initIocFrameworkAutoloader()
{
require_once(APPLICATION_PATH . '/../library/Ioc/lib/sfServiceContainerAutoloader.php');
sfServiceContainerAutoloader::register();
}
Next, I made some settings in application.ini which set the path to the wiring xml and allow to disable automatic dependency injection e. g. in unit tests:
ioc.controllers.wiringXml = APPLICATION_PATH "/objectconfiguration/controllers.xml"
ioc.controllers.enableIoc = 1
Then, I created a custom builder class, which extends sfServiceContainerBuilder and put it under /library/MyStuff/Ioc/Builder.php. In this test project I keep all my classes under /library/MyStuff/.
class MyStuff_Ioc_Builder extends sfServiceContainerBuilder
{
public function initializeServiceInstance($service)
{
$serviceClass = get_class($service);
$definition = $this->getServiceDefinition($serviceClass);
foreach ($definition->getMethodCalls() as $call)
{
call_user_func_array(array($service, $call[0]), $this->resolveServices($this->resolveValue($call[1])));
}
if ($callable = $definition->getConfigurator())
{
if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof sfServiceReference)
{
$callable[0] = $this->getService((string) $callable[0]);
}
elseif (is_array($callable))
{
$callable[0] = $this->resolveValue($callable[0]);
}
if (!is_callable($callable))
{
throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
}
call_user_func($callable, $service);
}
}
}
Last, I created a custom controller class in /library/MyStuff/Controller.php which all my controllers inherit from:
class MyStuff_Controller extends Zend_Controller_Action {
/**
* #override
*/
public function dispatch($action)
{
// NOTE: the application settings have to be saved
// in the registry with key "config"
$config = Zend_Registry::get('config');
if($config['ioc']['controllers']['enableIoc'])
{
$sc = new MyStuff_Ioc_Builder();
$loader = new sfServiceContainerLoaderFileXml($sc);
$loader->load($config['ioc']['controllers']['wiringXml']);
$sc->initializeServiceInstance($this);
}
parent::dispatch($action);
}
}
What this basically does is using the IoC Framework in order to initialize the already created controller instance ($this). Simple tests I did seemed to do what I want... let´s see how this performs in real life situations. ;)
It´s still monkey patching somehow, but the Zend Framework doesn´t seem to provide a hook where I can create the controller instance with a custom controller factory, so this is the best I came up with...
I'm currently working on the same question, and after deep research I've decide to use Symfony Dependency Injection component. You can get good info from official website http://symfony.com/doc/current/book/service_container.html.
I've build custom getContainer() method in bootstrap, which resturns now service container, and it simply can be used in controllers like
public function init()
{
$sc = $this->getInvokeArg('bootstrap')->getContainer();
$this->placesService = $sc->get('PlacesService');
}
Here you can find how to do that http://blog.starreveld.com/2009/11/using-symfony-di-container-with.html. But I changed ContainerFactory, because of using Symfony2 component, instead of first version.
You could also just use the PHP-DI ZF bridge: http://php-di.org/doc/frameworks/zf1.html
I know this question is really old but it pops up rather high in search engines when looking for DI in ZF1 so I thought I'd add a solution that doesn't require you to write it all by yourself.
With the Service Manager at Zend Framework 3.
Official Documentation:
https://zendframework.github.io/zend-servicemanager/
Dependencies at your Controller are usually be injected by DI Constructor injector.
I could provide one example, that inject a Factory responsible to create the ViewModel instance into the controller.
Example:
Controller
`
class JsonController extends AbstractActionController
{
private $_jsonFactory;
private $_smsRepository;
public function __construct(JsonFactory $jsonFactory, SmsRepository $smsRepository)
{
$this->_jsonFactory = $jsonFactory;
$this->_smsRepository = $smsRepository;
}
...
}
Creates the Controller
class JsonControllerFactory implements FactoryInterface
{
/**
* #param ContainerInterface $serviceManager
* #param string $requestedName
* #param array|null $options
* #return JsonController
*/
public function __invoke(ContainerInterface $serviceManager, $requestedName, array $options = null)
{
//improve using get method and callable
$jsonModelFactory = new JsonFactory();
$smsRepositoryClass = $serviceManager->get(SmsRepository::class);
return new JsonController($jsonModelFactory, $smsRepositoryClass);
}
}
`
Complete example at https://github.com/fmacias/SMSDispatcher
I hope it helps someone

Categories