对于用于抓取代码的文件夹结构,我有些困惑。使用console/commands
,而不是controller
。因此,在handle function
中,我正在编写整个抓取代码。但是我应该这样做吗?或者...什么是最好的方法?
如果我正确理解以下答案。现在应该看起来像这样。
呼叫服务
class siteControl extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$website_id = $this->argument("website_id");
if ($website_id == 1) {
$portal = "App\Services\Site1";
}
$crawler = new $portal;
$crawler->run();
}
}
在处理方法中
class Site1 extends Utility
{
public function __construct()
{
parent::__construct();
}
public function run()
{
echo "method runs";
}
}
摘要:
use Goutte\Client;
abstract class Utility implements SiteInterfaces
{
protected $client;
public function __construct()
{
$this->client = new Client();
}
}
界面:
namespace App\Services;
interface SiteInterfaces
{
public function run();
}
最后,我应该在run()方法中编写整个抓取代码?请纠正我。如果对此有误...我正在寻找最佳解决方案。
答案 0 :(得分:1)
最佳做法是从命令handle()
方法中调用单独的服务。这样,您可以在控制器中重用相同的服务。
技术版本:
因此,给您一个简短的概述:
[Web request] [CLI command] <-- these are ports
\ /
\ /
\ /
[Command] <--- this is a method call to your service
|
|
|
[Command handler] <--- this is the service doing the actual work
根据您提供的代码,我实现了上面提到的内容,
这是我上面提到的CLI命令。该类要做的所有事情是:
1.收集输入参数; (website_id
)
2.将这些参数包装在命令中
3.使用命令处理程序触发命令
namespace App\Console\Commands;
use App\Command\ScrapePortalSiteCommand;
use CommandHandler\ScrapePortalSiteCommandHandler;
class BotScrapeCommand extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function handle(ScrapePortalSiteCommandHandler $handler)
{
$portalSiteId = $this->argument("website_id");
$command = new ScrapePortalSiteCommand($portalSiteId);
$handler->handle($command);
}
}
这是我上面提到的命令。它的工作是将所有输入参数包装在一个类中,命令处理程序可以使用该类。
namespace App\Command;
class ScrapePortalSiteCommand
{
/**
* @var int
*/
private $portalSiteId;
public function __construct(int $portalSiteId)
{
$this->portalSiteId = $portalSiteId;
}
public function getPortalSiteId(): int
{
return $this->portalSiteId;
}
}
命令处理程序应基于其命令实现逻辑。在这种情况下,需要找出要选择的爬虫,然后将其解雇。
namespace App\CommandHandler;
use App\Command\ScrapePortalSiteCommand;
use App\Crawler\PortalSite1Crawler;
use App\Crawler\PortalSiteCrawlerInterface;
use InvalidArgumentException;
class ScrapePortalSiteCommandHandler
{
public function handle(ScrapePortalSiteCommand $command): void
{
$crawler = $this->getCrawlerForPortalSite($command->getPortalSiteId());
$crawler->crawl();
}
private function getCrawlerForPortalSite(int $portalSiteId): PortalSiteCrawlerInterface {
switch ($portalSiteId) {
case 1:
return new PortalSite1Crawler();
default:
throw new InvalidArgumentException(
sprintf('No crawler configured for portal site with id "%s"', $portalSiteId)
);
}
}
}
该接口可确保可以以类似方式调用所有搜寻器。此外,它还提供了不错的类型提示。
namespace App\Crawler;
interface PortalSiteCrawlerInterface
{
public function crawl(): void;
}
这是执行实际抓取的地方。
namespace App\Crawler;
class PortalSite1Crawler implements PortalSiteCrawlerInterface
{
public function crawl(): void
{
// Crawl your site here
}
}
在您有additional questions的情况下,我再次更新了答案。
:void
在方法声明中使用: void
意味着该方法将不返回任何内容。以相同的方式public function getPortalSiteId(): int
表示此方法将始终返回整数。返回类型提示的使用已添加到PHP 7中,而并非特定于Laravel。可以在the PHP documentation中找到有关返回类型提示的更多信息。
使用命令和命令处理程序是一种最佳实践,它是命令总线模式的一部分。此模式描述了处理用户输入(命令)的通用方法。 This post对命令和处理程序提供了很好的解释。此外,this blog post更详细地描述了什么是命令总线,如何使用命令总线以及其优点是什么。请注意,在我提供的代码中,总线实现本身被跳过。我认为您本身并不需要它,但在某些情况下确实可以增加价值。