Create dynamic properties with automapper php? - php

I use automapper component (https://jane.readthedocs.io/en/latest/components/AutoMapper.html)
My source can have array properties with keys and values not known in advance. In the example below for example the title of a document in several languages ['fr' => 'Mon titre', 'en' => 'My title']; Is it possible to set dynamic properties to the target (target is an array) as
$target[title_fr] => Mon titre; $target[title_en] => My title;
I was wondering if we could write something like below (it doesn't work of course) with this type of library:
class SolrDocumentMapperConfiguration implements MapperConfigurationInterface
{
public function getSource(): string
{
return Content::class;
}
public function getTarget(): string
{
return 'array';
}
/**
* #param MapperMetadata $metadata
*/
public function process(MapperGeneratorMetadataInterface $metadata): void
{
$metadata->forMember('rank', static fn (Content $content) => $content->getRank());
$metadata->forMember('title_'.$keyLang, fn (Content $content) {
foreach ($content->getTitles() as $keyLang => $titleLang)
return $titleLang;
}
}
}
Thanks in advance for any help.

Related

How can I make my class variable assignment dynamic?

In the following code, I want to assign an $settings's key, help to a constant class variable value, $To_default. However, I am told Constant expression contains invalid operations. Is there any way I can get around this?
class EmailClass extends PluginBase {
private $To_default = 'scrollout#stackoverflow.com';
protected $settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>$this->To_default, // Constant expression contains invalid operations
),
);
I've tried declaring $To_default in various ways including private const $To_default, static private $To_default, etc. but none worked. $settings is not static, as you can see, so I don't understand why this is a problem.
Don't know deep technical explanation for this but I think this is because normally you initialize properties in the constructor, like so:
class EmailClass
{
private $To_default = 'scrollout#stackoverflow.com'; // not a constant, so better throw it into constructor too
protected $settings;
public function __construct() {
$this->settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>$this->To_default
)
);
}
}
Analogical I don't know why: 'if isset(expression)' doesn't work but it doesn't have to. There's a better solution: 'if(expression)'. This is just how we do it.
You can not specify a default value that refers to another class property ($settings is an array that tries to read $To_default).
My recommendation is that $settings can be the result of a getter method, for example getSettings, and inside of it you can build an array and return it.
By doing do this, you can also decide to override the $To_default value with a setTo method.
Here is an example:
<?php
class EmailClass extends PluginBase {
private string $toDefault = '';
/**
* #param string $toDefault
*/
public function __construct( string $toDefault = 'scrollout#stackoverflow.com' ) {
$this->toDefault = $toDefault;
}
/**
* #param string $toDefault
*/
public function setToDefault( string $toDefault ): void
{
$this->toDefault = $toDefault;
}
public function getSettings(): array
{
return [
'TO' => [
'type' => 'text',
'label' => 'To',
'help' => $this->toDefault,
]
];
}
}
Use a class constant rather than a variable for the default.
Constants don't begin with $, that's the problem you had when you tried this.
class EmailClass extends PluginBase {
private const TO_DEFAULT = 'scrollout#stackoverflow.com';
protected $settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>self::TO_DEFAULT, // Constant expression contains invalid operations
),
);
}

PHP Using class property into another class and use the method again

