How to check if not instance of some class in symfony2 - php

I want to execute some functions if entity is member of few classes but not some.
There is a function called instanceof.
But is there something like
if ($entity !instanceof [User,Order,Product])

Give them a common interface and then
if (!$entity instanceof ShopEntity)
or stay with
if (!$entity instanceof User && !$entity instanceof Product && !$entity instanceof Order)
I would avoid creating arbitrary functions just to save some characters at a single place. On the other side if you need it "too often", you may have a design flaw? (In the meaning of "too much edge cases" or such)

PHP manual says: http://php.net/manual/en/language.operators.type.php
!($a instanceof stdClass)
This is just a logical and "grammatically" correct written syntax.
!$class instanceof someClass
The suggested syntax above, though, is tricky because we are not specifying which exactly is the scope of the negation: the variable itself or the whole construct of $class instanceof someclass. We will only have to rely on the operator precendence here [Edited, thanks to #Kolyunya].

PHP Operator Precedence
instanceof operator is just before negation then this expression:
!$class instanceof someClass
is just right in PHP and this do that you expect.

This function should do it:
function isInstanceOf($object, Array $classnames) {
foreach($classnames as $classname) {
if($object instanceof $classname){
return true;
}
}
return false;
}
So your code is
if (!isInstanceOf($entity, array('User','Order','Product')));

function check($object) {
$deciedClasses = [
'UserNameSpace\User',
'OrderNameSpace\Order',
'ProductNameSpace\Product',
];
return (!in_array(get_class($object), $allowedClasses));
}

Or you can try these
$cls = [GlobalNameSpace::class,\GlobalNameSpaceWithSlash::class,\Non\Global\Namespace::class];
if(!in_array(get_class($instance), $cls)){
//do anything
}

Related

Type hint for any object in function calls

How do I explicitly type-check in PHP that I received an object as a function argument?
This would satisfy my requirements only if it was a working example:
function example(object $argument)
{
$argument->property = 'something';
}
(The latter gives me "Argument must be an instance of object, instance of ... given")
is_object() does not constitute type checking because I want a program fail and fail hard in case if it wasn't provided an object.
On can argue that I can do assert(is_object($argument)) but assertions could be turned off thus avoiding this type of type checking. Throwing in any conditional expression throwing exceptions doesn't make it any better.
TL;DR - According to the manual, such a hint is not (yet?) possible.
You could use an stdClass hint to test for a generic object:
function example(\stdClass $argument)
{
echo $argument->foo;
}
Or make all your classes derive from your own base class or implement some interface:
function example(MyBaseClass $argument)
{
}
Or, simply check the variable type manually using is_object():
function example($argument)
{
if (!is_object($argument)) {
trigger_error("Invalid argument", E_USER_ERROR);
// or: throw new InvalidArgumentException();
}
// it's an object
}
That said, checking for any object doesn't provide much information to the application on the whole; an explicit class to test against would be the way to go.
Checking the function parameter is called type hinting:
class ConcreteClass {...}
function(ConcreteClass obj) {...}
This is for checking generally:
if ($obj instanceof ConcreteClass) {
//do stuff
}
or
if (is_object($obj)) {
//do stuff
}
I'd prefer type hinting and instanceof.

Is there another way to create an object with a classname returned by a method?

I have this method :
public function getInstance()
{
$classname = $this->getFullyQualifiedClassName();
return new $classname();
}
Is there a way to write this without the $classname variable, which gets used only once?
You could use Reflection:
$reflectionClass = new ReflectionClass('SomeClassName');
$reflectionClass->newInstanceArgs(array(1, 2, 3));
But as far as I know, that will be a lot slower than the solution you already have. In a perfect world you could just write something like this:
return new ( $this->getFullyQualifiedClassName() )(); // THIS DOES NOT WORK!!!
But unfortunately PHP's syntax isn't very flexible, and therefore you have to create that ugly variable thats troubling you.
I just realized that there is another (very ugly) way to do it: eval:
return eval("return new ".$this->getFullyQualifiedClassName()."();");
I do not suggest that you used this though, because of the security risk that comes with using eval.
Give your getFullyQualifiedClassName method a parameter whether return object or string.
public function getFullyQualifiedClassName($return_object = false) {
//............
return $return_object ? new $classname() : $classname;
}
then
public function getInstance()
{
return $this->getFullyQualifiedClassName(true);
}
But I think this way is not better than your have a $classname in your getInstance method if your getFullyQualifiedClassName just need to return string of the class name.
Edit:
As #x3ro said, it violates the method name, another way is to use a Help method, but it still worth little. Actually the $classname is not so ugly.
public function getInstance()
{
return Util::NewObjectFromName($this->getFullyQualifiedClassName());
}

How do you enforce your PHP method arguments?

How do you validate/manage your PHP method arguments and why do you do it this way?
Well, assuming that you're talking about type-checking method arguments, it depends:
If it's expecting an object, I use type-hinting with an interface:
public function foo(iBar $bar)
If it's expecting an array only, I use type-hinting with the array keyword.
public function foo(array $bar)
If it's expecting a string, int, bool or float, I cast it:
public function foo($bar) {
$bar = (int) $bar;
}
If it's expecting mixed, I just check in a cascade:
public function foo($bar) {
if (is_string($bar)) {
//handle string case
} elseif (is_array($bar)) {
//...
} else {
throw new InvalidArgumentException("invalid type");
}
}
Lastly, if it's expecting an iterable type, I don't use type-hinting. I check if it's an array first, then re-load the iterator:
public function foo($bar) {
if (is_array($bar)) {
$bar = new ArrayIterator($bar);
}
if (!$bar instanceof Traversable) {
throw new InvalidArgumentException("Not an Iterator");
}
}
If it's expecting a filename or directory, just confirm it with is_file:
public function foo($bar) {
if (!is_file($bar)) {
throw new InvalidArgumentException("File doesn't exist");
}
}
I think that handles most of the cases. If you think of any others, I'll gladly try to answer them...
Typechecking is something you should do at the development stage, not in production. So the appropriate syntactic feature for that would be:
function xyz($a, $b) {
assert(is_array($a));
assert(is_scalar($b));
However I'll try to avoid it, or use type coercion preferrably. PHP being dynamically typed does quite well adapting to different values. There are only few spots where you want to turndown the basic language behaviour.

Is there an is_iterable or similar function for PHP? [duplicate]

I have a lot of functions that either have type hinting for arrays or use is_array() to check the array-ness of a variable.
Now I'm starting to use objects that are iterable. They implement Iterator or IteratorAggregate. Will these be accepted as arrays if they pass through type hinting, or undergo is_array()?
If I have to modify my code, is there a generic sort of is_iterable(), or must I do something like:
if ( is_array($var) OR $var instance_of Iterable OR $var instanceof IteratorAggregate ) { ... }
What other iterable interfaces are out there?
I think you mean instanceof Iterator, PHP doesn't have an Iterable interface. It does have a Traversable interface though. Iterator and IteratorAggregate both extend Traversable (and AFAIK they are the only ones to do so).
But no, objects implementing Traversable won't pass the is_array() check, nor there is a built-in is_iterable() function. A check you could use is
function is_iterable($var) {
return (is_array($var) || $var instanceof Traversable);
}
To be clear, all php objects can be iterated with foreach, but only some of them implement Traversable. The presented is_iterable function will therefore not detect all things that foreach can handle.
PHP 7.1.0 has introduced the iterable pseudo-type and the is_iterable() function, which is specially designed for such a purpose:
This […] proposes a new iterable pseudo-type. This type is analogous to callable, accepting multiple types instead of one single type.
iterable accepts any array or object implementing Traversable. Both of these types are iterable using foreach and can be used with yield from within a generator.
function foo(iterable $iterable) {
foreach ($iterable as $value) {
// ...
}
}
This […] also adds a function is_iterable() that returns a boolean: true if a value is iterable and will be accepted by the iterable pseudo-type, false for other values.
var_dump(is_iterable([1, 2, 3])); // bool(true)
var_dump(is_iterable(new ArrayIterator([1, 2, 3]))); // bool(true)
var_dump(is_iterable((function () { yield 1; })())); // bool(true)
var_dump(is_iterable(1)); // bool(false)
var_dump(is_iterable(new stdClass())); // bool(false)
I actually had to add a check for stdClass, as instances of stdClass do work in foreach loops, but stdClass does not implement Traversable:
function is_iterable($var) {
return (is_array($var) || $var instanceof Traversable || $var instanceof stdClass);
}
I use a simple (and maybe a little hackish) way to test for "iterability".
function is_iterable($var) {
set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext)
{
throw new \ErrorException($errstr, null, $errno, $errfile, $errline);
});
try {
foreach ($var as $v) {
break;
}
} catch (\ErrorException $e) {
restore_error_handler();
return false;
}
restore_error_handler();
return true;
}
When you try to loop a non iterable variable, PHP throws a warning. By setting a custom error handler prior the attempt to iterate, you can transform an error into an exception thus enabling you to use a try/catch block. Afterwards you restore the previous error handler to not disrupt the program flow.
Here's a small test case (tested in PHP 5.3.15):
class Foo {
public $a = 'one';
public $b = 'two';
}
$foo = new Foo();
$bar = array('d','e','f');
$baz = 'string';
$bazinga = 1;
$boo = new StdClass();
var_dump(is_iterable($foo)); //boolean true
var_dump(is_iterable($bar)); //boolean true
var_dump(is_iterable($baz)); //boolean false
var_dump(is_iterable($bazinga)); //bolean false
var_dump(is_iterable($boo)); //bolean true
Unfortunately you won't be able to use type hints for this and will have to do the is_array($var) or $var instanceof ArrayAccess
stuff. This is a known issue but afaik it is still not resolved. At least it doesn't work with PHP 5.3.2 which I just tested.
You CAN use type hinting if you switch to using iterable objects.
protected function doSomethingWithIterableObject(Iterator $iterableObject) {}
or
protected function doSomethingWithIterableObject(Traversable $iterableObject) {}
However, this can not be used to accept iterable objects and arrays at the same time. If you really want to do that could try building a wrapper function something like this:
// generic function (use name of original function) for old code
// (new code may call the appropriate function directly)
public function doSomethingIterable($iterable)
{
if (is_array($iterable)) {
return $this->doSomethingIterableWithArray($iterable);
}
if ($iterable instanceof Traversable) {
return $this->doSomethingIterableWithObject($iterable);
}
return null;
}
public function doSomethingIterableWithArray(array $iterable)
{
return $this->myIterableFunction($iterable);
}
public function doSomethingIterableWithObject(Iterator $iterable)
{
return $this->myIterableFunction($iterable);
}
protected function myIterableFunction($iterable)
{
// no type checking here
$result = null;
foreach ($iterable as $item)
{
// do stuff
}
return $result;
}

