Side effects of PHP's serialize as hash function - php

I'm looking to create a sort of HashMap class in PHP. For the sake of being able to build upon PHP's associative array functionality, I have a hash function, which should be able to take any variable (primitive or object) and turn it into a string for use as an array key.
For this hash function, I am thinking of using serialize(), but I noticed that PHP calls __sleep() on an object when that function is called. I'm assuming this could be problematic. Am I right?
If so, what can I use to get a hash of either a primitive data type or of an object? I did look at spl_object_hash(), but its results seem less than unique, as it uses reference locations, which appear to be reused?
Any thoughts? Thanks
Update: If anyone's interested, this is (roughly speaking) what I ended up with. The Collection interface can be ignored. Any improvements welcome, of course. Oh, and there isn't a remove method yet.
<?php
include_once 'Collection.php';
class HashMap implements Collection {
private $data;
private $hashes;
public static function createEmpty() {
return new HashMap();
}
public function __construct() {
$this->data = new \SplObjectStorage();
$this->hashes = array();
}
public function add($key, $value) {
// var_dump($this->hash($key));
$this->data->offsetSet($this->hash($key), $value);
}
private function hash($key) {
if (!is_object($key)) {
if (isset($this->hashes[$key])) {
return $this->hashes[$key];
} else {
$obj = new PrimitiveAsObject(serialize($key));
return ($this->hashes[$key] = $obj);
}
} else {
return $key;
}
}
public function get($key) {
$key = $this->hash($key);
if ($this->data->contains($key)) {
return $this->data->offsetGet($key);
} else {
return null;
}
}
}
class PrimitiveAsObject {
private $val;
public function __construct($v) {
$this->val = $v;
}
}

You've mentioned that you're trying to use objects as keys in a hash, to store additional data.
The standard-in-PHP-5.3 SPL Object Storage class was designed for this use case, though it's kind of funky to use. It can behave as an array, thankfully.
Now, it can only be used to store actual objects, not primitives. This may pose a problem for your use case, but it's probably the best thing you've got for storage of objects as keys.

Objects as keys: SplObjectStorage

You can hash with md5
http://php.net/manual/en/function.md5.php
But of course you need a reliable and unique toString for objects involved.

Related

PHP use of anonymous functions

So I'm really confused about anonymous functions in PHP, I want to know if anonymous functions are mainly used as a matter of taste or coding style.
I'm asking this because you can achieve the same result without a callback function and less code.
So here is some test code:
$output = 10;
$people = (new People(new John));
//VERSION 1 ANONYMOUS FUNCTION
$people->run(function ($value) use(&$output){
$output = $output + $value;
});
var_dump($output); //RESULT => 20
//VERSION 2 WITHOUT ANONYMOUS FUNCTION
var_dump($people->run() + $output); //RESULT => 30
You can run and see the full code here:
https://www.tehplayground.com/IhWJJU0jbNnzuird
<?php
interface HumanInterface
{
public function hello();
}
class People
{
protected $person;
protected $value;
public function __construct(HumanInterface $person)
{
$this->person = $person;
return $this;
}
public function run(callable $callback = null, $name = null)
{
$this->value = 10;
if(is_callable($callback)) {
return call_user_func($callback, $this->value);
}
return $this->value;
}
}
class John implements HumanInterface
{
public function hello()
{
return 'hi i am john';
}
}
$output = 10;
$people = (new People(new John));
$people->run(function ($value) use(&$output){
$output = $output + $value;
});
var_dump($output);
var_dump($people->run() + $output);
So my question is: why use an anonymous function? Is it a matter of
personal choice?
Anonymous functions or „Closures“ are very useful if it is used as a one-time callback. If you use PHP's usort-method for example. The second parameter can be a Closure. So instead of writing a named function which is used once and then never again you use a Closure.
IMHO this is the only way to use Closures: as a callback.
Anonymous functions are useful to pass around for later execution or to other code accepting functions. Using them can dramatically reduce the code needed to do things.
Imagine you have a UserCollection object that uses a generic Collection underneath. You want to reduce the UserCollection to just the Users from a certain country. So you could add a method findByCountry() on the UserCollection:
public function findByCountry($country) : UserCollection {
$subset = new UserCollection;
foreach ($this->users as $user) {
if ($user->country === $country) {
$subset->add($user);
}
}
return $subset;
}
This is all fine, but additional finder methods will all do the same: iterate and collect into the subset with only the criteria being different. So a lot of boilerplate code.
You can separate the boilerplate from the criteria easily by adding a method find(callable $callback) on the underlying Collection, like this:
public function find(callable $criteria) : Collection {
$subset = new Collection;
foreach ($this->users as $user) {
if ($criteria($user)) {
$subset->add($user);
}
}
return $subset;
}
This is a generic finder. Now your code in the UserCollection will only contain the actual criteria:
public function findByCountry($country): UserCollection {
return $this->subset(function(User $user) {
return $user->country === $country;
});
}
private function subset($criteria): UserCollection {
return new UserCollection($this->allUsers->find($criteria));
}
By separating the criteria from the boilerplate, it's much easier to grasp that you are trying to find users by country. There is no iteration code distracting from the actual criteria. So it becomes easier to understand and less effort to write. Also, you cannot accidentally mess up the iteration because it's defined elsewhere.
When using anonymous functions like this, they are very similar to using the Strategy Pattern or a FilterIterator. The notable difference being that you are creating them on the fly.
Note that you have to differentiate between Lambdas and Closures. I deliberately ignored the difference for this answer.