I am not aware of the technical term how to call it.
I have a base class which has $content property holding entire page array elements. For some constraint, I cannot extends the base class
Base Class
/**
* This is the main class
*
* Class base
*/
class base {
/**
* Holding entire page data in nested array. Such as nav item, posts, metadata etc. with the respective key => []
* Example:
* $this->content = [
* 'head' => [ // some body elements such as meta tags, title etc],
* 'body' => [
* ]
* ]
*
* #var array
*/
public $content = [];
/* This is just for an example of array holding values */
public function some_methods() {
// various method to process the $content and return the element
$this->content = [
'head' => [ /* some body elements such as meta tags, title etc */ ],
'body' => [
'footer' => [ /* header elements */ ],
'nav' => [ /* nav items */ ],
'article' => [
'text' => [ /* text and title */],
'metadata' => [ /* metadata */]
],
'footer' => [ /* footer elements */ ]
]
];
}
public function load_navs( $navs ) {
$one = new one();
$one->hello($navs);
// OR
// some methods has loops in the `base` class
foreach ( $navs as $key => $value ) {
// whatever..
$one->hello($key, $value);
}
}
public function load_footer( $footer ) {
$two = new two();
$two->widget($footer);
}
}
What I want to do is to make some more classes to customize some logic of the base class methods. For that to get a value I need to use $content of the base class into the another class. They the methods from the newly created classes I will use in the base class methods
One
/**
* Class to customize or override base class logic
* Class one
*/
class one {
// to do some process I need to access $content from the `base` class. For example
public function hello($navs){
// here something to check from `nav`
// this method I will use in base class
if ($this->content['nav']['item']['selected']){
// do the stuff
}
}
}
Two
/**
* Class to customize or override base class logic
* Class two
*/
class two {
// to do some process I need to access $content from the `base` class. For example
public function hello($footer){
// here something to check from `footer`
// this method I will use in base class
if ($this->content['footer']['widget']['type'] == 'foo'){
// do the stuff
}
}
}
If you can't extends your class, you can use static attribute. An static attribute is an attribute link to the class and not to an instantiated object.
Look at this link for details. I copy past the important informations
Declaring class properties or methods as static makes them accessible
without needing an instantiation of the class.
So you can declare static $content in your base Class and use it like : base::$content;
class Base {
public static $content = [];
}
class One {
function foo () {
var_dump(Base::$content);
}
}
$foo = new One();
$foo->foo();
//show :
array (size=0)
empty
And by convention, your name Classe need to upperCase (Base, One, Two)
EDIT : No Static, No Extends.
/*
if you can't extends AND you can't use static, you can instancied Base object in you One Class.
Like Tobias say in this comment, if you want change the $content for both Base and One, you need pass the var via reference.
Look at this first example, the base->$content is empty before and after One work. And look at the 2nd example : I get the base content
via reference and the content change in both case.
My english is probably too poor so I suggest to you to read the doc for reference.
*/
class Base {
public $content = [];
function displayContent() {
var_dump($this->content);
}
}
class One {
function displayContent() {
$base = new Base();
$base->content = 'toto';
var_dump($base->content);
}
}
$base = new Base();
$one = new One();
$one->displayContent();
$base->displayContent();
// $one->content == 'toto'; but $base->content still empty.
Now: with reference :
class Base {
public $content = [];
function displayContent() {
var_dump($this->content);
}
}
class One {
public $content;
function displayContent() {
var_dump($this->content);
}
}
$base = new Base();
$one = new One();
$one->content = &$base->content;
$one->content = 'toto';
$one->displayContent();
$base->displayContent();
// NOW, $one->content == 'toto'; and $base->content = 'toto'
Please use below mentioned lines while inherits property of base class.
class One extends Base
class Two extends Base
Than after you can use the property of base class in child class

Using json_encode to temporarily store Resources from the YouTube Data API

