I'm currently studying OOP Method Chaining, but I'm having difficulties from making it work.
I've created setValue method where it returns a value based on the parameters.
Then, I've created setLabel method and added a parameter to be used after a setValue has been called.
Here is the current code I have:-
class Field
{
public $label;
public function setValue($property, $value)
{
$this->$property = new \stdClass();
$this->$property->value = $value;
$this->$property->label = $this->label;
return $this;
}
public function setLabel($label)
{
return $this->label = $label;
}
}
A sample code for retrieving the data:-
$field = new Field;
$field->setValue('textfield', 'something to print')->setLabel('Label here');
print $field->textfield->value; // Returns the value
print $field->textfield->label; // Returns an empty string
I can't figure out what's wrong with my code. Please help.
Assuming that the following is your goal:
print $field->textfield->value; // Returns the value
print $field->textfield->label; // Returns the label
if you want this to work:
$field = new Field;
$field->setValue('textfield', 'something to print')->setLabel('Label here');
Then you need to do something like this:
class FieldProperty {
public $label
public $value;
public setValue($value) { $this->value = $value; return $this; }
public setLabel($label) { $this->label = $label; return $this; }
}
class Field
{
public function setValue($property, $value)
{
$this->$property = new FieldProperty();
$this->$property->setValue($value);
return $this->$property;
}
}
Thinking about it further, we can add:
class Field
{
public function setValue($property, $value)
{
$this->$property = new FieldProperty();
$this->$property->setValue($value);
return $this->$property;
}
public function setProperty($propertyName)
{
$this->$propertyName = new FieldProperty;
return $this->$propertyName;
}
}
Now you can do this:
$field->setProperty('textfield')->setLabel('my label')->setValue('some value');
If you want to maintain a reference between $this->$property->label and $this->label, assign the value by reference
$this->$property->label =& $this->label;
Demo ~ https://eval.in/955943
Be warned, the reference works both ways so if you assign a new label to $field->textfield->label, it will also change $field->label.
I feel a better approach would be to use magic methods to automatically create each property. Then you can keep track of them all in a private field and update their labels accordingly.
class Field
{
public $label;
private $properties = []; // this will store all your dynamic properties
public function __get($name) {
// lazily create properties on demand
if (!isset($this->properties[$name])) {
$this->properties[$name] = new \stdclass();
}
return $this->properties[$name];
}
public function setValue($property, $value)
{
// this will automatically create "$property" if it's not defined
$this->$property->value = $value;
$this->$property->label = $this->label;
return $this;
}
public function setLabel($label)
{
// Set the field label and any known property labels
$this->label = $label;
foreach($this->properties as $property) {
$property->label = $label;
}
return $label; // ¯\_(ツ)_/¯
}
}
If you want to do it OOP way, I guess, I need to do it like the following. Create the base Field class:
class Field
{
public $value;
public $label;
public function setValue($value)
{
$this->value = $value;
return $this;
}
public function setLabel($label)
{
$this->label = $label;
return $this;
}
}
Maybe, even make it abstract.
Then you can extend this class, creating specific fields:
final class TextField extends Field
{
}
$textField = (new TextField)
->setValue('something to print')
->setLabel('Label here');
Here is the demo.
This way, you can utilize polymorphism later in your program. Say you have an object, to which you pass a bunch of different objects of type Field (read extend Field). Then doing your way, this object wouldn't know what $property each of them has, and it wouldn't be able to access value and label - the objects don't share the common interface. But, with objects, that share the common interface (in our case extend the same base class), we can simply loop over the bunch of fields and retrieve values and labels.
In order to render this fields, the one (read a little bit ugly) solution would be to utilize get_class function:
switch ($class = get_class($field)) {
case 'TextField':
$this->renderTextField($field);
break;
case 'InputField':
$this->renderInputField($field);
break;
// ...
default:
throw new Exception("Cannot render $class.");
}
You are changing label property of $property ('textfield') not $this. It means var_dump($field->textfield->label); doesn't contain anything but var_dump($field->label); does.
Saying that you need to store $property's name so that later you can refer to it. A quick working solution would be:
<?php
class Field
{
public $label;
public $propName;
public function setValue($property, $value)
{
$this->propName = $property;
$this->$property = new \stdClass();
$this->$property->value = $value;
$this->$property->label = $this->label;
return $this;
}
public function setLabel($label)
{
$this->{$this->propName}->label = $label;
}
}
$field = new Field;
$field->setValue('textfield', 'something to print')->setLabel('Label here');
var_dump($field->textfield->value, $field->textfield->label);
Live demo
Sorry to necromance but I wanted to do the same thing but without creating methods for each, while staying nice and short (i didn't want ->setMethod('propertyName', $value))
class Field
{
public function __call($property, $args): self
{
$this->{$property} = $args[0];
return $this;
}
}
Sample use:
$field = (new Field)->label('label!')->value('value!');
And the value of $field:
Field Object
(
[label] => label!
[value] => value!
)
I also whitelisted what property names I'll allow to be set, and just ignore anything else.
In your code "label" has the value into Field Object and not into textfield object as you can see:
Field object {
label => (string) Label here
textfield => stdClass object {
value => (string) something to print
label => null
}
}
You colud use $field->label, because they are stored in different object, or try this:
class Field {
public function setValue($value) {
$this->property = new \stdClass();
$this->property->value = $value;
return $this;
}
public function setLabel($label) {
$this->property->label = $label;
return $this->property;
}
}
$field = new Field();
$field->setValue('something to print')->setLabel('Label here');
print $field->property->value;
print $field->property->label;
Output
something to printLabel here
Related
I can not load data to properties using this construction I receive null in dump
<?php
namespace App\Domain\Good;
class GoodDto
{
public $name;
public $articul;
public $price;
public $type;
public $qnt;
public $discount;
public $category;
public $description;
public $description2;
public $color;
public function load($data)
{
$this->name = $data['name'];
$this->articul = $data['artikul'];
$this->price = $data['price'];
$this->type = (isset($data['type'])) ? $data['type'] : null;
$this->qnt = $data['count'];
$this->discount = $data['spinner-decimal'];
$this->category = $data['id_cat'];
$this->description = $data['editor1'];
$this->description2 = '';
$this->color = $data['color'];
//$this->user_id = Auth::user()->id;
}
public static function fromRequest($request)
{
dump('inp=>',(new self ())->load($request->input()));
return (new self ())->load($request->input());
}
}
Please explain to me why I receive null while request->input() is an array, I call it from another place
$dto=GoodDto::fromRequest($request);
Method chaining, returns the last return from the chain. The other returns are used to call the next link in the chain.
(new self ())->load()
So load() needs to return $this
public function load($data)
{
...
return $this;
}
Currently it returns null, which is why it returns null.
See you are not saving the instance from the constructor, instead you pass it to load by enclosing it within the (....). By pass it I mean you call the load method on the return from the constructor.
You can test this like so:
class foo{
function load(){
return $this;//return this
}
}
var_dump((new foo)->load());
class bar{
function load(){
//return null
}
}
var_dump((new bar)->load());
Output
//return this
object(foo)#1 (0) {
}
//return null
NULL
sandbox
The second class in the example above class bar, is essentially what you are doing.
PS. forgot to scroll down on your post at first ... lol ... So I had to update my answer.
Bonus
You can also simplify the load code like this:
public function load($data)
{
foreach($data as $prop=>$value){
if(property_exists($this,$prop)) $this->$prop = $value;
}
return $this;
}
This way if you add new properties you don't have to edit the load method ever again, you just have to name the array elements the same as the class properties. You can even throw an error if the property does not exist if you want, by adding an else to the condition etc...
Personally, when I do this I prefer to call a set method like this:
//eg. $data = ['foo' => '2019-06-16']
public function load(array $data)
{
foreach($data as $prop=>$value){
$method = 'set'.$prop; //$method = 'setfoo' using the example above
if(method_exists($this,$method )){
$this->$method($value); //calls 'setfoo' with '2019-06-16'
}else{
throw new Exception('Unknown method '.$method);
}
}
return $this;
}
public function setFoo($date){
$this->foo = new DateTime($date);
}
Then you can apply some transforms to the data etc... PHP method names are not case sensitive. You can even combine these by first checking for a method then a property then throw the error etc...
Cheers.
I've got one Aggregate root - Product - that has few fields and some of them are objects, like Price. It looks like that ( of course it is simplified ):
Product
{
private $price;
public function __construct(Price $price)
{
$this->price = $price;
}
}
Price
{
private $currency;
private $amount;
public function __construct($currency, $amount)
{
$this->currency = $currency;
$this->amount= $amount;
}
}
This aggregate root doesn't contain "getPrice" method, it's not needed in Domain.
The issue:
I need to serialize this aggregate, but I would like to have it in this format:
Product.json
{
"priceCurrency": "GBP",
"priceAmount": 100
}
I've been trying with JMSSerializer, but can't really get it from config. This for example doesn't work:
Product.yml
Namespace\Product:
virtual_properties:
getAmount:
exp: object.price.amount
serialized_name: priceAmount
type: integer
getCurrency:
exp: object.price.currency
serialized_name: priceCurrency
type: string
I understand that it's due to the fact, that "exp" part is being used by Symfony Expression Language and from what I know it doesn't support getting values from private fields in any other way then through theirs methods. I also know that JMSSerializer itself supports that. I don't have to have field "getPrice" to serialize "price" field.
Question: Is there any way to achieve what I want just through config or do I have to write listeners on post_serialize event?
Use something like this:
<?php
class Property
{
protected $reflection;
protected $obj;
public function __construct($obj)
{
$this->obj = $obj;
$this->reflection = new ReflectionObject($obj);
}
public function set($name, $value)
{
$this->getProperty($name)->setValue($this->obj, $value);
}
public function get($name)
{
return $this->getProperty($name)->getValue($this->obj);
}
protected function getProperty($name)
{
$property = $this->reflection->getProperty($name);
$property->setAccessible(true);
return $property;
}
}
// example
class Foo
{
protected $bar = 123;
public function getBar()
{
return $this->bar;
}
}
$foo = new Foo();
echo 'original: '.$foo->getBar().PHP_EOL;
$prop = new Property($foo);
echo 'reflection - before changes: '.$prop->get('bar').PHP_EOL;
$prop->set('bar', 'abc');
echo 'after changes: '.$foo->getBar().PHP_EOL;
I'm trying to figure out the best way to iterate over an object's properties so I can build a sql query for an insert or update. I also am looking to be able to omit certain fields in the iteration.
Below is an example object where I would like to grab name and age but omit employer because that is a join from another table.
class person
{
private $_name, $_age, $_employer;
public function get_name()
{
return $this->_name;
}
public function get_age()
{
return $this->_age;
}
public function get_employer()
{
return $this->_employer;
}
}
I could cast an object as an array to get the properties but I still don't have a good way to omit certain properties.
$personObj = new person();
foreach((array)$personObj as $k => $v)
{
$sql .= "...";
}
Hope this gives you a hint
class person
{
private $_name = 'dude';
private $_age = '27';
private $_employer = 'yes';
public function get_name()
{
return $this->_name;
}
public function get_age()
{
return $this->_age;
}
public function get_employer()
{
return $this->_employer;
}
}
$person = new person();
$required = array('name','age');
foreach($required as $req)
{
$func = "get_{$req}";
echo $person->$func();
}
https://3v4l.org/vLdAN
I have a problem to get an object from an array-collection of objects by ID.
The following is my code:
protected $_rootLocation;
public function __construct(Location $rootLocation)
{
$this->_rootLocation= $rootLocation;
var_dump($this->_rootLocation);
}
public function getLocationById($id)
{
$value = null;
foreach($this->_rootLocationas $root)
{
if ($id == $root->getId())
{
$value = $root;
break;
}
}
return $value;
}
Then the function return "NULL" so it's dosn't work...
Edit
I do like that :
$manager = new LocationManager($rootLocation);
echo "<pre>";
var_dump($manager->getLocationById('291'));
echo "</pre>";
Your function returns null because the object is not found!
It depends on the implementation of the myClasse object, this must implement the iterator interface and the getId() method must return a valid Id on each iteration.
Imagine that none of all objects in the array has the ID you're looking for. Your function will just return null. For example with an empty array.
As you can see, returning null does not mean that the function does not work. It works perfectly and did what you specified, it is just, that no such object exists.
It's then up to you to decide what to do if this happens. As you've not told in your question, there is not much to add but to offer you some options:
You could check if the function returns null and then take it as a "not found" case.
$result = $collection->getObjectById($id);
if (null === $result) {
# object not found
} else {
# object found
}
You can throw an Exception inside the function if the function should only be called for existing objects:
public function getObjectById($id) {
foreach ($this->_rootObject as $root) {
if ($id == $root->getId()) {
return $root;
}
}
throw new InvalidArgumentException(sprintf('Not a valid ID: %d', $id));
}
or finally:
Offer an additional function to check for an existing ID first:
private function findById($id) {
foreach ($this->_rootObject as $object) {
if ($id == $object->getId()) {
return $object;
}
}
return null;
}
public function hasObjectById($id) {
return null !== $this->findById($id);
}
public function getObjectById($id) {
if (null !== $root = $this->findById($id)) {
return $root;
}
throw new InvalidArgumentException(sprintf('Not a valid ID: %d', $id));
}
Also you might be interested to create yourself a class called that encapsulates your needs, so you do not need to implement that in your "I manage the root collection object " object which is more than indirect. This then is basically your own collection class. An example:
interface Identifiable {
public function getId();
}
/**
* Example Object Class
*/
class MyObject implements Identifiable {
private $id;
public function __construct($id) {
$this->id = (int) $id;
}
public function getId() {
return $this->id;
}
}
/**
* Example Collection
*/
class IdentifiableCollection implements Countable, IteratorAggregate
{
private $objects;
public function attach(Identifiable $object) {
$id = $object->getId();
$this->objects[$id] = $object;
}
public function count() {
return count($this->objects);
}
public function has($id) {
return isset($this->objects[$id]);
}
public function getById($id) {
if ($this->has($id)) {
return $this->objects[$id];
}
throw new InvalidArgumentException(sprintf("No object is identifiable for %d", $id));
}
public function getIterator() {
return new ArrayIterator($this->objects);
}
}
// create the collection
$collection = new IdentifiableCollection();
// fill the collection with some objects (ID 1 - 20)
foreach(range(1, 20) as $id) {
$collection->attach(new MyObject($id));
}
// test if an id exists and return object
$id = 2;
var_dump($collection->has($id), $collection->getById($id));
// iterate over the collection
foreach ($collection as $object) {
var_dump($object);
}
This collection class only offers to attach objects, not remove them but you can extend that as needed. It's also possible to extend from an existing class like ArrayObject or SplObjectStorage if you want to reuse existing functionality. An example is given in another answer in a somewhat related question:
Array of objects within class in PHP
I have a php singleton session class as follows.
class Session {
static private $_instance = NULL;
private function __construct()
{
session_start();
}
/**
* Prevents the class from being cloned
* #return NULL
*/
private function __clone() { }
/**
* Returns the singleton instance of this class
* #return Session
*/
public static function getInstance()
{
if (!self::$_instance) {
self::$_instance = new Session();
}
return self::$_instance;
}
public function __get($key) {
if (isset($_SESSION[$key])) {
return $_SESSION[$key];
}
return NULL;
}
public function __set($key, $value)
{
$_SESSION[$key] = $value;
}
public function __isset($key) {
return isset($_SESSION[$key]);
}
public function __unset($key) {
unset($_SESSION[$key]);
}
}
I can create an object as follows
$session = Session::getInstance();
$session->name = 'some name';
I can also get the value like
echo $session->name;
The problem is, i want to pass an array to this object and it is not working. for example, i wan to set something like
$_SESSION['user']['data'] = array('name'=>'some name',"empId"=>'123');
I am trying like this.
$session->['user']['data'] = array('name'=>'some name',"empId"=>'123');
but it is not working. Could you please suggest what is wrong.
The workaround in this case would be to use:
public function &__get($key) {
if (isset($_SESSION[$key])) {
return & $_SESSION[$key];
}
return NULL;
}
You need to modify the __get() method, because an assignment like
$session->user['data'] = ...
will actually retrieve the [user] key, and then try to assign a new subarray [data] to that temporary array result.
Also note that $session->['user']['data'] is invalid syntax. You either need $session->user['data'] or $session->{'user'}['data'].
Anyway, I think it is probably not a good idea to use a wrapper if you often want to do assignments like that. (I do actually have something very similar.)
$session->user = array('data' => array('name'=>'some name',"empId"=>'123'));
Make sure you don't overwrite anything else in user you want to keep