0

Imagine having a StdClass like this:

$foo = new stdClass();

$foo->bar = "baz";
$foo->baz = "1";

Now how could I cast $foo->baz to an int? I know I can do the following:

$foo->baz = (int) $foo->baz;

However, this seems too long of a statement. Is there a shorter way to do this?

Tiamo Idzenga
  • 1,006
  • 11
  • 23
  • Suppose doing `$foo->baz = (int) "1";` to begin with isn't applicable here. I assume your use case receives data from `$_GET/$_POST` or are results from a "dumb" extraction, and that's why some of your digits are strings? – Markus AO Jan 17 '21 at 20:18
  • That's exactly the case. I receive data from a POST request, cast that into an object and cast that object into a strongly typed class (using a generic helper function). The cast from the object into the strongly typed class goes wrong if the types aren't right so I need to handle that before. – Tiamo Idzenga Jan 17 '21 at 21:42
  • 1
    I have solved this with a setter in a strongly typed class that confirms types before actually setting them, and throws an exception if a coherent casting can't be done (ie. attempt to set a non-digit string into an int property). There's a bit of overhead, but it's not consequential unless called hundreds of times. Would eliminate the intermediate object/check in your process. – Markus AO Jan 18 '21 at 13:05
  • Thx for the advice. That seems like a logical step. Might be something I would implement if the scope of the application grows. – Tiamo Idzenga Jan 18 '21 at 13:27

2 Answers2

2

The standard "making operations shorter" happens with a user function. As short as it gets:

function as_int(&$var) {
    $var = (int) $var;
}

as_int($foo->baz);

With this function, we pass in just the object's property by reference. A slightly longer form, useful if you may need access to the object inside the function, would be:

function prop_as_int(object $obj, string $prop) {
    $obj->$prop = (int) $obj->$prop;
}

prop_as_int($foo, 'baz');

Either function results in:

var_dump($foo);

/* 
object(stdClass)#2 (2) {
  ["bar"]=>  string(3) "baz"
  ["baz"]=>  int(1)
}
 */

There is no return statement or referenced argument (&$obj) here, since objects are passed by reference by default in PHP. You could add in some sanity checks, e.g. that the value is numeric/digits, that the property exists (in the second example), etc. into your function.

You could also use a custom class and an int-typed property instead of stdClass; with a setter that handles type conformance, assuming you use strict types. If you don't use strict types, getting properties you add coerced into the correct datatype is really simple:

class Foo {
    public int $baz;
}

$foo = new Foo();
$foo->baz = "1";

var_dump($foo);

/* 
object(Foo)#2 (1) {
  ["baz"]=> int(1)
}
*/

As you can see, the value was automatically converted to the declared type of the property. If you had declare(strict_types = 1); in use, it would however result in TypeError: Typed property Foo::$baz must be int, string used, and the solution would be a type-confirming/coercing setter. Implementing that efficiently is however beyond the scope of this answer.

The downside of this approach is incurring the overhead of a function call, which is inconsequential if not called thousands of times. You'd want to do this if you find yourself frequently repeating an op in different contexts and want to cut down on typing. However if you do this in a loop iterating a large number of objects, spell it out. The performance gain is worth the trouble.

Finally, if your property names are unpredictable and you want to keep using stdClass, but you know that any variable consisting of all digits/integers should be datatype int... Then, once the object has been saturated with data, iterate over the properties with a check for !is_int($prop) && ctype_digit($prop), which will match all non-int properties that consist of digits only, and cast them to int datatype. You'll want to turn that into a function, too: that's for homework.

Markus AO
  • 4,771
  • 2
  • 18
  • 29
  • Insightful answer. I appreciate how you laid out multiple options for this problem. Adding a helper function with validation seems like a sensible step. – Tiamo Idzenga Jan 17 '21 at 21:43
1

This solution is shorter and uses a trick.

$foo->baz += 0;

However, this solution is difficult to understand and has only drawbacks in practice. It is therefore not recommended.

jspit
  • 7,276
  • 1
  • 9
  • 17
  • It will be longer when you have to add `// trick coercing "baz" into int` after each instance in your source code. `-= 0` would work too, making +/- a matter of style. Heck, even `*= 1` and `/= 1` work! – Markus AO Jan 17 '21 at 20:48