Is there any way I can set up PHP objects so that when I try to convert them to JSON, all of their protected properties will be shown?
I have read other answers suggesting I add a toJson() function to the object, but that may not really help me a great lot. In most cases, I have an array of objects and I perform the encode on the array itself.
$array = [
$object1, $object2, $object3, 5, 'string', $object4
];
return json_encode($array);
Yes, I can loop through this array and call toJson() on every element that has such method, but that just doesn't seem right. Is there a way I can use magic methods to achieve this?
You can implement the JsonSerializable interface in your classes so you have full control over how it is going to be serialized. You could also create a Trait to prevent copy pasting the serializing method:
<?php
trait JsonSerializer {
public function jsonSerialize()
{
return get_object_vars($this);
}
}
class Foo implements \JsonSerializable
{
protected $foo = 'bar';
use JsonSerializer;
}
class Bar implements \JsonSerializable
{
protected $bar = 'baz';
use JsonSerializer;
}
$foo = new Foo;
$bar = new Bar;
var_dump(json_encode([$foo, $bar]));
Alternatively you could use reflection to do what you want:
<?php
class Foo
{
protected $foo = 'bar';
}
class Bar
{
protected $bar = 'baz';
}
$foo = new Foo;
$bar = new Bar;
class Seriailzer
{
public function serialize($toJson)
{
$data = [];
foreach ($toJson as $item) {
$data[] = $this->serializeItem($item);
}
return json_encode($data);
}
private function serializeItem($item)
{
if (!is_object($item)) {
return $item;
}
return $this->getProperties($item);
}
private function getProperties($obj)
{
$rc = new ReflectionClass($obj);
return $rc->getProperties();
}
}
$serializer = new Seriailzer();
var_dump($serializer->serialize([$foo, $bar]));
Related
I need to be able to set my object like this:
$obj->foo = 'bar';
then I need to use it as an array like that:
if($obj['foo'] == 'bar'){
//more code here
}
Just add implements ArrayAccess to your class and add the required methods:
public function offsetExists($offset)
public function offsetGet($offset)
public function offsetSet($offset, $value)
public function offsetUnset($offset)
See http://php.net/manual/en/class.arrayaccess.php
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
Try extending ArrayObject
You'll also need to implement a __get Magic Method as Valentin Golev mentioned.
Your class will need to looks something like this:
Class myClass extends ArrayObject {
// class property definitions...
public function __construct()
{
//Do Stuff
}
public function __get($n) { return $this[$n]; }
// Other methods
}
ArrayObject implements the ArrayAccess interface (and some more). Using the ARRAY_AS_PROPS flag it provides the functionality you're looking for.
$obj = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
$obj->foo = 'bar';
echo $obj['foo'];
Alternatively you can implement the ArrayAccess interface in one of your own classes:
class Foo implements ArrayAccess {
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetSet($offset , $value) {
$this->$offset = $value;
}
public function offsetUnset($offset) {
unset($this->$offset);
}
}
$obj = new Foo;
$obj->foo = 'bar';
echo $obj['foo'];
You can access PHP object as PHP array, but in different ways. Try this:
$obj->{'foo'}
That is similar with accessing array like this:
$arr['foo']
You can also do this:
$propertyName = 'foo';
$obj->$propertyName; // same like first example
You'll have to implement the ArrayAccess interface to be able to do that -- which only means implementing a few (4 to be exact) simple methods :
ArrayAccess::offsetExists : Whether or not an offset exists.
ArrayAccess::offsetGet : Returns the value at specified offset.
ArrayAccess::offsetSet : Assigns a value to the specified offset.
and ArrayAccess::offsetUnset : Unsets an offset.
There is a full example on the manual's page I pointed to ;-)
You're mixing objects and arrays. You can create and access an object like so:
$obj = new stdClass;
$obj->foo = 'bar';
if($obj->foo == 'bar'){
// true
}
and an array like so:
$obj = new Array();
$obj['foo'] = 'bar';
if($obj['foo'] == 'bar'){
// true
}
You can define a class and add implements ArrayAccess if you want to access your class as both an array and a class.
http://www.php.net/manual/en/language.oop5.php
Your object must implement the ArrayAccess interface, then PHP will allow you to use the square brackets like that.
You could also cast the object as an array:
if((array)$obj['foo'] == 'bar'){
//more code here
}
Enhance Class capability with no functionality drawbacks
You can also use ArrayAccess to access a single array property in your class and leave other properties being accessed in OOP way. Yet still it will work as you requested.
class Foo implements \ArrayAccess
{
/**
* mixed[] now you can access this array using your object
* like a normal array Foo['something'] = 'blablabla'; echo Foo['something']; ... and so on
* other properties will remain accessed as normal: $Foo->getName();
*/
private myArrayOptions = [];
private $name = 'lala';
...
public function offsetExists($offset)
{
return isset($this->myArrayOptions[$offset]);
}
public function offsetGet($offset)
{
if ($this->offsetExists($offset)) {
return $this->myArrayOptions[$offset];
}
return null; // or throw the exception;
}
public function offsetSet($offset, $value)
{
$this->myArrayOptions[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->myArrayOptions[$offset]);
}
public function getName()
{
return $this->name;
}
public function __set($offset, $value){
$this->myArrayOptions[$offset] = $value;
}
...
}
The above will work as you expected.
$obj->foo = 'bar';
if($obj['foo'] == 'bar'){
echo "WoWo";
}
Also note that Foo['name'] !== Foo->getName()
those a two different variables
I'm testing a logic flow by mocking a class and testing for the function call.
function() setUp()
{
$this->shipping_method = $this->getMockBuilder(Wc_Trincargo_Shipping_Method::class)
->getMock();
$this->shipping_method->set_post_data([
'woocommerce_wc-trinicargo-shipping_waybill_password' => 'xxx',
'woocommerce_wc-trinicargo-shipping_waybill_username' => 'xxxx',
'woocommerce_wc-trinicargo-shipping_waybill_customer_id' => uniqid(),
'woocommerce_wc-trinicargo-shipping_waybill_pickupdays' => 2
]);
}
set_post_data is a public method that sets a protected property.
Later down I test to call a another method that needs to check the said protected property. I know they say you can't mock protected and private properties but if the properties are being set by public methods....shouldn't it work?
If you really need to access a protected property within a test and you don't have a getter (nor should you create one purely for a test), you could use reflection.
<?php
class MyClass
{
protected $myProperty;
public function setMyProperty($value)
{
$this->myProperty = $value;
}
}
$a = new MyClass();
$a->setMyProperty('TestValue');
// echo $a->myProperty; Can't do this because it's protected.
$r = new ReflectionProperty('MyClass', 'myProperty');
$r->setAccessible(true);
$value = $r->getValue($a);
echo $value; // 'TestValue'
It is irrelevant that the protected property was sat from a public method. The visibility belongs to the property regardless. If you need to check the value of the property afterwards, create a public getter for that property.
Note that it is useless to test simple getters and setters. You should test what the class does with the values of the properties instead.
Example of a useless test:
class MyClass {
private $prop;
public function setProp($value) {
$this->prop = $value;
}
public function getProp() {
return $this->prop;
}
}
Test
$myClass = new MyClass();
$myClass->setProp('foo');
assertTrue($myClass->getProp() === 'foo');
Example of a class that uses a prop in a meaningful way, which determines the behavior/output of another method:
class MyClass2 {
private $prop;
public function setProp($value) {
$this->prop = $value;
}
public function getPropUpperCase() {
return strtoupper($this->prop);
}
}
Test
$myClass2 = new MyClass();
$myClass2->setProp('foo');
assertTrue($myClass->getPropUpperCase() === 'FOO');
I have a class like this:
class example{
private $foo = array();
private $bar = array();
public function getFoo(){
return $this->foo;
}
public function getBar(){
return $this->bar;
}
//for example
public function doSomth(array $smth){
// do somth on $smth
return $smth;
}
}
I want to be able to define a method that works on all data members of my class which they have the type of array, somthing like this:
$exmpl = new Example();
$exmpl->getFoo()->doSmth();
//or
$exmpl->getBar()->doSmth();
What should I do?
Instead of returning $this->foo or $this->bar directly, return an object that takes the data and has a doSmth method, like:
class example{
private $foo = array();
private $bar = array();
public function getFoo(){
return new dosmth($this->foo);
}
public function getBar(){
return new dosmth($this->bar);
}
}
class dosmth {
public function __construct(array $smth) {
$this->smth = $smth;
}
public function doSmth() {
echo 'do something on $this->smth';
return $this->smth;
}
private $smth;
}
$exmpl = new Example();
$exmpl->getFoo()->doSmth();
$exmpl->getBar()->doSmth();
See also Fluent Interface.
While this appears to solve the problem as stated, I caution that there may be a better design approach. Specifically, let "example" be purely a data container with accessor methods for "foo" and "bar", and let "dosmth" be a helper class that you instantiate and call as needed. This would be an equivalent API invocation that is only marginally more typing but keeps the concerns clearly separated between classes:
$helper = new dosmth;
$exmpl = new example;
$helper->doSmth($exmpl->getFoo());
$helper->doSmth($exmpl->getBar());
Fluent interfaces are a siren song. Use them where they help, but don't implement them just because you can.
This is an very simplified example of some code that is probably overcoded, but I want to access a class member variable using a class constant and was wondering if there's a simpler syntax than using the $foo->__get below?
class Foo
{
const BAR = 'bar';
private $props = array( self::BAR => 'wee' );
public function __get($name)
{
return $this->props[$name];
}
}
$foo = new Foo();
echo $foo->__get(Foo::BAR);
This also works:
$foo->{Foo::BAR};
Or implement ArrayAccess, then you can have:
$foo[Foo::BAR]
But why not access it as $foo->bar ?! Are you planning to change that constant a lot or am I missing something here?
I think the code looks good, but you could use the constructor to initialize the props array:
class Foo
{
const BAR = 'bar';
private $props;
public function __construct() {
$this->props = array( self::BAR => 'wee' );
}
public function __get($name)
{
return $this->props[$name];
}
}
Here is the code layout outline all nicely laid out in 3 file and class's
$aa = new className();
class className {
/**
* Constructor
*/
function className() {
$this->init_SubClass();
}
function init_SubClass() {
require_once('sub_class.class.php');
$sub_class = new sub_class();
}
}
sub_class.class.php
class sub_class {
/**
* Constructor
*/
function sub_class() {
$this->init_Sub_Sub_Class();
}
function init_Sub_Sub_Class() {
require_once('Sub_Sub_Class.class.php');
$Sub_Sub_Class = new Sub_Sub_Class();
}
}
sub_sub_class.class.php
class Sub_Sub_Class {
public function function_I_to_call() {
echo ' show this text'
}
}
How to a call function_I_to_call()
This was mybest guess so far
$aa->className->sub_class->function_I_to_call()
Not sure how to do this or if it can be done.
Many Thanks
You are not assigning the newly created object to the instance. You need to use
$this->sub_class = new Subclass;
That will make them public properties and then you can use your
$aa = new className;
$aa->sub_class->function_I_to_call();
However, the entire approach is completely flawed:
The constructor should be __construct. The old style constructor is a relic from PHP4 times and wont work with namespaced classes.
Assigning properties on the fly is considered bad practice, because it's unobvious they exist when looking at the API. Declare them as members in the class.
Calls to require are unneeded when you use an Autoloader.
Use Dependency Injection to decouple your components. Makes them easier to unit-test as well.
If you need to assemble complex collaborator graphs, use a Factory or a Builder pattern instead.
Alternate approach
class Foo
{
protected $bar;
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
public function getBar()
{
return $this->bar;
}
}
And then Bar
class Bar
{
protected $baz;
public function __construct(Baz $baz)
{
$this->baz = $baz;
}
public function getBaz()
{
return $this->baz;
}
}
And Baz:
class Baz
{
public function fn()
{
return 'called';
}
}
And then assemble it via:
$foo = new Foo(new Bar(new Baz));
Or move that code to a Factory:
class FooFactory
{
public function create()
{
return new Foo(new Bar(new Baz));
}
}
Finally, the Autoloader (simplified):
spl_autoload_register(function($className) {
$classMap = array(
'Foo' => '/path/to/Foo.php',
'Bar' => '/path/to/Bar.php',
'Baz' => '/path/to/Baz.php',
);
require $classMap[$className];
});
And then you could call (demo)
$fooFactory = new FooFactory;
$foo = $fooFactory->create();
echo $foo->getBar()->getBaz()->fn();
But you shouldnt (unless it's some sort of DSL), because that is violating Law of Demeter because you are digging too deep into the collaborators.