laravel / prompts

Beautiful and user-friendly forms for your command-line PHP applications.
https://laravel.com/docs/prompts
MIT License
533 stars 94 forks source link

schedule:test does not print any output when running inside a php debian docker image #90

Closed michabbb closed 1 year ago

michabbb commented 1 year ago

Laravel Prompts Version

0.1.9

Laravel Version

10.25.1

PHP Version

8.2.8

Operating System & Version

linux

Terminal Application

xterm-256color (echo $TERM)

Description

i am running my php image

on a linux debian console like this:

docker run --rm -it -v ${PWD}:/app  -w /app --init michabbb/php:8.2.bullseye php artisan schedule:test

and i don´t see any output. if I hit "enter" the first scheduled command will run. i looks like the cursor is working but there is no output at all.

Steps To Reproduce

the dockerfile is very basic:

FROM php:8.2.8-fpm-bullseye

# Set non-interactive mode
ENV DEBIAN_FRONTEND=noninteractive

# Install system dependencies
RUN apt-get update && apt-get install -y \
    libbz2-dev \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libgmp-dev \
    libmagickwand-dev \
    libicu-dev \
    libzip-dev \
    libxml2-dev \
    libxslt-dev \
    libonig-dev \
    libmemcached-dev \
    zlib1g-dev \
    libcurl4-openssl-dev \
    libssl-dev \
    libgearman-dev \
    libsqlite3-dev \
    git \
    unzip

# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd

# Install Gearman extension from source
RUN curl -L -o /tmp/gearman.tar.gz https://github.com/php/pecl-networking-gearman/archive/8fb88d5a97111a7e8f0dc67553c387b49f047e53.tar.gz \
    && mkdir -p /tmp/gearman \
    && tar -xf /tmp/gearman.tar.gz -C /tmp/gearman --strip-components=1 \
    && rm /tmp/gearman.tar.gz \
    && ( \
        cd /tmp/gearman \
        && phpize \
        && ./configure \
        && make -j$(nproc) \
        && make install \
    ) \
    && rm -r /tmp/gearman 
#   && docker-php-ext-enable gearman

# Install PHP extensions
RUN docker-php-ext-install \
    bcmath \
    bz2 \
    calendar \
    gd \
    gmp \
    intl \
    mysqli \
    opcache \
    pcntl \
    pdo_mysql \
    soap \
    sockets \
    xsl \
    zip

# Install PECL extensions
RUN pecl install imagick \
    && pecl install memcached \
    && pecl install redis \
    && pecl install xdebug \
    && docker-php-ext-enable imagick redis

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Clean up
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
michabbb commented 1 year ago

btw...

// app/Providers/AppServiceProvider.php
public function boot(Request $request): void
{
   \Laravel\Prompts\Prompt::fallbackWhen(true);
}

is not working for me. there is no output, so to use "schedule:test" I first have to do a "schedule:list" and remember the number I want to call.

jessarcher commented 1 year ago

Hey @michabbb,

I'm having trouble replicating this one. I have Fedora and Podman, but I wouldn't think that matters.

A few questions:

  1. What is the actual terminal application that you are using? E.g. gnome-terminal, kitty, etc.

  2. Did this work for you previously? I.e. prior to Laravel 10.25 and Prompts 1.19?

  3. What is the result of running this command?

    docker run --rm -it -v ${PWD}:/app -w /app --init michabbb/php:8.2.bullseye php -r "var_dump(stream_isatty(STDIN));"
  4. Does it work outside of Docker? I.e. running php artisan schedule:test not in a container?

michabbb commented 1 year ago

thanks for your feedback.

1) I am running a regular Ubuntu 22.04.3 LTS and my konsole is:

$ /usr/bin/konsole -v
konsole 21.12.3

Operating System: Kubuntu 22.04
KDE Plasma Version: 5.24.7
KDE Frameworks Version: 5.92.0
Qt Version: 5.15.3
Kernel Version: 5.15.0-84-generic (64-bit)
Graphics Platform: X11