I've been working on a custom WordPress plugin for integrating YouTube content into a gaming blog, and in order to limit the strain on the daily API quota, I've been trying to come up with a way to store temporary cache objects which expire after 24 hours.
I created the following class to manage the data:
public class YouTubeDataCache {
protected $dataObject;
protected $expirationDate;
__construct($dataObject) {
$this->dataObject = $dataObject;
$this->expirationDate = time() + 86400;
}
public function isExpired() {
return $this->expirationDate < time();
}
public function getDataObject() {
return $this->dataObject;
}
}
And then, I call json_encode($dataCache) on instances of this class to generate a JSON representation of the instance that I can store in the DB.
Once I started testing this, though, I noticed two significant problems:
The database entries were blank despite verifyin that Google's API returned actual results.
calling json_decode() on the strings pulled out of the database returned fatal errors for undefined methods when I tried calling isExpired() on the decoded object.
My question is two-fold:
How can I make sure that all the necessary data elements get encoded into the JSON string?
How can I retain access to the object's methods after calling json_decode()?
It took some time to track this down, but here is the solution that I came up with.
How do I store all the necessary data elements in the cache?
The specifics of this will vary slightly depending on individual use-cases but the gist of it is this: json_encode() will only encode public variables.
Because my $dataObject and $expirationDate were defined as protected, json_encode() had no access to the values, and therefore encoded blank objects. Oops.
Digging a little deeper, the objects returned from the YouTube API also contained a lot of protected data elements, so even if I changed my class variables to public, I'd still encounter a similar problem trying to store things like video thumbnails.
Ultimately, I had to create my own serialization function.
Newer versions of PHP can utilize the JsonSerializable interface and define a class-specific implementation which, according to the documentation, "returns data which can be serialized by json_encode()"
Since I can't accurately predict what version of PHP users of this plugin will be running, I opted to just create a toJSON() method instead, and modified the constructor to pull specific data elements out of the $dataObject.
First, the constructor/class variables:
For my particular use-case, I'm only concerned about the ID, snippet, status and the thumbnail information. If you need additional data elements, you'll want to dig into the structure of the response object to see what your serialization function needs to manually include.
YouTubeDataCache {
protected $objectId;
protected $snippet;
protected $status;
protected $thumbnails = array();
protected $expirationDate;
__construct($dataObject = null) {
$this->expirationDate = time() + 86400;
if ($dataObject === null) {
return; // I'll explain later
}
$this->objectId = $dataObject->getId();
$this->snippet = $dataObject->getSnippet();
$this->status = $dataObject->getStatus();
$thumbs = $dataObject->getThumbnails();
if ($thumbs->getDefault()) {
$this->addThumbnail('default', $thumbs->getDefault());
}
if ($thumbs->getMedium()) {
$this->addThumbnail('medium', $thumbs->getMedium());
}
if ($thumbs->getHigh()) {
$this->addThumbnail('high', $thumbs->getHigh());
}
if ($thumbs->getMaxRes()) {
$this->addThumbnail('maxRes', $thumbs->getMaxRes());
}
}
public function setExpirationDate($expirationDate) {
$this->expirationDate = $expirationDate;
}
public function addThumbnail($name, $data) {
$this->thumbnails[$name] = $data;
}
public function getObjectId() {
return $this->objectId;
}
public function setObjectId($objectId) {
$this->objectId = $objectId;
}
public function getSnippet() {
return $this->snippet;
}
public function setSnippet($snippet) {
$this->snippet = $snippet;
}
public function getStatus() {
return $this->status;
}
public function setStatus($status) {
$this->status = $status;
}
}
NOTE: There is one caveat here. Technically, the thumbnail information is contained in the Snippet, but it's tied up in a protected array. Since I'll need to pull it out of that array eventually, doing it here provides a more consistent access pattern for what comes next.
Next I added the custom serialization method
NOTE: This is a class method. I just pulled it out for readability
public function toJSON() {
$array = array(
'objectId' => $this->objectId,
'snippet' => $this->snippet,
'status' => $this->status,
'thumbnails' => $this->thumbnails,
'expirationDate' => $this->expirationDate
);
return json_encode($array);
}
With this function, I can now call $cacheObject->toJson() to generate an accurately encoded JSON representation of the data that I can then write to the database, which brings us to the second part of the solution.
How do I retain access to the cache object's methods?
It turns out json_decode creates either a stdClass instance or an associative array (depending on how you choose to call the function). In either scenario, it doesn't have any of the methods present on the class originally used to create the JSON string, so I ultimately had to create a custom deserialization method as well.
I chose to make this particular function static so that I didn't need an instance of the cache object to use it.
public static function fromJSON($json) {
$data = json_decode($json);
$cacheObject = new YouTubeDataCache();
/* This is why the constructor now accepts a null argument.
I won't always have access to an API response object from
Google, and in such cases, it's faster to just call setters
on this class rather than jumping through hoops to create the
Google class instance. */
$cacheObject->setExpirationDate($data->expirationDate);
$cacheObject->setObjectId($data->objectId);
$cacheObject->setSnippet($data->snippet);
$cacheObject->setStatus($data->status);
foreach($data->thumbnails as $name => $thumbnail) {
$cacheObject->addThumbnail($name, $thumbnail);
}
return $cacheObject;
}
The final class definition
Just for reference purposes, here is the final class definition all in one place:
YouTubeDataCache {
protected $objectId;
protected $snippet;
protected $status;
protected $thumbnails = array();
protected $expirationDate;
__construct($dataObject = null) {
$this->expirationDate = time() + 86400;
if ($dataObject === null) {
return;
}
$this->objectId = $dataObject->getId();
$this->snippet = $dataObject->getSnippet();
$this->status = $dataObject->getStatus();
$thumbs = $dataObject->getThumbnails();
if ($thumbs->getDefault()) {
$this->addThumbnail('default', $thumbs->getDefault());
}
if ($thumbs->getMedium()) {
$this->addThumbnail('medium', $thumbs->getMedium());
}
if ($thumbs->getHigh()) {
$this->addThumbnail('high', $thumbs->getHigh());
}
if ($thumbs->getMaxRes()) {
$this->addThumbnail('maxRes', $thumbs->getMaxRes());
}
}
public function setExpirationDate($expirationDate) {
$this->expirationDate = $expirationDate;
}
public function addThumbnail($name, $data) {
$this->thumbnails[$name] = $data;
}
public function getObjectId() {
return $this->objectId;
}
public function setObjectId($objectId) {
$this->objectId = $objectId;
}
public function getSnippet() {
return $this->snippet;
}
public function setSnippet($snippet) {
$this->snippet = $snippet;
}
public function getStatus() {
return $this->status;
}
public function setStatus($status) {
$this->status = $status;
}
public function toJSON() {
$array = array(
'objectId' => $this->objectId,
'snippet' => $this->snippet,
'status' => $this->status,
'thumbnails' => $this->thumbnails,
'expirationDate' => $this->expirationDate
);
return json_encode($array);
}
public static function fromJSON($json) {
$data = json_decode($json);
$cacheObject = new YouTubeDataCache();
$cacheObject->setExpirationDate($data->expirationDate);
$cacheObject->setObjectId($data->objectId);
$cacheObject->setSnippet($data->snippet);
$cacheObject->setStatus($data->status);
foreach($data->thumbnails as $name => $thumbnail) {
$cacheObject->addThumbnail($name, $thumbnail);
}
return $cacheObject;
}
}

