vendor and env first commit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\DumpRecorder;
|
||||
|
||||
class Dump
|
||||
{
|
||||
protected string $htmlDump;
|
||||
|
||||
protected ?string $file;
|
||||
|
||||
protected ?int $lineNumber;
|
||||
|
||||
protected float $microtime;
|
||||
|
||||
public function __construct(string $htmlDump, ?string $file, ?int $lineNumber, ?float $microtime = null)
|
||||
{
|
||||
$this->htmlDump = $htmlDump;
|
||||
$this->file = $file;
|
||||
$this->lineNumber = $lineNumber;
|
||||
$this->microtime = $microtime ?? microtime(true);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'html_dump' => $this->htmlDump,
|
||||
'file' => $this->file,
|
||||
'line_number' => $this->lineNumber,
|
||||
'microtime' => $this->microtime,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\DumpRecorder;
|
||||
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
|
||||
class DumpHandler
|
||||
{
|
||||
protected DumpRecorder $dumpRecorder;
|
||||
|
||||
public function __construct(DumpRecorder $dumpRecorder)
|
||||
{
|
||||
$this->dumpRecorder = $dumpRecorder;
|
||||
}
|
||||
|
||||
public function dump(mixed $value): void
|
||||
{
|
||||
$data = (new VarCloner)->cloneVar($value);
|
||||
|
||||
$this->dumpRecorder->record($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\DumpRecorder;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\Arr;
|
||||
use ReflectionMethod;
|
||||
use ReflectionProperty;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
use Symfony\Component\VarDumper\VarDumper;
|
||||
|
||||
class DumpRecorder
|
||||
{
|
||||
/** @var array<array<int,mixed>> */
|
||||
protected array $dumps = [];
|
||||
|
||||
protected Application $app;
|
||||
|
||||
protected static bool $registeredHandler = false;
|
||||
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
public function start(): self
|
||||
{
|
||||
$multiDumpHandler = new MultiDumpHandler();
|
||||
|
||||
$this->app->singleton(MultiDumpHandler::class, fn () => $multiDumpHandler);
|
||||
|
||||
if (! self::$registeredHandler) {
|
||||
static::$registeredHandler = true;
|
||||
|
||||
$this->ensureOriginalHandlerExists();
|
||||
|
||||
$originalHandler = VarDumper::setHandler(fn ($dumpedVariable) => $multiDumpHandler->dump($dumpedVariable));
|
||||
|
||||
$multiDumpHandler?->addHandler($originalHandler);
|
||||
|
||||
$multiDumpHandler->addHandler(fn ($var) => (new DumpHandler($this))->dump($var));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function record(Data $data): void
|
||||
{
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 11);
|
||||
|
||||
$sourceFrame = $this->findSourceFrame($backtrace);
|
||||
|
||||
$file = (string) Arr::get($sourceFrame, 'file');
|
||||
$lineNumber = (int) Arr::get($sourceFrame, 'line');
|
||||
|
||||
$htmlDump = (new HtmlDumper())->dump($data);
|
||||
|
||||
$this->dumps[] = new Dump($htmlDump, $file, $lineNumber);
|
||||
}
|
||||
|
||||
public function getDumps(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->dumps = [];
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$dumps = [];
|
||||
|
||||
foreach ($this->dumps as $dump) {
|
||||
$dumps[] = $dump->toArray();
|
||||
}
|
||||
|
||||
return $dumps;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only the `VarDumper` knows how to create the orignal HTML or CLI VarDumper.
|
||||
* Using reflection and the private VarDumper::register() method we can force it
|
||||
* to create and register a new VarDumper::$handler before we'll overwrite it.
|
||||
* Of course, we only need to do this if there isn't a registered VarDumper::$handler.
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function ensureOriginalHandlerExists(): void
|
||||
{
|
||||
$reflectionProperty = new ReflectionProperty(VarDumper::class, 'handler');
|
||||
$reflectionProperty->setAccessible(true);
|
||||
$handler = $reflectionProperty->getValue();
|
||||
|
||||
if (! $handler) {
|
||||
// No handler registered yet, so we'll force VarDumper to create one.
|
||||
$reflectionMethod = new ReflectionMethod(VarDumper::class, 'register');
|
||||
$reflectionMethod->setAccessible(true);
|
||||
$reflectionMethod->invoke(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first meaningful stack frame that is not the `DumpRecorder` itself.
|
||||
*
|
||||
* @template T of array{class?: class-string, function?: string, line?: int, file?: string}
|
||||
*
|
||||
* @param array<T> $stacktrace
|
||||
*
|
||||
* @return null|T
|
||||
*/
|
||||
protected function findSourceFrame(array $stacktrace): ?array
|
||||
{
|
||||
$seenVarDumper = false;
|
||||
|
||||
foreach ($stacktrace as $frame) {
|
||||
// Keep looping until we're past the VarDumper::dump() call in Symfony's helper functions file.
|
||||
if (Arr::get($frame, 'class') === VarDumper::class && Arr::get($frame, 'function') === 'dump') {
|
||||
$seenVarDumper = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $seenVarDumper) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Return the next frame in the stack after the VarDumper::dump() call:
|
||||
return $frame;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\DumpRecorder;
|
||||
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
use Symfony\Component\VarDumper\Dumper\HtmlDumper as BaseHtmlDumper;
|
||||
|
||||
class HtmlDumper extends BaseHtmlDumper
|
||||
{
|
||||
public function __construct($output = null, string $charset = null, int $flags = 0)
|
||||
{
|
||||
parent::__construct($output, $charset, $flags);
|
||||
|
||||
$this->setDumpHeader('');
|
||||
}
|
||||
|
||||
public function dumpVariable($variable): string
|
||||
{
|
||||
$cloner = new VarCloner();
|
||||
|
||||
$clonedData = $cloner->cloneVar($variable)->withMaxDepth(3);
|
||||
|
||||
return $this->dump($clonedData);
|
||||
}
|
||||
|
||||
public function dump(Data $data, $output = null, array $extraDisplayOptions = []): string
|
||||
{
|
||||
return (string)parent::dump($data, true, [
|
||||
'maxDepth' => 3,
|
||||
'maxStringLength' => 160,
|
||||
]);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\DumpRecorder;
|
||||
|
||||
class MultiDumpHandler
|
||||
{
|
||||
/** @var array<int, callable|null> */
|
||||
protected array $handlers = [];
|
||||
|
||||
public function dump(mixed $value): void
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler) {
|
||||
$handler($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addHandler(callable $callable = null): self
|
||||
{
|
||||
$this->handlers[] = $callable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\JobRecorder;
|
||||
|
||||
use DateTime;
|
||||
use Error;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Contracts\Queue\Job;
|
||||
use Illuminate\Queue\CallQueuedClosure;
|
||||
use Illuminate\Queue\Events\JobExceptionOccurred;
|
||||
use Illuminate\Queue\Jobs\RedisJob;
|
||||
use Illuminate\Support\Str;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use RuntimeException;
|
||||
|
||||
class JobRecorder
|
||||
{
|
||||
protected ?Job $job = null;
|
||||
|
||||
public function __construct(
|
||||
protected Application $app,
|
||||
protected int $maxChainedJobReportingDepth = 5,
|
||||
) {
|
||||
}
|
||||
|
||||
public function start(): self
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->app['events']->listen(JobExceptionOccurred::class, [$this, 'record']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function record(JobExceptionOccurred $event): void
|
||||
{
|
||||
$this->job = $event->job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function getJob(): ?array
|
||||
{
|
||||
if ($this->job === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$this->getJobProperties(),
|
||||
[
|
||||
'name' => $this->job->resolveName(),
|
||||
'connection' => $this->job->getConnectionName(),
|
||||
'queue' => $this->job->getQueue(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->job = null;
|
||||
}
|
||||
|
||||
protected function getJobProperties(): array
|
||||
{
|
||||
$payload = collect($this->resolveJobPayload());
|
||||
|
||||
$properties = [];
|
||||
|
||||
foreach ($payload as $key => $value) {
|
||||
if (! in_array($key, ['job', 'data', 'displayName'])) {
|
||||
$properties[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (is_string($payload['data'])) {
|
||||
$properties['data'] = json_decode($payload['data'], true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
}
|
||||
|
||||
if ($pushedAt = DateTime::createFromFormat('U.u', $payload->get('pushedAt', ''))) {
|
||||
$properties['pushedAt'] = $pushedAt->format(DATE_ATOM);
|
||||
}
|
||||
|
||||
try {
|
||||
$properties['data'] = $this->resolveCommandProperties(
|
||||
$this->resolveObjectFromCommand($payload['data']['command']),
|
||||
$this->maxChainedJobReportingDepth
|
||||
);
|
||||
} catch (Exception $exception) {
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
protected function resolveJobPayload(): array
|
||||
{
|
||||
if (! $this->job instanceof RedisJob) {
|
||||
return $this->job->payload();
|
||||
}
|
||||
|
||||
try {
|
||||
return json_decode($this->job->getReservedJob(), true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Exception $e) {
|
||||
return $this->job->payload();
|
||||
}
|
||||
}
|
||||
|
||||
protected function resolveCommandProperties(object $command, int $maxChainDepth): array
|
||||
{
|
||||
$propertiesToIgnore = ['job', 'closure'];
|
||||
|
||||
$properties = collect((new ReflectionClass($command))->getProperties())
|
||||
->reject(function (ReflectionProperty $property) use ($propertiesToIgnore) {
|
||||
return in_array($property->name, $propertiesToIgnore);
|
||||
})
|
||||
->mapWithKeys(function (ReflectionProperty $property) use ($command) {
|
||||
try {
|
||||
$property->setAccessible(true);
|
||||
|
||||
return [$property->name => $property->getValue($command)];
|
||||
} catch (Error $error) {
|
||||
return [$property->name => 'uninitialized'];
|
||||
}
|
||||
});
|
||||
|
||||
if ($properties->has('chained')) {
|
||||
$properties['chained'] = $this->resolveJobChain($properties->get('chained'), $maxChainDepth);
|
||||
}
|
||||
|
||||
return $properties->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $chainedCommands
|
||||
* @param int $maxDepth
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function resolveJobChain(array $chainedCommands, int $maxDepth): array
|
||||
{
|
||||
if ($maxDepth === 0) {
|
||||
return ['Ignition stopped recording jobs after this point since the max chain depth was reached'];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function (string $command) use ($maxDepth) {
|
||||
$commandObject = $this->resolveObjectFromCommand($command);
|
||||
|
||||
return [
|
||||
'name' => $commandObject instanceof CallQueuedClosure ? $commandObject->displayName() : get_class($commandObject),
|
||||
'data' => $this->resolveCommandProperties($commandObject, $maxDepth - 1),
|
||||
];
|
||||
},
|
||||
$chainedCommands
|
||||
);
|
||||
}
|
||||
|
||||
// Taken from Illuminate\Queue\CallQueuedHandler
|
||||
protected function resolveObjectFromCommand(string $command): object
|
||||
{
|
||||
if (Str::startsWith($command, 'O:')) {
|
||||
return unserialize($command);
|
||||
}
|
||||
|
||||
if ($this->app->bound(Encrypter::class)) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
return unserialize($this->app[Encrypter::class]->decrypt($command));
|
||||
}
|
||||
|
||||
throw new RuntimeException('Unable to extract job payload.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\LogRecorder;
|
||||
|
||||
use Illuminate\Log\Events\MessageLogged;
|
||||
|
||||
class LogMessage
|
||||
{
|
||||
protected ?string $message;
|
||||
|
||||
protected string $level;
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected array $context = [];
|
||||
|
||||
protected ?float $microtime;
|
||||
|
||||
/**
|
||||
* @param string|null $message
|
||||
* @param string $level
|
||||
* @param array<string, string> $context
|
||||
* @param float|null $microtime
|
||||
*/
|
||||
public function __construct(
|
||||
?string $message,
|
||||
string $level,
|
||||
array $context = [],
|
||||
?float $microtime = null
|
||||
) {
|
||||
$this->message = $message;
|
||||
$this->level = $level;
|
||||
$this->context = $context;
|
||||
$this->microtime = $microtime ?? microtime(true);
|
||||
}
|
||||
|
||||
public static function fromMessageLoggedEvent(MessageLogged $event): self
|
||||
{
|
||||
return new self(
|
||||
$event->message,
|
||||
$event->level,
|
||||
$event->context
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'message' => $this->message,
|
||||
'level' => $this->level,
|
||||
'context' => $this->context,
|
||||
'microtime' => $this->microtime,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\LogRecorder;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Log\Events\MessageLogged;
|
||||
use Throwable;
|
||||
|
||||
class LogRecorder
|
||||
{
|
||||
/** @var \Spatie\LaravelIgnition\Recorders\LogRecorder\LogMessage[] */
|
||||
protected array $logMessages = [];
|
||||
|
||||
protected Application $app;
|
||||
|
||||
protected ?int $maxLogs;
|
||||
|
||||
public function __construct(Application $app, ?int $maxLogs = null)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
$this->maxLogs = $maxLogs;
|
||||
}
|
||||
|
||||
public function start(): self
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->app['events']->listen(MessageLogged::class, [$this, 'record']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function record(MessageLogged $event): void
|
||||
{
|
||||
if ($this->shouldIgnore($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logMessages[] = LogMessage::fromMessageLoggedEvent($event);
|
||||
|
||||
if (is_int($this->maxLogs)) {
|
||||
$this->logMessages = array_slice($this->logMessages, -$this->maxLogs);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<array<int,string>> */
|
||||
public function getLogMessages(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/** @return array<int, mixed> */
|
||||
public function toArray(): array
|
||||
{
|
||||
$logMessages = [];
|
||||
|
||||
foreach ($this->logMessages as $log) {
|
||||
$logMessages[] = $log->toArray();
|
||||
}
|
||||
|
||||
return $logMessages;
|
||||
}
|
||||
|
||||
protected function shouldIgnore(mixed $event): bool
|
||||
{
|
||||
if (! isset($event->context['exception'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $event->context['exception'] instanceof Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->logMessages = [];
|
||||
}
|
||||
|
||||
public function getMaxLogs(): ?int
|
||||
{
|
||||
return $this->maxLogs;
|
||||
}
|
||||
|
||||
public function setMaxLogs(?int $maxLogs): self
|
||||
{
|
||||
$this->maxLogs = $maxLogs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\QueryRecorder;
|
||||
|
||||
use Illuminate\Database\Events\QueryExecuted;
|
||||
|
||||
class Query
|
||||
{
|
||||
protected string $sql;
|
||||
|
||||
protected float $time;
|
||||
|
||||
protected string $connectionName;
|
||||
|
||||
/** @var array<string, string>|null */
|
||||
protected ?array $bindings;
|
||||
|
||||
protected float $microtime;
|
||||
|
||||
public static function fromQueryExecutedEvent(QueryExecuted $queryExecuted, bool $reportBindings = false): self
|
||||
{
|
||||
return new self(
|
||||
$queryExecuted->sql,
|
||||
$queryExecuted->time,
|
||||
/** @phpstan-ignore-next-line */
|
||||
$queryExecuted->connectionName ?? '',
|
||||
$reportBindings ? $queryExecuted->bindings : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sql
|
||||
* @param float $time
|
||||
* @param string $connectionName
|
||||
* @param array<string, string>|null $bindings
|
||||
* @param float|null $microtime
|
||||
*/
|
||||
protected function __construct(
|
||||
string $sql,
|
||||
float $time,
|
||||
string $connectionName,
|
||||
?array $bindings = null,
|
||||
?float $microtime = null
|
||||
) {
|
||||
$this->sql = $sql;
|
||||
$this->time = $time;
|
||||
$this->connectionName = $connectionName;
|
||||
$this->bindings = $bindings;
|
||||
$this->microtime = $microtime ?? microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'sql' => $this->sql,
|
||||
'time' => $this->time,
|
||||
'connection_name' => $this->connectionName,
|
||||
'bindings' => $this->bindings,
|
||||
'microtime' => $this->microtime,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\Recorders\QueryRecorder;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Database\Events\QueryExecuted;
|
||||
|
||||
class QueryRecorder
|
||||
{
|
||||
/** @var \Spatie\LaravelIgnition\Recorders\QueryRecorder\Query[] */
|
||||
protected array $queries = [];
|
||||
|
||||
protected Application $app;
|
||||
|
||||
protected bool $reportBindings = true;
|
||||
|
||||
protected ?int $maxQueries;
|
||||
|
||||
public function __construct(
|
||||
Application $app,
|
||||
bool $reportBindings = true,
|
||||
?int $maxQueries = 200
|
||||
) {
|
||||
$this->app = $app;
|
||||
$this->reportBindings = $reportBindings;
|
||||
$this->maxQueries = $maxQueries;
|
||||
}
|
||||
|
||||
public function start(): self
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->app['events']->listen(QueryExecuted::class, [$this, 'record']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function record(QueryExecuted $queryExecuted): void
|
||||
{
|
||||
$this->queries[] = Query::fromQueryExecutedEvent($queryExecuted, $this->reportBindings);
|
||||
|
||||
if (is_int($this->maxQueries)) {
|
||||
$this->queries = array_slice($this->queries, -$this->maxQueries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getQueries(): array
|
||||
{
|
||||
$queries = [];
|
||||
|
||||
foreach ($this->queries as $query) {
|
||||
$queries[] = $query->toArray();
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->queries = [];
|
||||
}
|
||||
|
||||
public function getReportBindings(): bool
|
||||
{
|
||||
return $this->reportBindings;
|
||||
}
|
||||
|
||||
public function setReportBindings(bool $reportBindings): self
|
||||
{
|
||||
$this->reportBindings = $reportBindings;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMaxQueries(): ?int
|
||||
{
|
||||
return $this->maxQueries;
|
||||
}
|
||||
|
||||
public function setMaxQueries(?int $maxQueries): self
|
||||
{
|
||||
$this->maxQueries = $maxQueries;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user