2) this is my first time using prompts (I guess), because in other projects I am using an older version of laravel, for example 10.3.3 and there I am getting output but in the old way of just pressing the number (without choosing by the cursor).

3)

bool(true)

4) i only work with docker, so i don´t have anything installed on my host, and i prefer to leave it that way ;-) but if all that info does not help, let me know....

pardon.... how can I force laravel NOT using prompts ? as mentioned in my other posting, the "failover" is not working for me. am I doing something wrong ? thanks!!

jessarcher commented 1 year ago

Hi @michabbb,

Your code for disabling prompts looks good to me. I've tested that code locally with your image and can confirm it enables Symfony's prompts instead. Because there's no output, it's hard to know whether Symfony or Laravel Prompts is waiting for input.

You mentioned that you can run schedule:list and remember the number. When you run schedule:test and enter a number (other than 0) and press Enter, does it run the correct schedule? If so, it would seem that you are getting Symfony prompts, because Laravel's select prompt doesn't accept a typed number (only arrow keys).

Are you able to perform the following test:

  1. Create a demo command:
    docker run --rm -it -v ${PWD}:/app -w /app --init michabbb/php:8.2.bullseye php artisan make:command Test
  2. Update the handle method on the generated command (app/Console/Commands/Test.php) to test both prompts:
    dump($this->choice('Test', ['a', 'b', 'c'], 'b'));
    dump(\Laravel\Prompts\select('Test', ['a', 'b', 'c'], 'c'));
  3. Run the command, once without the fallback enabled, and once with it enabled:
    docker run --rm -it -v ${PWD}:/app -w /app --init michabbb/php:8.2.bullseye php artisan app:test
  4. Let me know what output you see - I've specified defaults in that example so you should be able to press Enter if nothing is output.

It's a long shot, but could you also please try updating Prompts (to v0.1.10)?

michabbb commented 1 year ago

Sorry for my late response, I am currently on vacation. I will try this weekend and let you know.

michabbb commented 1 year ago

@jessarcher oh dear.... i have found the problem 😕 I am using my own package to monitor cron-jobs. because your example code is working perfectly fine, I removed everything else, my package included. now I have to find out what exactly in my package causes the issue 🤔 😖

michabbb commented 1 year ago

@jessarcher okay, i think i know where the problem is:

app/Console/Kernel.php

protected function schedule(Schedule $schedule): void
    {
        $schedule->command..................
        Artisan::call('about'); // <---------- this line prevents any output
    }

it may look strange calling an artisan command at this point but I don´t know any other way to intercept into the scheduling process, that´s why I created my package as it is. to get a unique md5 hash of each of my schedule-commands I am calling the command with my new parameter --mutex. so inside my package, I am doing an Artisan::call for each job that is scheduled. so it looks like, calling "any" artisan command at this point breaks the prompt output. i don´t really know if that is a "bug", but it´s necessary for me and my package to intercept the scheduler and get control of things that should happen after something got called.

maybe some more explanation:

when a job gets scheduled by "schedule:run" my "monitor" method at the end of "Kernel::schedule" needs a unique md5 hash for each scheduled job. the way getting that custom "mutex" is to use a trait in each schedule-job so that a new parameter is available for the job: --mutex. so when I have a job, let´s say php artisan testing:test my trait injects the parameter and now I can call php artisan testing:test --mutex to get only a md5 back, I will use to identify the job. that´s the reason why I am calling Artisan::call multiple times inside the Kernel::schedule. of course I have to find a way now to get my hash without calling an Artisan command, but to be honest, I don´t know if that is even possible, so I still hope there is a way to make this work.

the problem I am facing: the md5 is based on the parameters that are used to call the artisan command.

public function getCustomMutex(): string
    {
        $CommandOptions = $this->options();
        unset($CommandOptions['mutex']);

        $LaravelDefaultOptions = $this->getApplication()?->getDefinition()->getOptions();
        $onlyMyOptions         = array_diff_key($CommandOptions, $LaravelDefaultOptions);
        $arguments             = $this->arguments();
        ksort($arguments);
        ksort($onlyMyOptions);

        return md5(
            serialize([
                          'name'      => $this->getName(),
                          'arguments' => $arguments,
                          'options'   => $onlyMyOptions
                      ]));
    }