How to extract factory-friendly form element configuration array from an existing element instance in ZF2?

I have a Zend\Form\Element instance and I need to extract all configuration as array from this instance, serialize & persist somewhere and re-use later in a form element factory to generate a similar instance again.
Is there any programatic way exists to get full configuration signature from an already instantiated form element object in zend framework 2?
The short answer is no; you will have to roll your own.
You could create a independent class that can take any element and return the correct array by reading it's public methods; this is effectively reversing the functionality of the FormFactory.
A very brief example
class FormElementSerializer
{
public function toArray(ElementInterface $element)
{
$spec = $this->getElementSpec($element);
if ($element instanceof FieldsetInterface) {
$spec = $this->getFieldsetSpec($element, $spec);
}
if ($element instanceof Form) {
$spec = $this->getFormSpec($element, $spec);
}
return $spec;
}
protected function getElementSpec(ElementInterface $element)
{
$spec = array(
'type' => $this->getElementType($element),
'name' => $element->getName(),
'options' => $element->getOptions(),
'attributes' => $element->getAttributes(),
);
return $spec;
}
protected function getFieldsetSpec(FieldsetInterface $fieldset, array $spec)
{
foreach($fieldset->getElements() as $element) {
$spec['elements'][] = $this->getElementSpec($element);
}
return $spec;
}
// deals with hydrators, fieldsets etc
protected function getFormSpec(FormInterface $form, array $spec);
// could be as simple as returning the class name
protected function getElementType(ElementInterface $element);
}