Is it possible to overload operators in PHP?

Specifically, I would like to create an Array class and would like to overload the [] operator.
If you are using PHP5 (and you should be), take a look at the SPL ArrayObject classes. The documentation isn't too good, but I think if you extend ArrayObject, you'd have your "fake" array.
EDIT: Here's my quick example; I'm afraid I don't have a valuable use case though:
class a extends ArrayObject {
public function offsetSet($i, $v) {
echo 'appending ' . $v;
parent::offsetSet($i, $v);
}
}
$a = new a;
$a[] = 1;
Actually, the optimal solution is to implement the four methods of the ArrayAccess interface:
http://php.net/manual/en/class.arrayaccess.php
If you would also like to use your object in the context of 'foreach', you'd have to implement the 'Iterator' interface:
http://www.php.net/manual/en/class.iterator.php
PHP's concept of overloading and operators (see Overloading, and Array Operators) is not like C++'s concept. I don't believe it is possible to overload operators such as +, -, [], etc.
Possible Solutions
Implement SPL ArrayObject (as mentioned by cbeer).
Implement Iterator (if ArrayObject is too slow for you).
Use the PECL operator extension (as mentioned by Benson).
For a simple and clean solution in PHP 5.0+, you need to implements the ArrayAccess interface and override functions offsetGet, offsetSet, offsetExists and offsetUnset. You can now use the object like an array.
Example (in PHP7+):
<?php
class A implements ArrayAccess {
private $data = [];
public function offsetGet($offset) {
return $this->data[$offset] ?? null;
}
public function offsetSet($offset, $value) {
if ($offset === null) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->data[$offset]);
}
public function offsetUnset($offset) {
unset($this->data[$offset]);
}
}
$obj = new A();
$obj[] = 'a';
$obj['k'] = 'b';
echo $obj[0], $obj['k']; // print "ab"
It appears not to be a feature of the language, see this bug. However, it looks like there's a package that lets you do some sort of overloading.
Put simply, no; and I'd suggest that if you think you need C++-style overloading, you may need to rethink the solution to your problem. Or maybe consider not using PHP.
To paraphrase Jamie Zawinski, "You have a problem and think, 'I know! I'll use operator overloading!' Now you have two problems."

Categories