Show all public attributes (name and value) of an object

This thread didn't helped me.
If I use
$class_vars = get_class_vars(get_class($this));
foreach ($class_vars as $name => $value) {
echo "$name : $value\n";
}
I get
attrib1_name : attrib2_name : attrib3_name
There are no values. Also a private attribute is shown, which I don't want.
If I use
echo "<pre>";
print_r(get_object_vars($this));
echo "</pre>";
I get
Array
(
[atrrib1_name] => attrib1_value
[attrib2_name] => attrib2_value
)
Here again I have a private attribute and all sub attributes. But this time I have the values. How can I constrain this to one level?
Isn't there a possibility to show all public attributes with their values of an object?
You are seeing non-public properties because get_class_vars works according to current scope. Since you are using $this your code is inside the class, so the non-public properties are accessible from the current scope. The same goes for get_object_vars which is probably a better choice here.
In any case, a good solution would be to move the code that retrieves the property values out of the class.
If you do not want to create a free function for that (why? seriously, reconsider!), you can use a trick that involves an anonymous function:
$getter = function($obj) { return get_object_vars($obj); };
$class_vars = $getter($this);
See it in action.
Update: Since you are in PHP < 5.3.0, you can use this equivalent code:
$getter = create_function('$obj', 'return get_object_vars($obj);');
$class_vars = $getter($this);
You can do this easily with php Reflection api
Extending Mr.Coder's answer, here is a snippet to fetch the public attributes of the object (name and value) as an array
public function getPublicProperties()
{
$results = [];
$reflectionObject = (new ReflectionObject($this));
$properties = $reflectionObject->getProperties(ReflectionProperty::IS_PUBLIC);
foreach ($properties as $property) {
$results[$property->getName()] = $property->getValue($this);
}
return $results;
}
Use get_object_vars.
see: http://dk.php.net/manual/en/function.get-object-vars.php
I Fully recognize what you are trying to achieve so why not have something external like this to help out... (pasted from PHPFiddle)
<?php
final class utils {
public static function getProperties(& $what) {
return get_object_vars($what);
}
}
class ball {
var $name;
private $x, $y;
function __construct($name,$x,$y) {
}
function publicPropsToArray() {
return utils::getProperties($this);
}
function allPropsToArray() {
return get_object_vars($this);
}
}
$ball1 = new ball('henry',5,6);
//$ball2 = new ball('henry',3,4);
echo "<pre>";
print_r($ball1->publicPropsToArray());
echo "\r\n\r\n";
print_r($ball1->allPropsToArray());
echo "\r\n\r\n";
?>
This way I can both access all properties of the object or for something such as a database access layer or similarly for a function that send "safe" data to a view or another un-privileged model I can send just the public properties, but have the behaviour defined within the object.
Sure this leads to coupling with a utility class, but to be fair not all couplings are bad, some are nesecarry to achieve an end goal, dont get bogged down by these things

PHP object method doesn't behave as I expect

