Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Spatie\LaravelIgnition\Recorders\JobRecorder;
4
 
5
use DateTime;
6
use Error;
7
use Exception;
8
use Illuminate\Contracts\Encryption\Encrypter;
9
use Illuminate\Contracts\Foundation\Application;
10
use Illuminate\Contracts\Queue\Job;
11
use Illuminate\Queue\CallQueuedClosure;
12
use Illuminate\Queue\Events\JobExceptionOccurred;
13
use Illuminate\Queue\Jobs\RedisJob;
14
use Illuminate\Support\Str;
15
use ReflectionClass;
16
use ReflectionProperty;
17
use RuntimeException;
18
 
19
class JobRecorder
20
{
21
    protected ?Job $job = null;
22
 
23
    public function __construct(
24
        protected Application $app,
25
        protected int $maxChainedJobReportingDepth = 5,
26
    ) {
27
    }
28
 
29
    public function start(): self
30
    {
31
        /** @phpstan-ignore-next-line */
32
        $this->app['events']->listen(JobExceptionOccurred::class, [$this, 'record']);
33
 
34
        return $this;
35
    }
36
 
37
    public function record(JobExceptionOccurred $event): void
38
    {
39
        $this->job = $event->job;
40
    }
41
 
42
    /**
43
     * @return array<string, mixed>|null
44
     */
45
    public function getJob(): ?array
46
    {
47
        if ($this->job === null) {
48
            return null;
49
        }
50
 
51
        return array_merge(
52
            $this->getJobProperties(),
53
            [
54
                'name' => $this->job->resolveName(),
55
                'connection' => $this->job->getConnectionName(),
56
                'queue' => $this->job->getQueue(),
57
            ]
58
        );
59
    }
60
 
61
    public function reset(): void
62
    {
63
        $this->job = null;
64
    }
65
 
66
    protected function getJobProperties(): array
67
    {
68
        $payload = collect($this->resolveJobPayload());
69
 
70
        $properties = [];
71
 
72
        foreach ($payload as $key => $value) {
73
            if (! in_array($key, ['job', 'data', 'displayName'])) {
74
                $properties[$key] = $value;
75
            }
76
        }
77
 
78
        if ($pushedAt = DateTime::createFromFormat('U.u', $payload->get('pushedAt', ''))) {
79
            $properties['pushedAt'] = $pushedAt->format(DATE_ATOM);
80
        }
81
 
82
        try {
83
            $properties['data'] = $this->resolveCommandProperties(
84
                $this->resolveObjectFromCommand($payload['data']['command']),
85
                $this->maxChainedJobReportingDepth
86
            );
87
        } catch (Exception $exception) {
88
        }
89
 
90
        return $properties;
91
    }
92
 
93
    protected function resolveJobPayload(): array
94
    {
95
        if (! $this->job instanceof RedisJob) {
96
            return $this->job->payload();
97
        }
98
 
99
        try {
100
            return json_decode($this->job->getReservedJob(), true, 512, JSON_THROW_ON_ERROR);
101
        } catch (Exception $e) {
102
            return $this->job->payload();
103
        }
104
    }
105
 
106
    protected function resolveCommandProperties(object $command, int $maxChainDepth): array
107
    {
108
        $propertiesToIgnore = ['job', 'closure'];
109
 
110
        $properties = collect((new ReflectionClass($command))->getProperties())
111
            ->reject(function (ReflectionProperty $property) use ($propertiesToIgnore) {
112
                return in_array($property->name, $propertiesToIgnore);
113
            })
114
            ->mapWithKeys(function (ReflectionProperty $property) use ($command) {
115
                try {
116
                    $property->setAccessible(true);
117
 
118
                    return [$property->name => $property->getValue($command)];
119
                } catch (Error $error) {
120
                    return [$property->name => 'uninitialized'];
121
                }
122
            });
123
 
124
        if ($properties->has('chained')) {
125
            $properties['chained'] = $this->resolveJobChain($properties->get('chained'), $maxChainDepth);
126
        }
127
 
128
        return $properties->all();
129
    }
130
 
131
    /**
132
     * @param array<string, mixed> $chainedCommands
133
     * @param int $maxDepth
134
     *
135
     * @return array
136
     */
137
    protected function resolveJobChain(array $chainedCommands, int $maxDepth): array
138
    {
139
        if ($maxDepth === 0) {
140
            return ['Ignition stopped recording jobs after this point since the max chain depth was reached'];
141
        }
142
 
143
        return array_map(
144
            function (string $command) use ($maxDepth) {
145
                $commandObject = $this->resolveObjectFromCommand($command);
146
 
147
                return [
148
                    'name' => $commandObject instanceof CallQueuedClosure ? $commandObject->displayName() : get_class($commandObject),
149
                    'data' => $this->resolveCommandProperties($commandObject, $maxDepth - 1),
150
                ];
151
            },
152
            $chainedCommands
153
        );
154
    }
155
 
156
    // Taken from Illuminate\Queue\CallQueuedHandler
157
    protected function resolveObjectFromCommand(string $command): object
158
    {
159
        if (Str::startsWith($command, 'O:')) {
160
            return unserialize($command);
161
        }
162
 
163
        if ($this->app->bound(Encrypter::class)) {
164
            /** @phpstan-ignore-next-line */
165
            return unserialize($this->app[Encrypter::class]->decrypt($command));
166
        }
167
 
168
        throw new RuntimeException('Unable to extract job payload.');
169
    }
170
}