I'm trying to create a Job for my sitemap generator in Laravel. It should create a job for adding each 100 rows form my Cars model but I'm stuck at one error. As soon as I use sitemap:make command and my Job handler is trying to call a addUrlTag() methid I'm getting this error
Error: Typed property App\Services\Sitemap\SitemapService::$xml must not be accessed before initialization in /var/www/html/app/Services/Sitemap/SitemapService.php:41 Stack trace: #0 /var/www/html/app/Jobs/MakeSitemapJob.php(41):
I guess that there is something wrong in my Job class logic, but I can't figure out what exactly. I appreciate any help! Thanks!
Here are my classes:
MakeSitemapCommand.php:
<?php
namespace App\Console\Commands;
use App\Jobs\MakeSitemapJob;
use App\Services\Sitemap\SitemapService;
use Illuminate\Console\Command;
class MakeSitemapCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sitemap:make';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command creates sitemap';
/**
* Execute the console command.
*/
public function handle(SitemapService $sitemapService): void
{
$sitemapService->make();
}
}
SitemapSerivce.php
namespace App\Services\Sitemap;
use App\Jobs\MakeSitemapJob;
use App\Models\Car;
use SimpleXMLElement;
class SitemapService
{
private SimpleXMLElement $xml;
public function make(): SimpleXMLElement
{
$this->clear();
$this->xml = new SimpleXMLElement(
'<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</urlset>');
$urls = [
url('/'),
];
foreach ($urls as $url)
{
$this->addUrlTag($url, date('Y-m-d'));
}
Car::chunk(100, function ($cars) {
MakeSitemapJob::dispatch($cars);
});
$this->divide($this->xml, env('SITEMAP_URL_LIMIT', 50000));
return $this->xml;
}
public function addUrlTag($url, $lastmod): void
{
$sitemapTag = $this->xml->addChild('url');
$sitemapTag->addChild('loc', $url);
$sitemapTag->addChild('lastmod', $lastmod);
}
public function divide($sitemap, $urlLimit): void
{
$urlTags = $sitemap->url;
$numberOfFilesToCreate = ceil(count($urlTags) / $urlLimit);
$finalSitemap = new SimpleXMLElement(
'<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</sitemapindex>');
for ($i = 1; $i <= $numberOfFilesToCreate; $i++) {
$sitemapTag = $finalSitemap->addChild('sitemap');
$sitemapTag->addChild('loc', url('/') . "/sitemaps/sitemap-$i.xml");
$sitemapTag->addChild('lastmod', date('Y-m-d'));
$start = ($i - 1) * $urlLimit;
$end = $i * $urlLimit - 1;
$fileContent = new SimpleXMLElement(
'<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</urlset>'
);
for ($j = $start; $j <= $end && $j < count($urlTags); $j++) {
$sourceUrlTag = $urlTags[$j];
$newUrlTag = $fileContent->addChild('url');
foreach ($sourceUrlTag->children() as $child) {
$tagName = $child->getName();
$tagValue = (string)$child;
$newUrlTag->addChild($tagName, $tagValue);
}
}
file_put_contents(public_path() . "/sitemaps/sitemap-$i.xml", $fileContent->asXML());
}
file_put_contents(public_path() . '/sitemap.xml', $finalSitemap->asXML());
file_put_contents(public_path() . '/robots.txt', "Sitemap: http://vincheck.site/sitemap.xml\n", FILE_APPEND);
}
public function clear(): void
{
$this->removeMainFile();
$this->removeFromFolder();
$this->removeFromRobots();
}
public function removeFromRobots(): void
{
$robotsFilePath = public_path() . '/robots.txt';
$pattern = '/^Sitemap:\s*.*\.xml$/im';
$updatedRobotsContent = preg_replace($pattern, '', file_get_contents($robotsFilePath));
$updatedRobotsContent = preg_replace("/^\h*\v+/m", "", $updatedRobotsContent);
$updatedRobotsContent = preg_replace("/\n{2,}/", "\n", $updatedRobotsContent);
file_put_contents($robotsFilePath, $updatedRobotsContent);
}
public function removeFromFolder(): void
{
$baseDirectory = public_path() . '/sitemaps';
$filePattern = '/^sitemap\s*.*\.xml$/';
$files = scandir($baseDirectory);
foreach ($files as $file) {
if (preg_match($filePattern, $file)) {
$filePath = $baseDirectory . '/' . $file;
unlink($filePath);
}
}
}
public function removeMainFile(): void
{
$baseDirectory = public_path() . '/sitemap.xml';
if (file_exists($baseDirectory))
{
unlink($baseDirectory);
}
}
}
MakeSitemapJob.php
<?php
namespace App\Jobs;
use App\Models\Car;
use App\Services\Sitemap\SitemapService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class MakeSitemapJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $cars;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 3;
/**
* Create a new job instance.
*/
public function __construct($cars)
{
$this->cars = $cars;
}
/**
* Execute the job.
*/
public function handle(SitemapService $sitemapService): void
{
foreach ($this->cars as $car) {
$sitemapService->addUrlTag("http://vincheck.site/cars/$car->vin", $car->updated_at);
}
}
}
I've tried to remove the addUrlTag() method from the handler and it works exactly how I need it, but as soon as I add the logic it can't find $xml variable.
I've also tried to add the __construct() method to make $xml variable accessible, but in this case even if it runs the Jobs without any errors it does not add any of the cars urls to the xml and the file looks empty:
class SitemapService
{
private SimpleXMLElement $xml;
public function __construct()
{
$this->xml = new SimpleXMLElement(
'<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</urlset>'
);
}
public function make(): SimpleXMLElement
{
$this->clear();
$urls = [
url('/'),
];
// ...
}