I can't quite understand why the output of this code is '1'.
My guess is that php is not behaving like most other OO languages that I'm used to, in that the arrays that php uses must not be objects. Changing the array that is returned by the class does not change the array within the class. How would I get the class to return an array which I can edit (and has the same address as the one within the class)?
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function getArr()
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = $t->getArr();
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
EDIT: I expected the output to be 0
You have to return the array by reference. That way, php returns a reference to the array, in stead of a copy.
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function & getArr() //Returning by reference here
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = &$t->getArr(); //Reference binding here
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
This returns 0.
From the returning references subpage:
Unlike parameter passing, here you have to use & in both places - to
indicate that you want to return by reference, not a copy, and to
indicate that reference binding, rather than usual assignment, should
be done
Note that although this gets the job done, question is if it is a good practice. By changing class members outside of the class itself, it can become very difficult to track the application.
Because array are passed by "copy on write" by default, getArr() should return by reference:
public function &getArr()
{
return $this->arr;
}
[snip]
$tobj_arr = &$t->getArr();
For arrays that are object, use ArrayObject. Extending ArrayObject is probably better in your case.
When you unset($tobj_arr[0]); you are passing the return value of the function call, and not the actual property of the object.
When you call the function again, you get a fresh copy of the object's property which has yet to be modified since you added 5 to it.
Since the property itself is public, try changing:
unset($tobj_arr[0]);
To: unset($t->arr[0]);
And see if that gives you the result you are looking for.
You are getting "1" because you are asking PHP how many elements are in the array by using count. Remove count and use print_r($tobj_arr_fresh)

Is there an integer equivalent of __toString()

Is there a way to tell PHP how to convert your objects to ints? Ideally it would look something like
class ExampleClass
{
...
public function __toString()
{
return $this->getName();
}
public function __toInt()
{
return $this->getId();
}
}
I realize it's not supported in this exact form, but is there an easy (not-so-hacky) workaround?
---------------------- EDIT EDIT EDIT -----------------------------
Thanks everybody! The main reason I'm looking into this is I'd like to make some classes (form generators, menu classes etc) use objects instead of arrays(uniqueId => description). This is easy enough if you decide they should work only with those objects, or only with objects that extend some kind of generic object superclass.
But I'm trying to see if there's a middle road: ideally my framework classes could accept either integer-string pairs, or objects with getId() and getDescription() methods. Because this is something that must have occurred to someone else before I'd like to use the combined knowledge of stackoverflow to find out if there's a standard / best-practice way of doing this that doesn't clash with the php standard library, common frameworks etc.
I'm afraid there is no such thing. I'm not exactly sure what the reason is you need this, but consider the following options:
Adding a toInt() method, casting in the class. You're probably aware of this already.
public function toInt()
{
return (int) $this->__toString();
}
Double casting outside the class, will result in an int.
$int = (int) (string) $class;
Make a special function outside the class:
function intify($class)
{
return (int) (string) $class;
}
$int = intify($class);
Of course the __toString() method can return a string with a number in it: return '123'. Usage outside the class might auto-cast this string to an integer.
Make your objects implement ArrayAccess and Iterator.
class myObj implements ArrayAccess, Iterator
{
}
$thing = new myObj();
$thing[$id] = $name;
Then, in the code that consumes this data, you can use the same "old style" array code that you had before:
// Here, $thing can be either an array or an instance of myObj
function doSomething($thing) {
foreach ($thing as $id => $name) {
// ....
}
}
You can use retyping:
class Num
{
private $num;
public function __construct($num)
{
$this->num = $num;
}
public function __toString()
{
return (string) $this->num;
}
}
$n1 = new Num(5);
$n2 = new Num(10);
$n3 = (int) (string) $n1 + (int) (string) $n2; // 15
__toString() exists as a magic method.
http://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.tostring
__toInt() does not.

how can I get around no arrays as class constants in php?

I have a class with a static method. There is an array to check that a string argument passed is a member of a set. But, with the static method, I can't reference the class property in an uninstantiated class, nor can I have an array as a class constant.
I suppose I could hard code the array in the static method, but then if I need to change it, I'd have to remember to change it in two places. I'd like to avoid this.
You can create a private static function that will create the array on demand and return it:
class YourClass {
private static $values = NULL;
private static function values() {
if (self::$values === NULL) {
self::$values = array(
'value1',
'value2',
'value3',
);
}
return self::$values;
}
}
I put arrays in another file and then include the file wherever I need it.
I am having a really really hard time understanding your question. Here is essentially what I understood:
I need to maintain a proper set, where
no two elements are the same.
PHP does not have a set type, not even in SPL! We can emulate the functionality of a set but any solution I can think of is not pleasant. Here is what I think is the cleanest:
<?php
class Set {
private $elements = array();
public function hasElement($ele) {
return array_key_exists($ele, $elements);
}
public function addElement($ele) {
$this->elements[$ele] = $ele;
}
public function removeElement($ele) {
unset($this->elements[$ele]);
}
public function getElements() {
return array_values($this->elements);
}
public function countElements() {
return count($this->elements);
}
}
Example usage:
<?php
$animals = new Set;
print_r($animals->getElments());
$animals->addElement('bear');
$animals->addElement('tiger');
print_r($animals->getElements());
$animals->addElement('chair');
$animals->removeElement('chair');
var_dump($animals->hasElement('chair'));
var_dump($animals->countElements());

Categories