Count function surprise in PHP 7

Generally speaking , it’s rare to not find the famous function count in a recipe code . In fact it’s pratical for a lot of use cases , but there is some challenge in case you are migrating to some upper PHP version .

problematic

If and only if you work on PHP version less than 7 , and want to push up your code PHP version to the latest version (staring from PHP 7.2 released on 30 November 2017) , you code is very likely to fail .

The warning

Warning:  count(): Parameter must be an array or an object that implements Countable in [...][...] on line X .

The warning above can be throwed while executing your PHP script for the first time after migrating your php version .

It’s clearly evident that the warning is light , and trying to inform you that the count function should have as argument type only an Array or an object that implement the built-in PHP Interface Countable .

Why this work before ?

For the reason that the count function accept null as argument type in the previous versions .

Application

<?php
  //Running this code on versions less than 7.2.0
  if(count(null)){
    echo 'good morning  Inferom';
  }  
  else{
    echo 'good night Inferom ';
  }
  // output : good night Inferom 

Since the count function accept null in the oldest version , the code run without warning .

<?php
  //Running this code on version  7.2.4
  if(count(null)){
    echo 'good morning  Inferom';
  }
  else{
    echo 'good night Inferom ';
  }
  // output :Warning:  count(): Parameter must be an array or an object that implements Countable in [...][...] on line 5 . 

A twice Lines to illustrate the issue source don’t matter , but how to deal with that warning incase you are dealing with a huge project with tones of lines ?

Solutions

Be sure for the injected value to be suitable for the the count prototype requirements

<?php
//Running this code on version  7.2.4
class Post{

    public $comments;
    
    public function __get($attr){
        return $this->$attr;
    }
    
    public function addComment($comment){
        $this->comments [] = $comment; 
    }
    
    public function ifHasComments(){
        
        if(count($this->comments)>0){
            echo 'This post contain comments';
        }
        else{
            echo  'no comment on this post';
        }
    }

}

$post = new Post;
$post->ifHasComments();

 // output :Warning:  count(): Parameter must be an array or an object that implements Countable in [...][...] on line 24 . 

The comments attribute is null when instanciate the Post object . to solve that , think to initialize the comments in the construct as the snippet bellow


    public function __construct(){
        $this->comments = [];
    }

Here , you are sure that all the new instances of post class have the comments attribute initialized with an empty array which will avoid the count warning .

Ask if the count function can be skipped

In the same case , if you want just to verify if there is comments inside , don’t use count , use instead a simple test as

public function ifHasComments(){
        if($this->comments){
            echo 'This post contain comments';
        }
        else{
            echo  'no comment on this post';
        }
    }

Prefix by a condition

Some hard case can’t be resolved with the avobe propositons .

let’s suppose that you are using a third party library contain a function with some logic similar to :

public function getRoles(){
  // ... some code
  if($this->mode){
    return $this->roles; // we suppose that the roles is an array 
  }
  return null;
}

your code call can be for example like

public fuction hasFullAccess(){
  if(count($this->user->getRoles()) > 3){
   // the user has full access .
  }
  else{
  // no full access for the current user .
  }
}

Since the gerRoles return null for some cases , you function is vulnerable and you should to save it using a preffix condition .

public fuction hasFullAccess(){
   if( is_array($this->user->getRoles()) && count($this->user->getRoles()) > 3){
    // the user has full access .
   }
   else{
   // no full access for the current user .
   }
 }

This way , the function will never throw the annoying count warning in case the getRoles return null at worst .

Adapte mocking test

Sometimes , tests failed in case you are testing a function that use the magic count function on something that should be mocked.

class SecurityService {
   
    private $userService; 
    
    public function hasRoles() {     
        if(count($this->userService->getRoles())){
         return true;    
        }
        else{
            return false;
        }   
    } 
}

class test extends TestCase{
    
    	/** @test */
	public function testHasRolesShouldReturnFalse()
    {
        $hasRoles = $this->securityService->hasRoles();
        $this->assertEquals(false, $hasRoles);
        
    }
    
}

In the first step , we are not mocking the getRoles of userService and assert to get false for the result of hasRoles in SecurityService . Unfortunately , the warning count will appear for the reason that a null will be injected to count argument . to solve that , it needs to mock it and control the result of the getRoles .

public function testHasRolesShouldReturnTrue()
    {

        $this->userServiceMock
            ->expects($this->once())
            ->method('getRoles')
            ->will($this->returnValue([]));

        $hasRoles = $this->securityService->hasRoles();
        $this->assertEquals(true, $hasRoles);
        
    }

It’s similar to the first proposition somehow .Only context is vairous.

Hope his guide help you to solve your count issue .Don’t hesitate to mention more context if not reported in this tuto.

inferom
web developer passionate about new technologies