Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/**
4
 * Hoa
5
 *
6
 *
7
 * @license
8
 *
9
 * New BSD License
10
 *
11
 * Copyright © 2007-2017, Hoa community. All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *     * Redistributions of source code must retain the above copyright
16
 *       notice, this list of conditions and the following disclaimer.
17
 *     * Redistributions in binary form must reproduce the above copyright
18
 *       notice, this list of conditions and the following disclaimer in the
19
 *       documentation and/or other materials provided with the distribution.
20
 *     * Neither the name of the Hoa nor the names of its contributors may be
21
 *       used to endorse or promote products derived from this software without
22
 *       specific prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 */
36
 
37
namespace Psy\Readline\Hoa;
38
 
39
/**
40
 * Manipulate a processus as a stream.
41
 */
42
class ConsoleProcessus extends Stream implements StreamIn, StreamOut, StreamPathable
43
{
44
    /**
45
     * Signal: terminal line hangup (terminate process).
46
     */
47
    const SIGHUP = 1;
48
 
49
    /**
50
     * Signal: interrupt program (terminate process).
51
     */
52
    const SIGINT = 2;
53
 
54
    /**
55
     * Signal: quit program (create core image).
56
     */
57
    const SIGQUIT = 3;
58
 
59
    /**
60
     * Signal: illegal instruction (create core image).
61
     */
62
    const SIGILL = 4;
63
 
64
    /**
65
     * Signal: trace trap (create core image).
66
     */
67
    const SIGTRAP = 5;
68
 
69
    /**
70
     * Signal: abort program, formerly SIGIOT (create core image).
71
     */
72
    const SIGABRT = 6;
73
 
74
    /**
75
     * Signal: emulate instruction executed (create core image).
76
     */
77
    const SIGEMT = 7;
78
 
79
    /**
80
     * Signal: floating-point exception (create core image).
81
     */
82
    const SIGFPE = 8;
83
 
84
    /**
85
     * Signal: kill program (terminate process).
86
     */
87
    const SIGKILL = 9;
88
 
89
    /**
90
     * Signal: bus error.
91
     */
92
    const SIGBUS = 10;
93
 
94
    /**
95
     * Signal: segmentation violation (create core image).
96
     */
97
    const SIGSEGV = 11;
98
 
99
    /**
100
     * Signal: non-existent system call invoked (create core image).
101
     */
102
    const SIGSYS = 12;
103
 
104
    /**
105
     * Signal: write on a pipe with no reader (terminate process).
106
     */
107
    const SIGPIPE = 13;
108
 
109
    /**
110
     * Signal: real-time timer expired (terminate process).
111
     */
112
    const SIGALRM = 14;
113
 
114
    /**
115
     * Signal: software termination signal (terminate process).
116
     */
117
    const SIGTERM = 15;
118
 
119
    /**
120
     * Signal: urgent condition present on socket (discard signal).
121
     */
122
    const SIGURG = 16;
123
 
124
    /**
125
     * Signal: stop, cannot be caught or ignored  (stop proces).
126
     */
127
    const SIGSTOP = 17;
128
 
129
    /**
130
     * Signal: stop signal generated from keyboard (stop process).
131
     */
132
    const SIGTSTP = 18;
133
 
134
    /**
135
     * Signal: continue after stop (discard signal).
136
     */
137
    const SIGCONT = 19;
138
 
139
    /**
140
     * Signal: child status has changed (discard signal).
141
     */
142
    const SIGCHLD = 20;
143
 
144
    /**
145
     * Signal: background read attempted from control terminal (stop process).
146
     */
147
    const SIGTTIN = 21;
148
 
149
    /**
150
     * Signal: background write attempted to control terminal (stop process).
151
     */
152
    const SIGTTOU = 22;
153
 
154
    /**
155
     * Signal: I/O is possible on a descriptor, see fcntl(2) (discard signal).
156
     */
157
    const SIGIO = 23;
158
 
159
    /**
160
     * Signal: cpu time limit exceeded, see setrlimit(2) (terminate process).
161
     */
162
    const SIGXCPU = 24;
163
 
164
    /**
165
     * Signal: file size limit exceeded, see setrlimit(2) (terminate process).
166
     */
167
    const SIGXFSZ = 25;
168
 
169
    /**
170
     * Signal: virtual time alarm, see setitimer(2) (terminate process).
171
     */
172
    const SIGVTALRM = 26;
173
 
174
    /**
175
     * Signal: profiling timer alarm, see setitimer(2) (terminate process).
176
     */
177
    const SIGPROF = 27;
178
 
179
    /**
180
     * Signal: Window size change (discard signal).
181
     */
182
    const SIGWINCH = 28;
183
 
184
    /**
185
     * Signal: status request from keyboard (discard signal).
186
     */
187
    const SIGINFO = 29;
188
 
189
    /**
190
     * Signal: User defined signal 1 (terminate process).
191
     */
192
    const SIGUSR1 = 30;
193
 
194
    /**
195
     * Signal: User defined signal 2 (terminate process).
196
     */
197
    const SIGUSR2 = 31;
198
 
199
    /**
200
     * Command name.
201
     */
202
    protected $_command = null;
203
 
204
    /**
205
     * Command options (options => value, or input).
206
     */
207
    protected $_options = [];
208
 
209
    /**
210
     * Current working directory.
211
     */
212
    protected $_cwd = null;
213
 
214
    /**
215
     * Environment.
216
     */
217
    protected $_environment = null;
218
 
219
    /**
220
     * Timeout.
221
     */
222
    protected $_timeout = 30;
223
 
224
    /**
225
     * Descriptor.
226
     */
227
    protected $_descriptors = [
228
 
229
        1 => ['pipe', 'w'],
230
        2 => ['pipe', 'w'],
231
    ];
232
 
233
    /**
234
     * Pipe descriptors of the processus.
235
     */
236
    protected $_pipes = null;
237
 
238
    /**
239
     * Seekability of pipes.
240
     */
241
    protected $_seekable = [];
242
 
243
    /**
244
     * Start a processus.
245
     */
246
    public function __construct(
247
        string $command,
248
        array $options = null,
249
        array $descriptors = null,
250
        string $cwd = null,
251
        array $environment = null,
252
        int $timeout = 30
253
    ) {
254
        $this->setCommand($command);
255
 
256
        if (null !== $options) {
257
            $this->setOptions($options);
258
        }
259
 
260
        if (null !== $descriptors) {
261
            $this->_descriptors = [];
262
 
263
            foreach ($descriptors as $descriptor => $nature) {
264
                if (isset($this->_descriptors[$descriptor])) {
265
                    throw new ConsoleException('Pipe descriptor %d already exists, cannot '.'redefine it.', 0, $descriptor);
266
                }
267
 
268
                $this->_descriptors[$descriptor] = $nature;
269
            }
270
        }
271
 
272
        $this->setCwd($cwd ?: \getcwd());
273
 
274
        if (null !== $environment) {
275
            $this->setEnvironment($environment);
276
        }
277
 
278
        $this->setTimeout($timeout);
279
        parent::__construct($this->getCommandLine(), null, true);
280
        $this->getListener()->addIds(['input', 'output', 'timeout', 'start', 'stop']);
281
 
282
        return;
283
    }
284
 
285
    /**
286
     * Open the stream and return the associated resource.
287
     */
288
    protected function &_open(string $streamName, StreamContext $context = null)
289
    {
290
        $out = @\proc_open(
291
            $streamName,
292
            $this->_descriptors,
293
            $this->_pipes,
294
            $this->getCwd(),
295
            $this->getEnvironment()
296
        );
297
 
298
        if (false === $out) {
299
            throw new ConsoleException('Something wrong happen when running %s.', 1, $streamName);
300
        }
301
 
302
        return $out;
303
    }
304
 
305
    /**
306
     * Close the current stream.
307
     */
308
    protected function _close(): bool
309
    {
310
        foreach ($this->_pipes as $pipe) {
311
            @\fclose($pipe);
312
        }
313
 
314
        return (bool) @\proc_close($this->getStream());
315
    }
316
 
317
    /**
318
     * Run the process and fire events (amongst start, stop, input, output and
319
     * timeout).
320
     * If an event returns false, it will close the current pipe.
321
     * For a simple run without firing events, use the $this->open() method.
322
     */
323
    public function run()
324
    {
325
        if (false === $this->isOpened()) {
326
            $this->open();
327
        } else {
328
            $this->_close();
329
            $this->_setStream($this->_open(
330
                $this->getStreamName(),
331
                $this->getStreamContext()
332
            ));
333
        }
334
 
335
        $this->getListener()->fire('start', new EventBucket());
336
 
337
        $_read = [];
338
        $_write = [];
339
        $_except = [];
340
 
341
        foreach ($this->_pipes as $p => $pipe) {
342
            switch ($this->_descriptors[$p][1]) {
343
                case 'r':
344
                    \stream_set_blocking($pipe, false);
345
                    $_write[] = $pipe;
346
 
347
                    break;
348
 
349
                case 'w':
350
                case 'a':
351
                    \stream_set_blocking($pipe, true);
352
                    $_read[] = $pipe;
353
 
354
                    break;
355
            }
356
        }
357
 
358
        while (true) {
359
            foreach ($_read as $i => $r) {
360
                if (false === \is_resource($r)) {
361
                    unset($_read[$i]);
362
                }
363
            }
364
 
365
            foreach ($_write as $i => $w) {
366
                if (false === \is_resource($w)) {
367
                    unset($_write[$i]);
368
                }
369
            }
370
 
371
            foreach ($_except as $i => $e) {
372
                if (false === \is_resource($e)) {
373
                    unset($_except[$i]);
374
                }
375
            }
376
 
377
            if (empty($_read) && empty($_write) && empty($_except)) {
378
                break;
379
            }
380
 
381
            $read = $_read;
382
            $write = $_write;
383
            $except = $_except;
384
            $select = \stream_select($read, $write, $except, $this->getTimeout());
385
 
386
            if (0 === $select) {
387
                $this->getListener()->fire('timeout', new EventBucket());
388
 
389
                break;
390
            }
391
 
392
            foreach ($read as $i => $_r) {
393
                $pipe = \array_search($_r, $this->_pipes);
394
                $line = $this->readLine($pipe);
395
 
396
                if (false === $line) {
397
                    $result = [false];
398
                } else {
399
                    $result = $this->getListener()->fire(
400
                        'output',
401
                        new EventBucket([
402
                            'pipe' => $pipe,
403
                            'line' => $line,
404
                        ])
405
                    );
406
                }
407
 
408
                if (true === \feof($_r) || \in_array(false, $result, true)) {
409
                    \fclose($_r);
410
                    unset($_read[$i]);
411
 
412
                    break;
413
                }
414
            }
415
 
416
            foreach ($write as $j => $_w) {
417
                $result = $this->getListener()->fire(
418
                    'input',
419
                    new EventBucket([
420
                        'pipe' => \array_search($_w, $this->_pipes),
421
                    ])
422
                );
423
 
424
                if (true === \feof($_w) || \in_array(false, $result, true)) {
425
                    \fclose($_w);
426
                    unset($_write[$j]);
427
                }
428
            }
429
 
430
            if (empty($_read)) {
431
                break;
432
            }
433
        }
434
 
435
        $this->getListener()->fire('stop', new EventBucket());
436
 
437
        return;
438
    }
439
 
440
    /**
441
     * Get pipe resource.
442
     */
443
    protected function getPipe(int $pipe)
444
    {
445
        if (!isset($this->_pipes[$pipe])) {
446
            throw new ConsoleException('Pipe descriptor %d does not exist, cannot read from it.', 2, $pipe);
447
        }
448
 
449
        return $this->_pipes[$pipe];
450
    }
451
 
452
    /**
453
     * Check if a pipe is seekable or not.
454
     */
455
    protected function isPipeSeekable(int $pipe): bool
456
    {
457
        if (!isset($this->_seekable[$pipe])) {
458
            $_pipe = $this->getPipe($pipe);
459
            $data = \stream_get_meta_data($_pipe);
460
            $this->_seekable[$pipe] = $data['seekable'];
461
        }
462
 
463
        return $this->_seekable[$pipe];
464
    }
465
 
466
    /**
467
     * Test for end-of-file.
468
     */
469
    public function eof(int $pipe = 1): bool
470
    {
471
        return \feof($this->getPipe($pipe));
472
    }
473
 
474
    /**
475
     * Read n characters.
476
     */
477
    public function read(int $length, int $pipe = 1)
478
    {
479
        if (0 > $length) {
480
            throw new ConsoleException('Length must be greater than 0, given %d.', 3, $length);
481
        }
482
 
483
        return \fread($this->getPipe($pipe), $length);
484
    }
485
 
486
    /**
487
     * Alias of $this->read().
488
     */
489
    public function readString(int $length, int $pipe = 1)
490
    {
491
        return $this->read($length, $pipe);
492
    }
493
 
494
    /**
495
     * Read a character.
496
     */
497
    public function readCharacter(int $pipe = 1)
498
    {
499
        return \fgetc($this->getPipe($pipe));
500
    }
501
 
502
    /**
503
     * Read a boolean.
504
     */
505
    public function readBoolean(int $pipe = 1)
506
    {
507
        return (bool) $this->read(1, $pipe);
508
    }
509
 
510
    /**
511
     * Read an integer.
512
     */
513
    public function readInteger(int $length = 1, int $pipe = 1)
514
    {
515
        return (int) $this->read($length, $pipe);
516
    }
517
 
518
    /**
519
     * Read a float.
520
     */
521
    public function readFloat(int $length = 1, int $pipe = 1)
522
    {
523
        return (float) $this->read($length, $pipe);
524
    }
525
 
526
    /**
527
     * Read an array.
528
     * Alias of the $this->scanf() method.
529
     */
530
    public function readArray(string $format = null, int $pipe = 1)
531
    {
532
        return $this->scanf($format, $pipe);
533
    }
534
 
535
    /**
536
     * Read a line.
537
     */
538
    public function readLine(int $pipe = 1)
539
    {
540
        return \stream_get_line($this->getPipe($pipe), 1 << 15, "\n");
541
    }
542
 
543
    /**
544
     * Read all, i.e. read as much as possible.
545
     */
546
    public function readAll(int $offset = -1, int $pipe = 1)
547
    {
548
        $_pipe = $this->getPipe($pipe);
549
 
550
        if (true === $this->isPipeSeekable($pipe)) {
551
            $offset += \ftell($_pipe);
552
        } else {
553
            $offset = -1;
554
        }
555
 
556
        return \stream_get_contents($_pipe, -1, $offset);
557
    }
558
 
559
    /**
560
     * Parse input from a stream according to a format.
561
     */
562
    public function scanf(string $format, int $pipe = 1): array
563
    {
564
        return \fscanf($this->getPipe($pipe), $format);
565
    }
566
 
567
    /**
568
     * Write n characters.
569
     */
570
    public function write(string $string, int $length, int $pipe = 0)
571
    {
572
        if (0 > $length) {
573
            throw new ConsoleException('Length must be greater than 0, given %d.', 4, $length);
574
        }
575
 
576
        return \fwrite($this->getPipe($pipe), $string, $length);
577
    }
578
 
579
    /**
580
     * Write a string.
581
     */
582
    public function writeString(string $string, int $pipe = 0)
583
    {
584
        $string = (string) $string;
585
 
586
        return $this->write($string, \strlen($string), $pipe);
587
    }
588
 
589
    /**
590
     * Write a character.
591
     */
592
    public function writeCharacter(string $char, int $pipe = 0)
593
    {
594
        return $this->write((string) $char[0], 1, $pipe);
595
    }
596
 
597
    /**
598
     * Write a boolean.
599
     */
600
    public function writeBoolean(bool $boolean, int $pipe = 0)
601
    {
602
        return $this->write((string) (bool) $boolean, 1, $pipe);
603
    }
604
 
605
    /**
606
     * Write an integer.
607
     */
608
    public function writeInteger(int $integer, int $pipe = 0)
609
    {
610
        $integer = (string) (int) $integer;
611
 
612
        return $this->write($integer, \strlen($integer), $pipe);
613
    }
614
 
615
    /**
616
     * Write a float.
617
     */
618
    public function writeFloat(float $float, int $pipe = 0)
619
    {
620
        $float = (string) (float) $float;
621
 
622
        return $this->write($float, \strlen($float), $pipe);
623
    }
624
 
625
    /**
626
     * Write an array.
627
     */
628
    public function writeArray(array $array, int $pipe = 0)
629
    {
630
        $array = \var_export($array, true);
631
 
632
        return $this->write($array, \strlen($array), $pipe);
633
    }
634
 
635
    /**
636
     * Write a line.
637
     */
638
    public function writeLine(string $line, int $pipe = 0)
639
    {
640
        if (false === $n = \strpos($line, "\n")) {
641
            return $this->write($line."\n", \strlen($line) + 1, $pipe);
642
        }
643
 
644
        ++$n;
645
 
646
        return $this->write(\substr($line, 0, $n), $n, $pipe);
647
    }
648
 
649
    /**
650
     * Write all, i.e. as much as possible.
651
     */
652
    public function writeAll(string $string, int $pipe = 0)
653
    {
654
        return $this->write($string, \strlen($string), $pipe);
655
    }
656
 
657
    /**
658
     * Truncate a file to a given length.
659
     */
660
    public function truncate(int $size, int $pipe = 0): bool
661
    {
662
        return \ftruncate($this->getPipe($pipe), $size);
663
    }
664
 
665
    /**
666
     * Get filename component of path.
667
     */
668
    public function getBasename(): string
669
    {
670
        return \basename($this->getCommand());
671
    }
672
 
673
    /**
674
     * Get directory name component of path.
675
     */
676
    public function getDirname(): string
677
    {
678
        return \dirname($this->getCommand());
679
    }
680
 
681
    /**
682
     * Get status.
683
     */
684
    public function getStatus(): array
685
    {
686
        return \proc_get_status($this->getStream());
687
    }
688
 
689
    /**
690
     * Get exit code (alias of $this->getStatus()['exitcode']);.
691
     */
692
    public function getExitCode(): int
693
    {
694
        $handle = $this->getStatus();
695
 
696
        return $handle['exitcode'];
697
    }
698
 
699
    /**
700
     * Whether the processus have ended successfully.
701
     *
702
     * @return bool
703
     */
704
    public function isSuccessful(): bool
705
    {
706
        return 0 === $this->getExitCode();
707
    }
708
 
709
    /**
710
     * Terminate the process.
711
     *
712
     * Valid signals are self::SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGKILL,
713
     * SIGALRM and SIGTERM.
714
     */
715
    public function terminate(int $signal = self::SIGTERM): bool
716
    {
717
        return \proc_terminate($this->getStream(), $signal);
718
    }
719
 
720
    /**
721
     * Set command name.
722
     */
723
    protected function setCommand(string $command)
724
    {
725
        $old = $this->_command;
726
        $this->_command = \escapeshellcmd($command);
727
 
728
        return $old;
729
    }
730
 
731
    /**
732
     * Get command name.
733
     */
734
    public function getCommand()
735
    {
736
        return $this->_command;
737
    }
738
 
739
    /**
740
     * Set command options.
741
     */
742
    protected function setOptions(array $options): array
743
    {
744
        foreach ($options as &$option) {
745
            $option = \escapeshellarg($option);
746
        }
747
 
748
        $old = $this->_options;
749
        $this->_options = $options;
750
 
751
        return $old;
752
    }
753
 
754
    /**
755
     * Get options.
756
     */
757
    public function getOptions(): array
758
    {
759
        return $this->_options;
760
    }
761
 
762
    /**
763
     * Get command-line.
764
     */
765
    public function getCommandLine(): string
766
    {
767
        $out = $this->getCommand();
768
 
769
        foreach ($this->getOptions() as $key => $value) {
770
            if (!\is_int($key)) {
771
                $out .= ' '.$key.'='.$value;
772
            } else {
773
                $out .= ' '.$value;
774
            }
775
        }
776
 
777
        return $out;
778
    }
779
 
780
    /**
781
     * Set current working directory of the process.
782
     */
783
    protected function setCwd(string $cwd)
784
    {
785
        $old = $this->_cwd;
786
        $this->_cwd = $cwd;
787
 
788
        return $old;
789
    }
790
 
791
    /**
792
     * Get current working directory of the process.
793
     */
794
    public function getCwd(): string
795
    {
796
        return $this->_cwd;
797
    }
798
 
799
    /**
800
     * Set environment of the process.
801
     */
802
    protected function setEnvironment(array $environment)
803
    {
804
        $old = $this->_environment;
805
        $this->_environment = $environment;
806
 
807
        return $old;
808
    }
809
 
810
    /**
811
     * Get environment of the process.
812
     */
813
    public function getEnvironment()
814
    {
815
        return $this->_environment;
816
    }
817
 
818
    /**
819
     * Set timeout of the process.
820
     */
821
    public function setTimeout(int $timeout)
822
    {
823
        $old = $this->_timeout;
824
        $this->_timeout = $timeout;
825
 
826
        return $old;
827
    }
828
 
829
    /**
830
     * Get timeout of the process.
831
     */
832
    public function getTimeout(): int
833
    {
834
        return $this->_timeout;
835
    }
836
 
837
    /**
838
     * Set process title.
839
     */
840
    public static function setTitle(string $title)
841
    {
842
        \cli_set_process_title($title);
843
    }
844
 
845
    /**
846
     * Get process title.
847
     */
848
    public static function getTitle()
849
    {
850
        return \cli_get_process_title();
851
    }
852
 
853
    /**
854
     * Found the place of a binary.
855
     */
856
    public static function locate(string $binary)
857
    {
858
        if (isset($_ENV['PATH'])) {
859
            $separator = ':';
860
            $path = &$_ENV['PATH'];
861
        } elseif (isset($_SERVER['PATH'])) {
862
            $separator = ':';
863
            $path = &$_SERVER['PATH'];
864
        } elseif (isset($_SERVER['Path'])) {
865
            $separator = ';';
866
            $path = &$_SERVER['Path'];
867
        } else {
868
            return null;
869
        }
870
 
871
        foreach (\explode($separator, $path) as $directory) {
872
            if (true === \file_exists($out = $directory.\DIRECTORY_SEPARATOR.$binary)) {
873
                return $out;
874
            }
875
        }
876
 
877
        return null;
878
    }
879
 
880
    /**
881
     * Quick process execution.
882
     * Returns only the STDOUT.
883
     */
884
    public static function execute(string $commandLine, bool $escape = true): string
885
    {
886
        if (true === $escape) {
887
            $commandLine = \escapeshellcmd($commandLine);
888
        }
889
 
890
        return \rtrim(\shell_exec($commandLine) ?? '');
891
    }
892
}