13

Prior to PHP 7.2 using count() on a scalar value or non-countable object would return 1 or 0.

For example: https://3v4l.org/tGRDE

var_dump(count(123)); //int(1)
var_dump(count(new stdclass)); //int(1)
var_dump(count('hello world'));  //int(1)
var_dump(count(null));  //int(0)

In the updates to PHP 7.2+, using count() as demonstrated above will emit a warning message.

An E_WARNING will now be emitted when attempting to count() non-countable types (this includes the sizeof() alias function).

Warning: count(): Parameter must be an array or an object that implements Countable [sic]

As a result many popular Frameworks will elevate the E_WARNING and throw an Exception instead.

[ErrorException] count(): Parameter must be an array or an object that implements Countable

The error elevation behavior was also commented on by the PHP developers.

Environments that display warnings or convert them to more severe errors/exceptions would be affected, but this should just bring attention to a bug in the code.

How can the previous behavior of count() be achieved in PHP 7.2+, that does not emit an E_WARNING, without modifying the error reporting setting and without using @count()?

Toskan
  • 13,911
  • 14
  • 95
  • 185
  • strlen()... count is for arrays or objects that implement Countable.. – Lawrence Cherone Apr 04 '18 at 23:45
  • @LawrenceCherone count used to work for both. strlen does only work for strings and numbers. So all code bases have to be rewritten for this? why? add a check everywhere? write your own count function? sad – Toskan Apr 04 '18 at 23:48
  • 2
    your passing a string/number, wrong function to use in first place.. – Lawrence Cherone Apr 04 '18 at 23:49
  • Unfortunately you can't please everyone when you make progress, make a `countLegacy()` function yourself and do a simple find and replace through your codebase. – Scuzzy Apr 04 '18 at 23:49
  • In stock PHP this returns as expected, but with a warning generated. You've got something additional that is causing this to be thrown as an ErrorException. Maybe change that instead? Though the real question is: Why are you even doing that in the first place? – Sammitch Apr 04 '18 at 23:51
  • @Scuzzy @Sammitch who said I do use it? this is all over the place. Everyone does it. Why not do it? Progress? this is breaking all kind of stuff, for absolutely no gain. Why not change the `echo` function as well? python just about to recover from their misery, we heading straight there where they are coming from. It's a joke – Toskan Apr 04 '18 at 23:53
  • Try this ;p https://3v4l.org/S28K7 – Lawrence Cherone Apr 04 '18 at 23:54
  • just google for >count(): Parameter must be an array or an object that implements Countable – Toskan Apr 04 '18 at 23:54
  • The link says `An E_WARNING will now be emitted` right? A warning and not a fatal, thus giving developers time to react. – Scuzzy Apr 04 '18 at 23:55
  • the most popular frameworks fatal on warnings and notices, as is good practice – Toskan Apr 05 '18 at 00:04
  • `Everyone does it. Why not do it? ` Not covering best-practices will yield undesirable/unexpected results, which is what you're seeing now in the transition to 7.2 and using `count()` in an [unintended fashion as documented since 2013](https://web.archive.org/web/20131001171213/http://php.net/manual/en/function.count.php) with `array_or_countable`. I suggest instead to force the expected data type by utilizing type-casting [`count((array) $value)`](https://3v4l.org/AOqP8) which will have the undesired result of converting the `stdClass` to an array and yield 0 instead of 1. – Will B. Apr 05 '18 at 00:16
  • @fyrye python 3 is far superior than python 2, word. Did they do themselves a favor in changing their `print` function? why change the core `count` function after what, 15 years? now all the libs throw warnings and I need to roll back to 7.1. Can't wait to see they change all other core functions as well. "Best practice" is to be compatible if there is not a very very good reason to not be. – Toskan Apr 05 '18 at 00:26
  • Complaining about your desire to upgrade to PHP 7.2 and the developers practices of making changes to the codebase that caused incompatibilities with your application/libs, Is not conducive to your question, will not change how PHP 7.2 functions and does not belong on SO. Best-Practices in any programming sense is to write predictable code and to know the datatype you are supplying to a given function, along with tests covering its usage. `mcrypt`, `mysql_*` was supported in PHP 4 and 5 but not in 7, etc, All as a means to optimize the core engine for it to perform faster or be more secure. – Will B. Apr 05 '18 at 00:36
  • 4
    I'd roll back to 7.1. The PHP devs should have made 7.2 an 8.0; the commonly accepted guidelines for versioning numbers say to increment the major (first) number when changes break backwards compatibility. It's not unreasonable to expect a minor patch (increment to second number) to not have such breaks. – citrusy Apr 05 '18 at 00:44
  • @fyrye I actually asked a question, nobody addressed it so far. Everyone got lost in emotions – Toskan Apr 05 '18 at 00:48
  • You could also forcibly ignore the warnings [`@count($value)`](https://3v4l.org/QQkPL) in your codebase. Since count would perform the same regardless on any datatype. However this would not account for any vendor libraries you use and should only really be used in preparation of updating the functionality. – Will B. Apr 05 '18 at 01:01
  • @fyrye I cannot help but chuckle. The question was very specific, a function that has as output, input, and acts exactly the same like the old function. There is no space for interpretation. Yes a custom function. Exactly that. So where is the answer? – Toskan Sep 05 '18 at 21:54
  • When it was voted to close, your desired result was no validation on the input, and to work in every case that `count` currently does without raising the `warning` that it does in PHP 7.2+ As there is no definitive way to accomplish this, and can NOT account for the usage of `count()` in your third-party packages, it would only provide opinionated answers. For example : https://3v4l.org/UFieo are three different ways you could achieve an alternative to count. There are significant other methods as well. But ultimately the way you are using count is not supported, and should be refactored. – Will B. Sep 05 '18 at 22:55
  • well thanks for the link, now I don't understand why you couldn't put those into an answer, and maybe select a best option based on performance and long term maintainability? I really don't see what the problem is at all. – Toskan Sep 05 '18 at 23:06
  • Your question is not opinionated, the answers will be. The aspect of "Select a best option" will be based on opinion/desired use. Because each one of those examples are specific to a use-case. For example ignoring the warning using `@` may not be desired. In addition to your case of the Framework that uses `count` in its underlying code as well. None of the examples would resolve the issue you have with `count` in the Framework. This would lead to other answers to resolve the framework issue. Such as a overriding `count` from the PHP core library and many others that would not be definitive. – Will B. Sep 05 '18 at 23:15
  • I never asked to solve the framework issue. Using `@` is bad because it will not maintain long term support. Maybe one day, they will throw an error instead of a warning. For some reason, you are just trying to avoid a very valid question. "There is no better and worse, there is only black and white". I think it is silly. It really is. Stackoverflow close vote bots need to stop. – Toskan Sep 06 '18 at 00:52
  • 1
    You are literally asking for "What is the best way to emulate count()", which is not a valid question for SO. As it will garner multiple answers that produce opinionated results. In your opinion `@` is bad, despite it producing the desired result, due to your very valid concerns. Long term maintainability is also subjective. It all boils down to you wanting to continue using count in an unintended manner that has been documented since 2013. The best approach IMO would be to refactor your code to be compatible with 7.2+, for the reasons you noted; that in the future it may throw an exception – Will B. Sep 06 '18 at 01:32
  • 1
    I'm just wondering what happened to stackoverflow. Anyway, thanks for the link to the answer. A pity it cannot be posted here, I would have loved to get feedback on those solutions, or to give feedback. A real pity. – Toskan Sep 06 '18 at 06:45
  • 1
    This is why people use stackoverflow - to find solutions to problems - not to just criticize a question that is very important to many developers. I appreciate this question and all the answers. – Sol Nov 07 '20 at 04:41

3 Answers3

15

As we discussed, there are multiple ways to achieve the original functionality of count() and not emit an E_WARNING.

In PHP 7.3 a new function was added is_countable, specifically to address the E_WARNING issue and the prevalence of applications adopting is_array($var) || $var instanceof \Countable in their code.

In PHP 7.2, a Warning was added while trying to count uncountable things. After that, everyone was forced to search and change their code, to avoid it. Usually, the following piece of code became standard:

if (is_array($foo) || $foo instanceof Countable) { // $foo is countable }

https://wiki.php.net/rfc/is-countable


Custom Function Replacement

For that reason it seems the best method of resolving the issue, is to perform the same functionality that PHP is doing with is_countable and creating a custom function to ensure compliance with the original functionality of count.

Example https://3v4l.org/8M0Wd

function countValid($array_or_countable, $mode = \COUNT_NORMAL)
{
    if (
        (\PHP_VERSION_ID >= 70300 && \is_countable($array_or_countable)) ||
        \is_array($array_or_countable) ||
        $array_or_countable instanceof \Countable
    ) {
        return \count($array_or_countable, $mode);
    }

    return null === $array_or_countable ? 0 : 1;
}

Result

array: 3
string: 1
number: 1
iterator: 3
countable: 3
zero: 1
string_zero: 1
object: 1
stdClass: 1
null: 0
empty: 1
boolt: 1
boolf: 1

Notice: Undefined variable: undefined in /in/8M0Wd on line 53
undefined: 0

Shim is_countable() function

Using the above replacement function, it is also possible to shim is_countable in PHP <= 7.2, so it is only used when needed, with minimal overhead.

Example https://3v4l.org/i5KWH

if (!\function_exists('is_countable')) {
    function is_countable($value)
    {
        return \is_array($value) || $value instanceof \Countable;
    }
}

function countValid($array_or_countable, $mode = \COUNT_NORMAL)
{
    if (\is_countable($array_or_countable)) {
        return \count($array_or_countable, $mode);
    }

    return null === $array_or_countable ? 0 : 1;
}

Ignore count() Warnings

As the functionality of count() has not changed and not did not typically emit warnings in the past. An alternative to using a custom function, is to ignore the warning outright by using the @ Error Control Operator

Warning: This approach has the impact of treating undefined variables as NULL and not displaying Notice: Undefined variable: message.

Example https://3v4l.org/nmWmE

@count($var);

Result

array: 3
string: 1
number: 1
iterator: 3
countable: 3
zero: 1
string_zero: 1
object: 1
stdClass: 1
null: 0
empty: 1
boolt: 1
boolf: 1
---
Undefined: 0

Replace count() using APD extension

As for replacing the internal PHP function count(). There is a PECL extension APD (Advanced PHP Debugger), that allows for override_function that works on core PHP functions. As the extension name suggests, it is technically meant for debugging, but is a viable alternative to replacing all instances of count for a custom function.

Example

\rename_function('count', 'old_count');
\override_function('count', '$array_or_countable,$mode', 'return countValid($array_or_countable,$mode);');

if (!\function_exists('is_countable')) {
    function is_countable($value)
    {
        return \is_array($value) || $value instanceof \Countable;
    }
}

function countValid($array_or_countable, $mode = \COUNT_NORMAL)
{
    if (\is_countable($array_or_countable)) {
        return \old_count($array_or_countable, $mode);
    }

    return null === $array_or_countable ? 0 : 1;
}
Will B.
  • 17,883
  • 4
  • 67
  • 69
6

The problem is that calling count() on a scalar or object that doesn't implement the Countable interface returns 1, which can easily hide bugs.

Given the following:

function handle_records(iterable $iterable)
{
    if (count($iterable) === 0) {
        return handle_empty();
    }

    foreach ($iterable as $value) {
        handle_value($value);
    }
}

Passing a Generator that yields nothing would not call handle_empty() nor handle_value().
Also, no indication would be given that neither were called.

By default, this will still return 1, though will additionally log a warning. If anything, this warning will bring attention to potential bugs in the code.

See Counting Non-Countables for further information.

Obsidian Age
  • 41,205
  • 10
  • 48
  • 71
  • well, you are not wrong. The thing is, that is how this function worked. If you don't understand how the function works, it can lead to bugs, agreed. What they did now is they changed a core function because someone didn't like the old function. Breaking a lot of libraries. – Toskan Apr 05 '18 at 00:02
  • I simply set a the variable to array before count like this. if(!is_array($arrayVariable)){ $arrayVariable = array(); } – Apit John Ismail Apr 23 '18 at 12:17
1

You can solve it by using "??"-operator. If the left side is null, the right side will be used. So as we have a empty array, our result will be zero.

count(null ?? [])

Another way would be to typecast it as an array.

count((array) null)
dipser
  • 442
  • 3
  • 5