Skip to content

Instantly share code, notes, and snippets.

@trovster
Last active January 27, 2023 13:57
Show Gist options
  • Save trovster/c0a7ddc875cb21ed43e8640e0e7bc295 to your computer and use it in GitHub Desktop.
Save trovster/c0a7ddc875cb21ed43e8640e0e7bc295 to your computer and use it in GitHub Desktop.
Check links for HTTP errors and optionally mark them as broken. `php artisan link:error-checking {--update}`
<?php
namespace App\Console\Commands\Link;
use App\Models\Link\Link;
use App\Support\Str;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Traits\Conditionable;
class ErrorChecking extends Command
{
use Conditionable;
/** @var string */
protected $signature = 'link:error-checking {--update}';
/** @var string */
protected $description = 'Check links for HTTP errors and optionally mark them as broken.';
protected array $httpOptions = [
'verify' => false,
];
public function handle(): mixed
{
$errorLinks = Collection::make();
$update = $this->option('update');
$query = Link::query()
->whereNot(fn (Builder $query) => $query->broken())
->oldest('added');
$links = $this->withProgressBar($query->get(), function (Link $link) use ($errorLinks) {
$error = $this->isLinkAnError($link);
$errorLinks->when($error, fn () => $errorLinks->add($link));
});
$this->newLine();
$errorLinks->each(function (Link $link) use ($update): void {
$this->error($link->permalink);
$this->when($update, fn () => $link->markAsBroken());
});
$this->info($this->message($links->count(), $errorLinks->count()));
return Command::SUCCESS;
}
protected function isLinkAnError(Link $link): bool
{
try {
return ! Http::withOptions($this->httpOptions)->get($link->permalink)->successful();
} catch (ConnectionException | RequestException) {
return true;
}
}
protected function message(int $total, int $errorCount): string
{
$link = Str::plural('link', $total);
$error = Str::plural('error', $errorCount);
$were = $errorCount === 1 ? 'was' : 'were';
return "{$total} {$link} checked. There {$were} {$errorCount} {$error}.";
}
}
<?php
namespace App\Console\Commands\Link;
use App\Models\Link\Link;
use Illuminate\Console\Command;
use Illuminate\Support\Traits\Conditionable;
// Create broken link report, like https://optional.is/required/2022/03/09/link-rot/
class ErrorReport extends Command
{
use Conditionable;
/** @var string */
protected $signature = 'link:error-report {--style=default}';
/** @var string */
protected $description = 'Output a report about broken links.';
protected array $tableHeaders = [
'Year',
'Links',
'Broken',
'Percentage',
];
public function handle(): mixed
{
$errorLinks = Link::query()
->withoutGlobalScopes()
->selectRaw('YEAR(added) as `Year`, count(*) as `Total`')
->selectRaw('(SELECT count(*) FROM `links` as l WHERE l.broken = 1 AND YEAR(l.added) = YEAR(links.added)) as `Broken`')
->groupBy('Year')
->orderBy('Year')
->get()
->map(function (Link $link): array {
return array_merge($link->toArray(), [
'Percentage' => $this->percentage($link->Broken, $link->Total),
]);
});
$this->table(
$this->tableHeaders,
$errorLinks->add([
'Total',
$errorLinks->sum('Total'),
$errorLinks->sum('Broken'),
$this->percentage($errorLinks->sum('Broken'), $errorLinks->sum('Total'))
]),
$this->option('style'),
);
return Command::SUCCESS;
}
protected function percentage(int $broken, int $total): string
{
$percentage = round(($broken / $total) * 100, 1);
return "{$percentage}%";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment