briannesbitt / Carbon

A simple PHP API extension for DateTime.
https://carbon.nesbot.com/
MIT License
16.57k stars 1.28k forks source link

production.ERROR: Unsupported operand types: int + array /src/Carbon/Traits/Units.php:136 #2298

Closed trevorpan closed 3 years ago

trevorpan commented 3 years ago

Hello,

I encountered an issue with the following code:

trait Units
{
    /**
     * Add seconds to the instance using timestamp. Positive $value travels
     * forward while negative $value travels into the past.
     *
     * @param string $unit
     * @param int    $value
     *
     * @return static
     */
    public function addRealUnit($unit, $value = 1)
    {
        switch ($unit) {
            // @call addRealUnit
            case 'micro':

            // @call addRealUnit
            case 'microsecond':
                /* @var CarbonInterface $this */
                $diff = $this->microsecond + $value;
                $time = $this->getTimestamp();
                $seconds = (int) floor($diff / static::MICROSECONDS_PER_SECOND);
                $time += $seconds;
                $diff -= $seconds * static::MICROSECONDS_PER_SECOND;
                $microtime = str_pad("$diff", 6, '0', STR_PAD_LEFT);
                $tz = $this->tz;

                return $this->tz('UTC')->modify("@$time.$microtime")->tz($tz);

            // @call addRealUnit
            case 'milli':
            // @call addRealUnit
            case 'millisecond':
                return $this->addRealUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND);

                break;

            // @call addRealUnit
            case 'second':
                break;

            // @call addRealUnit
            case 'minute':
                $value *= static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'hour':
                $value *= static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'day':
                $value *= static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'week':
                $value *= static::DAYS_PER_WEEK * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'month':
                $value *= 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'quarter':
                $value *= static::MONTHS_PER_QUARTER * 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'year':
                $value *= 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'decade':
                $value *= static::YEARS_PER_DECADE * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'century':
                $value *= static::YEARS_PER_CENTURY * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            // @call addRealUnit
            case 'millennium':
                $value *= static::YEARS_PER_MILLENNIUM * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;

                break;

            default:
                if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) {
                    throw new UnitException("Invalid unit for real timestamp add/sub: '$unit'");
                }

                return $this;
        }

        /* @var CarbonInterface $this */
 //this line       return $this->setTimestamp((int) ($this->getTimestamp() + $value)); 
    }

Carbon version: *versions : 2.46.0**

PHP version: PHP 8.0.3 (cli) (built: Mar 5 2021 07:54:13) ( NTS )

I expected to get:

No problems.

But I actually get:

[2021-03-09 18:41:05] production.ERROR: Unsupported operand types: int + array {"exception":"[object] (TypeError(code: 0): Unsupported operand types: int + array at ..../vendor/nesbot/carbon/src/Carbon/Traits/Units.php:136)
[stacktrace]

This is used with Laravel 8.31 The above error crashes the site in production but not locally. I wasn't reading the $value as an array, but that was error. Not sure how to handle that, as you've declared in the doc block (int) and then typed it on line 136.

kylekatarnls commented 3 years ago

Hello, 👋

The code we need to analyze your issue is not the library method source code but how you call this method give an example with static values calling the method that trigger the error when we run it on https://try-carbon.herokuapp.com/

Then provide expected output: the result you expected for this call in this given example:

Thanks.

trevorpan commented 3 years ago

Hi Kyle,

Just to be clear, my code is not directly calling Carbon. This is the Laravel framework.

Wasn't sure to get exactly where or how it was called. The error's stack trace references:

#0 /..../vendor/nesbot/carbon/src/Carbon/Traits/Date.php(2532): Carbon\\Carbon->addRealUnit()
#1 /..../vendor/laravel/framework/src/Illuminate/Support/InteractsWithTime.php(37): Carbon\\Carbon->__call()

Here's the line, I tried assigning

$delay = 0;
Carbon::now()->addRealSeconds($delay)->getTimestamp(); 

in your handy program but the output was nothing.

Here's the method being called

    /**
     * Get the "available at" UNIX timestamp.
     *
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @return int
     */
    protected function availableAt($delay = 0)
    {
        $delay = $this->parseDateInterval($delay);

        return $delay instanceof DateTimeInterface
                            ? $delay->getTimestamp()
                            : Carbon::now()->addRealSeconds($delay)->getTimestamp();
    }

Now, the first stack trace references this method: public function addRealUnit($unit, $value = 1) (Carbon).

Wondering if that's a better method to call?

Also, is the program you sent using PHP 8?

kylekatarnls commented 3 years ago

Yes, https://try-carbon.herokuapp.com/ run PHP 8.0.2 as you can see if you type echo PHP_VERSION;

According to the error message you provided, an array is passed instead of an int, for instance you can reproduce the error with:

echo Carbon::now()->addRealSeconds([])->getTimestamp();

But [] is not expected to work, so this error is actually the correct behavior, and you probably need to check what you are passing to ->availableAt() or check what is returned from $delay = $this->parseDateInterval($delay); which is likely not what it should be (e.g. a number of second).

trevorpan commented 3 years ago

Hi @kylekatarnls well.

I thought it was an php8.0 error, as I'd just read up on the new features and specifically the stronger typing.

Was hoping my mistake might make an improvement to Carbon or Laravel .... instead I misconfigured the queue!

After following each and every stack I realized that it was on deploy the php artisan horizon:terminate would call the those methods, but because the config/database.php referenced phpredis not predis it gave it bad data and the error.

Anyhow, thank you very much... I'll certainly use the try-carbon app in the future if I find some Carbon related items..

trevorpan commented 3 years ago

just wanted to make an additional note here for anyone coming on this.

The real issue was not phpredis vs. predis it was this line: 'retry_after' => ['60', '120', '240'], added to config/queue.php. This is where the array originated.

It's a little confusing because you can use exponential backoff as a property on a queued job for example, but the config/queue.php file accepts only a single value, such as: 'retry_after' => 90,