I've been playing with the Zend Hydrator class today and just found the Naming strategies for converting the input keys on the fly. But when playing with the MapNamingStrategy in conjunction with the ObjectProperty hydrator, it seems to add properties that didn't initially exist in the object if the input array contained them.
Is there any way to restrict it from adding new properties and only populating/hydrating existing ones in the input object?
Still no response on this - what I ended up doing was using one of two scenarios but it is still not ideal. The first is to use Class Reflection myself to get a list of keys that are accessible or to search for standard name accessors for same. (this, of course, would not find magic method accessors)
The second was to pre-define a map that didn't only include mismatched key->property mappings but also included all the one-to-one (matched) key->property mappings then filter the input using PHP's array functions prior to running the hydration using the map's key/value pairs. But this kind of defeats the purpose of using hydration as by that point in time, I may as well have used a foreach loop instead. And it eliminates any ability to use abstract destinations in that you have to know all potential input/output key->property relationships in advance.
I ended up doing my own implementation of the first method (again, that will not necessarily handle magic method accessors) which looks for public properties and/or public accessors fitting the standard camel-cased setPropertyName()/getPropertyName() accessor methods.:
<?php
/**
* simple object hydrator using class reflection to find publicly accessible properties and/or methods
*
* Created by PhpStorm.
* User: scottw
* Date: 12/12/16
* Time: 12:06 PM
*/
namespace Finao\Util;
class SimpleHydrator
{
/**
* whether to reset the keyMap following each hydration to clear the hydrator for other data/object pairs
*
* #var bool $resetMap
*/
private static $resetMap = true;
/**
* associative array of key mappings between incoming data and object property names/accessors
* #var array $keyMap
*/
private static $keyMap = array();
public static function setKeyMap($map) {
if(self::is_assoc($map))
static::$keyMap = $map;
}
public static function populateObject(&$targetObject, $dataArray)
{
if (self::is_assoc($dataArray) && is_object($targetObject)) {
// step through array elements and see if there are matching properties or methods
try {
foreach ($dataArray as $k => $v) {
$key = $k;
if(self::is_assoc(static::$keyMap) && array_key_exists($k))
$key = static::$keyMap[$k];
// if original value contains an object, try populating it if the associated value is also array
$origVal = self::getObjectPropertyValue($targetObject, $key);
if (is_object($origVal) && self::is_assoc($v)) {
self::populateObject($origVal, $v);
$v = $origVal;
}
$accessor = 'set' . ucfirst($key);
if (in_array($key, self::getObjectPublicProperties($targetObject)))
$targetObject->$key = $v;
elseif (in_array($accessor, self::getObjectPublicMethods($targetObject)))
$targetObject->$accessor($v);
}
} catch (\Exception $d) {
// do something with failures
}
if(static::$resetMap) static::$keyMap = array();
}
return $targetObject;
}
public static function getObjectPropertyValue($object, $property)
{
$objectReflection = new \ReflectionClass($object);
if ($objectReflection->hasProperty($property) && $objectReflection->getProperty($property)->isPublic())
return $object->$property;
else {
$accessor = 'get' . ucfirst($property);
if ($objectReflection->hasProperty($accessor) && $objectReflection->getMethod($accessor)->isPublic())
return $object->$accessor();
}
}
public static function getObjectPublicProperties($object)
{
if (is_object($object)) {
$publicProperties = array();
$objectReflection = new \ReflectionClass($object);
foreach ($objectReflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $p)
array_push($publicProperties, $p->name);
return $publicProperties;
}
}
public static function getObjectPublicMethods($object)
{
if (is_object($object)) {
$publicMethods = array();
$objectReflection = new \ReflectionClass($object);
foreach ($objectReflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $p)
array_push($publicMethods, $p->name);
return $publicMethods;
}
}
/**
* Determine if a variable is an associative array.
*
* #param mixed Input variable
* #return boolean If the input variable is an associative array.
* #see http://us2.php.net/manual/en/function.is-array.php
*/
public static function is_assoc($array) {
return (is_array($array) && 0 !== count(array_diff_key($array, array_keys(array_keys($array)))));
}
}
I eventually added a simple key mapping ability to it. (Note that this has not been rigorously tested and, as the name suggests, is just a simple solution.)
Related
I'm trying to write plugin in Shopware6, it must add some custom values (like deposit) to Product line item. So I wrote my class
class DepositCalculatedPrice extends CalculatedPrice
{
/** #var float $deposit */
protected $deposit;
public function __construct(
float $unitPrice,
float $totalPrice,
CalculatedTaxCollection $calculatedTaxes,
CalculatedTaxCollection $calculatedTaxesNoDeposit,
TaxRuleCollection $taxRules,
int $quantity = 1,
?ReferencePrice $referencePrice = null,
?ListPrice $listPrice = null,
float $deposit = 0,
) {
parent::__construct(
$unitPrice,
$totalPrice,
$calculatedTaxes,
$taxRules,
$quantity,
$referencePrice,
$listPrice
);
$this->deposit = $deposit;
}
public function getDeposit(): float
{
return FloatComparator::cast($this->deposit);
}
}
Then I use it in my Calculator and then in my PriceProcessor. All goes well until I try to submit my order, but then Shopware6 checks field definintion classes in Checkout/Order/Aggregate/OrderLineItem/OrderLineItemDefinition.php, and it checks Price json field against CalculatedPrice, not DepositCalculatedPrice.
So is there any way to resolve it? Maybe I can use somewhere some descendant of OrderLineItemDefinition.php? Or make it not checking field definitions?
I think you need to follow that document https://docs.shopware.com/en/shopware-platform-dev-en/how-to/cart-change-price
I also need to implement custom price applying from an external system, and i exactly did it according to that documentation, so you need to set exactly the price during cart processing, you can do it using your DepositCalculatedPrice value.
I've translation models and I want to run Global query scope that determine the current locale and return the corresponding value upon it or fall back into English if the translation doesn't exist in DB.
I've created a global scope for this purpose and its running good without the ability to fall back into English, so some pages crashes since I'm trying to get property of NULL, and I tried passing some value, but inside the builder I can't determine if the query is going to return null.
How may achieve such thing in Laravel?
my code as follows:
trait WhereLanguage {
/**
* Boot the Where Language trait for a model.
*
* #return void
*/
public static function bootWhereLanguage()
{
static::addGlobalScope(new WhereLanguageScope);
}
}
and the Scope file:
class WhereLanguageScope implements ScopeInterface {
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
*/
public function apply(Builder $builder, Model $model)
{
$this->addWhereLang($builder);
}
/**
* Remove the scope from the given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
*
* #return void
*/
public function remove(Builder $builder, Model $model)
{
$query = $builder->getQuery();
foreach ((array) $query->wheres as $key => $where)
{
// If the where clause is a soft delete date constraint, we will remove it from
// the query and reset the keys on the wheres. This allows this developer to
// include deleted model in a relationship result set that is lazy loaded.
if ($where['column'] == 'lang_id')
{
unset($query->wheres[$key]);
$query->wheres = array_values($query->wheres);
}
}
}
/**
* Extend Builder with custom method.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
*
*/
protected function addWhereLang(Builder $builder)
{
$builder->macro('whereLang', function(Builder $builder)
{
// here 1 is ID for English,
// 48 Arabic, 17 Netherlands...etc
// and It was the App:currentlocale() passed into Language model to determine the ID of current locale.
// but for testing now I hard coded it with ID of 48
$builder->where('lang_id','=','48');
return $builder;
});
}
}
Usage example:
$title = $centre->translations()->whereLang()->first()->name;
Where Centre is my model without localization, and translation is the name of method that handling the relation between Centre & CentreTranslation.
btw I don't want to pass variable obligately.
Here Cycle is a domain class
class Cycle {
int lenght = 42
String[] monitor = new String[length]
static mapping = {
monitor defaultValue:"defaultstrval(length)"
}
def defaultstrval(int length)
{
String[] defaultval =new String[length]
for(int i=0;i<length;i++)
{
defaultval[i]=","
}
return defaultval
}
}
Is Domain class only accept sql function.I really need help with good example.
Rather than using the mapping closure to call your function you can simply call the function from your variable assignment like so
String[] monitor = defaultstravel(length)
I created a sort base module in my ZF2 vendor library. So far everything is working the way I want it to work. I do have a problem. While I am able to extend the base module's controllers, I am unable to access the base service. I am using Doctrine 2 as my database layer.
After implementing the ServiceLocator, I am getting Fatal error: Call to a member function get() on a non-object in my base service file. My BaseService file is shown as below:
namespace MyResource\Service;
use Doctrine\ORM\Mapping as ORM;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class BaseService implements ServiceLocatorAwareInterface
{
/**
* Entity manager instance
*
* #var Doctrine\ORM\EntityManager
*/
protected $_em;
protected $_serviceLocator;
public function __construct()
{
$this->getEntityManager();
}
/**
* Returns an instance of the Doctrine entity manager loaded from the service
* locator
*
* #return Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
if (null === $this->_em) {
$this->_em = $this->getServiceLocator()
->get('doctrine.entitymanager.orm_default');
}
return $this->_em;
}
/**
* Set serviceManager instance
*
* #param ServiceLocatorInterface $serviceLocator
* #return void
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
/**
* Retrieve serviceManager instance
*
* #return ServiceLocatorInterface
*/
public function getServiceLocator()
{
return $this->serviceLocator;
}
}
Can anyone help?
Thanks
1):
Your property is called
protected $_serviceLocator;
but you are assigning your values to
protected $serviceLocator;
2)
Are you creating your Service via DI or the service manager? If you do then the ServiceLocator should be automatically injected for you, if you are creating it manually using the "new" keyword then it will not have the ServiceLocatior attached.
There seems to be a glitch in ZF2 .If you try setting the properties
as below the problem will be fixed. Try like this
foreach ($resultSet as $row) {
$entity = new Countrypages();
$entity->setId($row->id);
$entity->setName($row->name);
$entity->setSnippet($row->snippet);
$entity->setSortorder($row->sortorder);
$entity->setActive($row->active);
$entity->setCreated($row->created);
$entity->setModified($row->modified);
$entity->setFirstname($row->firstname);
$entity->setCreatedby($row->createdby);
$entities[] = $entity;
}
ignore this
foreach ($resultSet as $row) {
$entity = new Countrypages();
$entity->setId($row->id);
->setName($row->name);
->setSnippet($row->snippet);
->setSortorder($row->sortorder);
->setActive($row->active);
->setCreated($row->created);
->setModified($row->modified);
->setFirstname($row->firstname);
->setCreatedby($row->createdby);
$entities[] = $entity;
}
I hope this help you save your time.
You're using use use Zend\ServiceManager\ServiceManagerAwareInterface but you're implementing ServiceLocatorAwareInterface (and there's no "use" statement for that one).
I have a request to create a form filter that has two fields, one a freeform text
and the other a select. The value in the select will determine how to handle the value of the text is turned into a criteria.
I can create a custom addXXXColumnCriteria for either field, but how can I access the other field from within this function?
I suggest you not to use de addXXXColumnCriteria, but overwrite the FormFilter doBuildCriteria (Propel) or doBuildQuery(Doctrine) methods.
I have never used Propel, but I guess that works as good as for Doctrine.
For example:
class yourPropelFormFilter extends anyKindOfSfFormFilterPropel {
public function doBuildCriteria(array $values) {
$criteria = parent::doBuildCriteria($values);
// ... change the criteria behaviour with the $values array (do some print_r to $values to see how the data array is formatted)
return $criteria;
}
}
For Doctrine (remember to use the getRootAlias query method):
class yourDoctrineFormFilter extends anyKindOfSfFormFilterDoctrine {
public function doBuildQuery(array $values) {
$q = parent::doBuildQuery($values);
$rootAlias = $q->getRootAlias();
if(...) {
$q->innerJoin($rootAlias.'.RelationX rx')
->addWhere('rx.value = ?',$values['...']);
}
return $q;
}
}
Please, remember to return the criteria/query modified object!