Symfony i AI: Provjerite klijentske web-stranice za ponude redizajna pomoću Technology Detector API-ja
Navođenje citata za redizajn samo na osnovu snimaka ekrana je krhko. Vidljive stranice mogu izgledati jednostavno dok osnovni sajt sadrži CMS, oznake za analitiku, više JavaScript okvira, stare dodatke i preusmeravanja koja komplikuju migraciju. Otkrivanje tih detalja nakon dogovora o ceni pretvara procenu u upravljanje štetom.
Ovaj vodič gradi Symfony komandu orijentisanu ka produkciji koja proverava javni sajt klijenta pre pripreme ponude. Poziva API za detekciju tehnologije veb sajta, pretvara njegov odgovor u objekte u vlasništvu aplikacije i štampa sažet izveštaj o steku. Integracija uključuje ograničene vremenske rokove, selektivna ponovna pokušavanja, strukturirane greške, sigurno logovanje i determinističke testove.
Dobijte pristup i kopirajte token opsega usluge
Započnite kreiranjem naloga na https://ai.mihajlo.mk/register ili koristite https://ai.mihajlo.mk/login ako već imate nalog.
- Otvorite stranicu usluge detektora tehnologije veb sajta.
- Odaberite dostupan besplatni, Plus ili Pro plan i dovršite njegovu aktivaciju.
- Otvorite zvaničnu dokumentaciju usluge.
- Pronađite panel Token usluge i kopirajte token opsega usluge prikazan tamo.
- Sačuvajte taj token u konfiguraciji projekta zasnovanoj na okruženju.
Ova usluga zahteva autentifikaciju. Prihvata Bearer token, X-API-Token zaglavlje ili token upitni parametar. Implementacija ispod koristi Bearer token jer zadržava akreditive van URL-ova, zapisnika pristupa proksija, istorije pretraživača i sistema za analitiku.
Regeneracija tokena usluge opoziva prethodno aktivni token. Tretirajte regeneraciju kao rotaciju akreditiva: ažurirajte svako raspoređeno okruženje koje koristi uslugu, verifikujte novi token, i tek tada smatrajte rotaciju završenom.
Potvrdite tačan API poziv
Detektor koristi ovaj tačan zahtev:
POST https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies
Očekuje JSON telo koje sadrži url. Pre pisanja Symfony koda, napravite jedan minimalni zahtev iz pouzdanog terminala:
curl --request POST \
--url 'https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies' \
--header 'Authorization: Bearer YOUR_SERVICE_TOKEN' \
--header 'Content-Type: application/json' \
--data '{"url":"https://example.com"}'
Ne lepite pravi token u istoriju ljuske na deljenoj mašini. Varijabla okruženja ili radni tok terminala svestan tajni je sigurniji.
Kreirajte Symfony projekat
Funkcija je namerno sinhrona. Programer je pokreće tokom kvalifikacije zahteva za redizajn, tako da dodavanje Messengera i reda bi uvelo operativnu mašineriju bez poboljšanja radnog toka. Ako revizije kasnije postanu grupni ili zakazani proces, isti klijent se može pozvati iz Messenger rukovaoca.
composer create-project symfony/skeleton quote-auditor
cd quote-auditor
composer require symfony/http-client symfony/console symfony/monolog-bundle
composer require --dev symfony/test-pack
Relevantna struktura projekta je mala:
quote-auditor/
├── config/
│ └── services.yaml
├── src/
│ ├── Command/AuditWebsiteCommand.php
│ └── WebsiteAudit/
│ ├── AuditApiException.php
│ ├── AuditMapper.php
│ ├── TechnologyDetection.php
│ ├── WebsiteAudit.php
│ └── WebsiteTechnologyClient.php
└── tests/
└── WebsiteAudit/WebsiteTechnologyClientTest.php
Čuvajte akreditive u konfiguraciji okruženja
Lokalne tajne stavite u .env.local, koje ne bi trebalo da se čuvaju u repozitorijumu:
WEBSITE_DETECTOR_TOKEN=YOUR_SERVICE_TOKEN
WEBSITE_DETECTOR_ENDPOINT=https://ai.mihajlo.mk/api/website-technology-detector/v1/detect-technologies
Povežite te vrednosti sa klijentom preko config/services.yaml:
parameters:
website_detector.endpoint: '%env(string:WEBSITE_DETECTOR_ENDPOINT)%'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
App\WebsiteAudit\WebsiteTechnologyClient:
arguments:
$endpoint: '%website_detector.endpoint%'
$token: '%env(string:WEBSITE_DETECTOR_TOKEN)%'
U produkciji, ubrizgajte iste nazive promenljivih kroz prodavnicu tajni platforme za hosting. Krajnja tačka je konfigurisana za testabilnost i operativnu kontrolu, ali raspoređivanja bi trebalo da koriste tačnu zvaničnu HTTPS krajnju tačku.
Vlastiti odgovor na granici aplikacije
Spoljni JSON ne bi trebalo da prodire kroz celu aplikaciju. Detektor vraća detekcije sa ocenom pouzdanosti, potkrepljujuće dokaze, informacije o verziji i preusmeravanja, ali svaka vrednost i dalje zahteva provere tipa. Nedostajući opcionih podataka trebalo bi da rezultira nepotpunim izveštajem, a ne PHP greškom tipa.
Kreirajte TechnologyDetection.php i WebsiteAudit.php:
<?php
// src/WebsiteAudit/TechnologyDetection.php
namespace App\WebsiteAudit;
final readonly class TechnologyDetection
{
public function __construct(
public string $name,
public ?float $confidence,
public array $versions,
public array $evidence,
) {}
}
// src/WebsiteAudit/WebsiteAudit.php
namespace App\WebsiteAudit;
final readonly class WebsiteAudit
{
/** @param list<TechnologyDetection> $detections */
public function __construct(
public array $detections,
public array $redirects,
) {}
}
Mapa u nastavku defanzivno proverava konceptualna polja odgovora. Unosi preusmeravanja ostaju neprozirni jer komandi za citiranje treba samo da označi njihovo prisustvo; kod koji kasnije prikazuje pojedinačne hopove bi trebalo da uvede drugi DTO zasnovan na tačnom obliku dokumentovanom od strane usluge.
<?php
// src/WebsiteAudit/AuditMapper.php
namespace App\WebsiteAudit;
final class AuditMapper
{
public function fromPayload(array $payload): WebsiteAudit
{
$rows = $payload['detections'] ?? [];
$rows = is_array($rows) ? $rows : [];
$detections = [];
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$name = $row['name'] ?? null;
if (!is_string($name) || trim($name) === '') {
continue;
}
$confidence = $row['confidence'] ?? null;
$confidence = is_numeric($confidence)
? max(0.0, min(100.0, (float) $confidence))
: null;
$versions = $this->listValue($row['versions'] ?? []);
$evidence = $this->listValue($row['evidence'] ?? []);
$detections[] = new TechnologyDetection(
trim($name),
$confidence,
$versions,
$evidence,
);
}
$redirects = $payload['redirects'] ?? [];
return new WebsiteAudit(
$detections,
is_array($redirects) ? array_values($redirects) : [],
);
}
private function listValue(mixed $value): array
{
if (is_string($value) || is_numeric($value)) {
return [(string) $value];
}
return is_array($value) ? array_values($value) : [];
}
}
Zadržavanje pretpostavki u jednoj mapi je praktična odluka za održavanje. Ako se zvanična dokumentacija razvija, jedna granica se menja dok komanda i radni tok citiranja ostaju stabilni.
Napravite otpornog HTTP klijenta
Greške treba da imaju kategorije na koje operater može da reaguje. Greške pri autentifikaciji zahtevaju ispravku tokena, greške pri validaciji zahtevaju ispravku ulaza, a privremene greške na strani servera mogu opravdati ponovno pokušavanje.
<?php
// src/WebsiteAudit/AuditApiException.php
namespace App\WebsiteAudit;
final class AuditApiException extends \RuntimeException
{
public function __construct(
public readonly string $category,
public readonly ?int $status = null,
?\Throwable $previous = null,
) {
parent::__construct(
"Website audit failed: {$category}",
0,
$previous,
);
}
}
Klijent koristi kratka ograničenja veze i ukupnog odgovora. Ponovo pokušava samo greške transporta, ograničavanje brzine i odabrane privremene greške servera. Odgovori validacije i autentifikacije se vraćaju odmah jer ponavljanje istog nevažećeg zahteva ne može da ih popravi.
<?php
// src/WebsiteAudit/WebsiteTechnologyClient.php
namespace App\WebsiteAudit;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class WebsiteTechnologyClient
{
public function __construct(
private readonly HttpClientInterface $http,
private readonly LoggerInterface $logger,
private readonly AuditMapper $mapper,
private readonly string $endpoint,
private readonly string $token,
) {}
public function inspect(string $url): WebsiteAudit
{
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
throw new AuditApiException('invalid_url');
}
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
$response = $this->http->request('POST', $this->endpoint, [
'headers' => [
'Authorization' => 'Bearer '.$this->token,
'Accept' => 'application/json',
],
'json' => ['url' => $url],
'timeout' => 5.0,
'max_duration' => 20.0,
]);
$status = $response->getStatusCode();
$body = $response->getContent(false);
if ($status >= 200 && $status < 300) {
try {
$payload = json_decode(
$body,
true,
512,
JSON_THROW_ON_ERROR,
);
} catch (\JsonException $exception) {
throw new AuditApiException(
'invalid_json',
$status,
$exception,
);
}
if (!is_array($payload)) {
throw new AuditApiException('invalid_payload', $status);
}
return $this->mapper->fromPayload($payload);
}
if ($status === 401 || $status === 403) {
throw new AuditApiException('authentication', $status);
}
if ($status === 400 || $status === 422) {
throw new AuditApiException('validation', $status);
}
$retryable = in_array(
$status,
[429, 502, 503, 504],
true,
);
if (!$retryable || $attempt === 3) {
$category = $status === 429
? 'rate_limited'
: 'upstream_http';
throw new AuditApiException($category, $status);
}
} catch (TransportExceptionInterface $exception) {
if ($attempt === 3) {
throw new AuditApiException(
'transport',
null,
$exception,
);
}
}
$this->logger->warning('Website audit request will retry', [
'host' => parse_url($url, PHP_URL_HOST),
'attempt' => $attempt,
]);
usleep(200_000 * (2 ** ($attempt - 1)));
}
throw new AuditApiException('unexpected');
}
}
Usporavanje je namerno ograničeno. Komanda ne bi trebalo da visi beskonačno kada je kvota iscrpljena ili provajder nedostupan. Za duža kašnjenja, posebno veliku vrednost Retry-After, zaustavite se i zakažite kasnije pokretanje umesto da zauzimate PHP radnika.
Izložite reviziju kao komandu za citiranje
<?php
// src/Command/AuditWebsiteCommand.php
namespace App\Command;
use App\WebsiteAudit\AuditApiException;
use App\WebsiteAudit\WebsiteTechnologyClient;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'quote:audit-website',
description: 'Inspect a public website before preparing a redesign quote',
)]
final class AuditWebsiteCommand extends Command
{
public function __construct(
private readonly WebsiteTechnologyClient $client,
) {
parent::__construct();
}
protected function configure(): void
{
$this->addArgument('url', InputArgument::REQUIRED, 'Public website URL');
}
protected function execute(
InputInterface $input,
OutputInterface $output,
): int {
$io = new SymfonyStyle($input, $output);
$url = (string) $input->getArgument('url');
try {
$audit = $this->client->inspect($url);
} catch (AuditApiException $exception) {
$io->error(sprintf(
'Audit unavailable (%s%s).',
$exception->category,
$exception->status ? ', HTTP '.$exception->status : '',
));
return Command::FAILURE;
}
$rows = [];
foreach ($audit->detections as $detection) {
$rows[] = [
$detection->name,
$detection->confidence === null
? 'unknown'
: $detection->confidence.'%',
$detection->versions === []
? 'not reported'
: implode(', ', $detection->versions),
count($detection->evidence),
];
}
$io->table(
['Technology', 'Confidence', 'Version', 'Evidence items'],
$rows,
);
$io->note(sprintf(
'%d redirect record(s) reported.',
count($audit->redirects),
));
return Command::SUCCESS;
}
}
Pokrenite ga sa php bin/console quote:audit-website https://example.com. Rezultati su ulaz za profesionalni sud, a ne automatski kalkulator cena. Detekcije niske pouzdanosti zaslužuju ručnu potvrdu; preusmeravanja mogu signalizirati rad na konsolidaciji domena; dokazi o verziji mogu otkriti rizik migracije bez dokazivanja da je komponenta ranjiva.
Testirajte bez pozivanja živog servisa
MockHttpClient čini ponovna pokušavanja i mapiranja determinističkim. Ovi testovi nikada ne troše kvotu i nikada ne sadrže pravi token.
<?php
// tests/WebsiteAudit/WebsiteTechnologyClientTest.php
namespace App\Tests\WebsiteAudit;
use App\WebsiteAudit\AuditApiException;
use App\WebsiteAudit\AuditMapper;
use App\WebsiteAudit\WebsiteTechnologyClient;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
final class WebsiteTechnologyClientTest extends TestCase
{
public function testMapsSuccessfulDetection(): void
{
$http = new MockHttpClient(new MockResponse(json_encode([
'detections' => [[
'name' => 'Example CMS',
'confidence' => 92,
'versions' => ['1.2'],
'evidence' => ['public markup signature'],
]],
'redirects' => [],
], JSON_THROW_ON_ERROR), ['http_code' => 200]));
$client = new WebsiteTechnologyClient(
$http,
new NullLogger(),
new AuditMapper(),
'https://service.invalid/detect',
'test-token',
);
$audit = $client->inspect('https://example.com');
self::assertCount(1, $audit->detections);
self::assertSame('Example CMS', $audit->detections[0]->name);
self::assertSame(92.0, $audit->detections[0]->confidence);
}
public function testDoesNotRetryAuthenticationFailure(): void
{
$requests = 0;
$http = new MockHttpClient(function () use (&$requests) {
$requests++;
return new MockResponse('', ['http_code' => 401]);
});
$client = new WebsiteTechnologyClient(
$http,
new NullLogger(),
new AuditMapper(),
'https://service.invalid/detect',
'invalid-token',
);
try {
$client->inspect('https://example.com');
self::fail('Expected an authentication failure.');
} catch (AuditApiException $exception) {
self::assertSame('authentication', $exception->category);
self::assertSame(1, $requests);
}
}
}
Pokrenite php bin/phpunit. Dalji test ponovnog pokušaja može da zakaže dva 503 odgovora nakon čega sledi uspešan odgovor, iako bi produkcijski testni paketi trebalo da ubrizgaju apstrakciju za uspavljivanje tako da testovi usporavanja zapravo ne pauziraju.
Bezbednost, posmatranje i implementacija
Reviziju vršite samo javnim URL-ovima klijenata koje ste ovlašćeni da proveravate. Ako ovaj klijent kasnije bude izložen putem veb obrasca, lažiranje zahteva na strani servera postaje ozbiljan problem: odbijte šeme koje nisu HTTP, localhost, privatne adrese i adrese veza, kao i hostove koji se rezolucijom prevode u privatne mreže. Ponovo proverite nakon DNS rezolucije i preusmeravanja umesto da se oslanjate samo na poslati niz.
Nikada ne logujte zaglavlje autorizacije, token, ceo sadržaj odgovora ili akreditive iz upitnih parametara. Primer loguje ciljni host, pokušaj ponovnog pokušaja, kategoriju i status—dovoljno za dijagnostiku bez prikupljanja nepotrebnih dokaza sa stranice. Dodajte identifikator korelacije zahteva ako revizije prolaze kroz više komponenti aplikacije.
Implementirajte sa optimizovanim Composer zavisnostima, produkcijskim varijablama okruženja i Symfony kešom i direktorijumima za logovanje koji mogu da se pišu. Nakon rotacije tokena, ponovo pokrenite dugotrajne PHP procese kako bi ponovo učitali konfiguraciju zasnovanu na okruženju.
Uobičajeni načini grešaka
- 401 ili 403: proverite aktivaciju plana i token opsega usluge. Regenerisani token poništava stari.
- 400 ili 422: proverite da li je zahtev JSON i da li sadrži važeći javni
url. - 429: zaustavite se nakon ograničenih ponovnih pokušaja i pokrenite kasnije ili pregledajte kapacitet plana.
- Vremenski istek ili greška transporta: razlikujte privremene mrežne probleme od sajta klijenta koji je dosledno nedostupan.
- Prazne detekcije: ne prevodite odsustvo dokaza u "bez tehnologije". Zabeležite ograničenje i ručno proverite.
- Neočekivani JSON: greška na granici mape, uporedite oblik tereta sa zvaničnom dokumentacijom i namerno ažurirajte adapter.
Završni kontrolni spisak
- Komanda šalje
POSTna tačnu krajnju tačku detekcije sa JSON-om koji sadržiurl. - Bearer token dolazi iz konfiguracije zasnovane na okruženju i nikada se ne pojavljuje u izvoru ili logovima.
- Vreme veze i ukupnog odgovora su ograničeni.
- Greške autentifikacije i validacije se ne ponavljaju.
- Ograničenja brzine i privremene greške na strani servera dobijaju samo ograničeno usporavanje.
- Detekcije, pouzdanost, verzije, dokazi i preusmeravanja se mapiraju defanzivno.
- Testovi koriste
MockHttpClienti ne vrše spoljne zahteve. - Živi "smoke test" uspeva u produkcijskom okruženju pre nego što se radni tok počne pouzdati.
Izveštaj o tehnologiji vam neće napisati ponudu za redizajn, i to je upravo njegova vrednost. Zamenjuje nagađanja dokazima koji se mogu proveriti, ostavljajući odluke o obimu i ceni programeru. Kada je otkrivanje ponovljivo, ograničeno i iskreno o nesigurnosti, rezultujuća ponuda postaje lakša za odbranu—i daleko manje verovatno da će sakriti skupo iznenađenje.