Referencing a class programatically in PHP

I receive an object during some process and this object needs to figure out its coloring scheme.
For example, I have a coloring scheme that is stored like this:
class FirstScheme {
public static $COLORS = array('1' => 'green', '2' => 'red', ...);
}
class SecondScheme {
public static $COLORS = array('1' => 'red', '2' => 'green', ...);
}
I know all the coloring schemes names in advance; they can only change when the code changes.
But the coloring scheme to be used for each object needs to be determined at run-time by matching the attribute of this object.
And here I don't know what to do. In python I would define a dict holding the mappings of color schemes to names like this:
d = {'attr_value1': FirstScheme, 'attr_value2': SecondScheme, 'attr_value3': FirstScheme, ...}
And then just access the "COLORS" variable, because every class should have it. But in PHP there is not way to reference a class in a such way, so what is the right way to do it?
Note that more than one attribute can map to the same coloring scheme.
If every class should have the colors, define the interface that allows to get them:
interface ColorsProvider {
function getColors();
}
class FirstScheme implements ColorsProvider {
public static COLORS = array('1' => 'green', '2' => 'red', ...);
public function getColors() {
return self::COLORS;
}
}
class SecondScheme implements ColorsProvider {
public static COLORS = array('1' => 'red', '2' => 'green', ...);
public function getColors() {
return self::COLORS;
}
}
Then, where you have stack of yout params:
$a = array(
'attr_value1' => new FirstScheme(),
'attr_value2' => new SecondScheme(),
);
You can call:
$param = 'attr_value1';
if(!isset($a[$param]))
throw new Exception("undefined param");
if(!($a[$param] instanceof ColorsProvider))
throw new Exception("Param should point to ColorsProvider");
$a[$param]->getColors();
Please note that it is full-objective. In PHP there are simplier ways to get this effects, but my solution is just elegant.
The another point is the interface completely separates the source of colors. There would be from file, database, xml, hardcoded etc.
Default implementation might be:
abstract class DefaultColorsProviderImpl implements ColorsProvider {
protected static COLORS = array();
public function getColors() {
return self::COLORS;
}
}
class FirstScheme extends DefaultColorsProviderImpl {
protected static COLORS = array( ... );
}
But still allows to make generic implementation that returns colors from e.x. from file.
Of course you can:
$schemes = [
'attr_value1' => FirstScheme::$COLORS,
'attr_value2' => SecondScheme::$COLORS,
...
];
Or even at runtime:
$schemes = [
'attr_value1' => 'FirstScheme',
'attr_value2' => 'SecondScheme',
...
];
And then:
$reflector = new ReflectionClass($schemes['attr_value1']);
$schema = $reflector->getStaticPropertyValue('COLORS');
But this seems not any maintainable at all, and you would like to store such informations in a proper data layer, without hardcoding them as static fields of a class [which is not their purpose].
An alternative to hard-coding the colors in their own classes would be the following approach:
class ColorScheme {
protected $colors;
public function __construct(array $colors) {
$this->colors = $colors;
}
public function getColors() {
return $this->colors;
}
}
$scheme1 = new ColorScheme(array('red', 'blue', 'green'));
$scheme2 = new ColorScheme(array('yellow', 'pink', 'cyan'));
The equivalent for a python dictionary in PHP is an associative array. So you could do this:
$d = array (
'values_1' => $scheme1->getColors(),
'values_2' => $scheme2->getColors()
);

Categories