at the time where I made the package I didn´t see any other way to get a unique hash of the job without calling the job 😏 I hope that explains a bit why I am calling Artisan::call inside the scheduler.

i hope there is a way to fix that....

michabbb commented 1 year ago

for now, i will try to NOT use any Artisan:call in my package. but I would guess, I am not the only one facing this issue. it would be nice, if that would be possible again in the future.....

jessarcher commented 1 year ago

Hey @michabbb,

Glad you figured it out! This sounds like the same issue as #74.

If you're inside a command, you can use $this->call() instead of Artisan::call(), which will restore the output buffer after running another command. It sounds like this might not be an option for your use case if it's not part of a command. You could take a look at the code for that method though and see whether you can replicate the behaviour. Artisan::call() is intended for calling commands from outside of the CLI so it brings its own null output buffer which is then passed to Prompts.

I wonder whether there is an event you could hook into instead? And if not, perhaps one could be added.

michabbb commented 1 year ago

@jessarcher i am sorry, but $this->call(....) is not the solution in my case. you can try yourself.

app/Console/Kernel.php

protected function schedule(Schedule $schedule): void
    {
        $schedule->command..................
        $this->call('about'); // <---------- this line prevents any output
    }

making a call at this point sadly doesn´t change anything 😕 as you said, in this case I don´t see a situation where hooks make any sense, because in this special case, you want full control about how laravel is running scheduled jobs, for that you have to inject your code directly in the schedule command.

is there any other thing that comes to your mind how I can reset the output after doing $this->call(...) or Artisan::call() ?? could Process::run('.....'); maybe be an alternative ? 🤔

jessarcher commented 1 year ago

Hey @michabbb,

I'm not sure I understand why you need to call the command with the --mutex option. Can you not just use md5($event->command) to get a unique hash for each command? Or even use the existing $event->mutexName() method?

Also, I'm not sure whether this would work for your use case, but instead of adding [log] to the description and having your users add $this->monitor($schedule) to their schedule, could you add a macro to the Event class instead?

E.g.

Event::macro('monitor', function ($output = true, $force = false) {
    dump($output, $force);
    dump($this->command);
    dump(md5($this->command));
    dump($this->mutexName());

    $this->before(function () {
        dump('before');
    });

    $this->after(function () {
        dump('after');
    });
});

And then the schedule would look something like this:

protected function schedule(Schedule $schedule): void
{
    $schedule->command('app:send-emails')
             ->everyMinute()
             ->monitor()
             ->description('Send emails every minute');

    $schedule->command('app:send-emails --foo')
             ->everyMinute()
             ->monitor(output: false)
             ->description('Send emails every minute');

    $schedule->command(SendEmails::class, ['--foo'])
             ->everyMinute()
             ->monitor(force: true)
             ->description('Send emails every minute');
}

If I understand your package correctly, this should prevent the need to add traits and method calls to the schedule and commands.

jessarcher commented 1 year ago

Going to close this for now but I'll have a think about the Artisan::call issue as it would be good to be able to use that in a command context without losing the output buffer afterwards.

michabbb commented 1 year ago

Thank so much for all that input. At the time I made that package I wasn't aware of the concept "macro" or at least never thought I could use it here. looks very interesting 😏 I already changed my package as u can see in the referring commit and I am using the "command" as my custom mutex. To be honest, the "--mutex" is a heck but that time, I didn't know it better 🤷‍♂️ 😏

I don't use Laravel's own mutex because it includes the cron-time, which makes sense for Laravel to handle "withoutOverlapping", but in my case I want something that is only unique to the job itself, without the cron. Because if a job runs at two different times, I still need to know it's the same job, so that forced me to create my own mutex in the first time 😏

I will check the macro idea, thanks a lot for pointing me to that direction 👍

And also thanks that you agree, that it would be nice to be able to make artisan calls inside cli calls in general 👍