diff --git a/browser.php b/browser.php
index 47185e1..55d1c4d 100644
--- a/browser.php
+++ b/browser.php
@@ -3,40 +3,45 @@
include_once 'vendor/autoload.php';
use Illuminate\Database\Capsule\Manager as Capsule;
+use Krzysiej\RyobiCrawler\Controller\CategoryController;
+use Krzysiej\RyobiCrawler\Controller\IndexController;
+use Krzysiej\RyobiCrawler\Controller\ProductController;
+use Krzysiej\RyobiCrawler\Controller\SearchController;
use Krzysiej\RyobiCrawler\Models\Product;
use Twig\{Environment, Loader\FilesystemLoader};
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
if (!file_exists('database.sqlite')) {
exit('Database file database.sqlite missing. Run docker compose
docker compose exec php-app php index.php app:migrateto create it.'); } +$productRoute = new Route('browser.php/product/{product_id}', ['_controller' => ProductController::class], ['product_id' => '\d+']); +$searchRoute = new Route('browser.php?search={search_term}', ['_controller' => SearchController::class]); +$categoryRoute = new Route('/browser.php/category/{category_name}', ['_controller' => CategoryController::class]); +$indexRoute = new Route('browser.php', ['_controller' => IndexController::class]); +$routes = new RouteCollection(); -$capsule = new Capsule; -$capsule->addConnection(['driver' => 'sqlite', 'database' => __DIR__ . '/database.sqlite']); -$capsule->setAsGlobal(); -$capsule->bootEloquent(); -$loader = new FilesystemLoader(__DIR__ . '/src/templates'); -$twig = new Environment($loader); -if (isset($_GET['product_id'])) { - $template = 'product.html.twig'; - $product = Product::with('price')->find($_GET['product_id']); +$routes->add('product_show', $productRoute); +$routes->add('search_show', $searchRoute); +$routes->add('category_show', $categoryRoute); +$routes->add('index_show', $indexRoute); + +$context = new RequestContext(); +$matcher = new UrlMatcher($routes, $context); +try { + $parameters = $matcher->match($_SERVER['REQUEST_URI']); +} catch (Exception $e) { + die($e->getMessage()); } -if (isset($_GET['category'])) { - $template = 'productList.html.twig'; - $products = Product::with('price')->selectRaw('products.*')->fromRaw('products, json_each(products.categories)')->whereRaw('json_each.value = ?', [$_GET['category']]) - ->orderByDesc('starred')->orderByDesc('created_by')->get(); -} -if (isset($_GET['search'])) { - $template = 'productList.html.twig'; - $products = Product::with('price') - ->orWhere([['name', 'like', "%{$_GET['search']}%"]]) - ->orWhere([['subTitle', 'like', "%{$_GET['search']}%"]])->get(); -} -if (isset($_GET['star'])) { - Product::find($_GET['star'])->toggleStarred()->save(); - header('Location: '.$_SERVER['HTTP_REFERER']); -} -if (empty($_GET)) { - $template = 'productList.html.twig'; - $products = Product::with('price')->orderByDesc('starred')->orderByDesc('created_by')->get(); -} -$twig->display($template, ['products' => $products ?? [], 'product' => $product ?? [], 'search' => $_GET['search'] ?? '']); + +//dd($parameters['category_name']); + +match ($parameters['_controller']) { + SearchController::class => (new $parameters['_controller']())($parameters['search_term']), + CategoryController::class => (new $parameters['_controller']())($parameters['category_name']), + ProductController::class => (new $parameters['_controller']())($parameters['product_id']), + IndexController::class => (new $parameters['_controller']())(), + default => throw new Exception('Route not found') +}; diff --git a/composer.json b/composer.json index 6fadfec..10b3d0b 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,9 @@ "illuminate/database": "11.26.0.0", "ext-json": "*", "twig/twig": "3.14.0.0", - "symfony/console": "^7.0" + "symfony/console": "^7.0", + "symfony/routing": "^7.1", + "laravel/serializable-closure": "^1.3" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index e9491a3..7efdcde 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e9489916095593f696613b706467239b", + "content-hash": "0857f170e12b9885e20a632ba9c5d530", "packages": [ { "name": "brick/math", @@ -942,6 +942,67 @@ }, "time": "2024-10-08T18:54:07+00:00" }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2024-09-23T13:33:08+00:00" + }, { "name": "nesbot/carbon", "version": "3.8.0", @@ -2108,6 +2169,87 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/routing", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-29T08:16:25+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.5.0", @@ -2697,5 +2839,5 @@ "ext-json": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Command/ScrapeWebsite.php b/src/Command/ScrapeWebsite.php index e1ede47..b931a42 100644 --- a/src/Command/ScrapeWebsite.php +++ b/src/Command/ScrapeWebsite.php @@ -40,6 +40,7 @@ class ScrapeWebsite extends Command $products = $this->getProducts(); $progress->setMaxSteps(count($products)); foreach ($products as $product) { + //dd($product); $this->saveProduct($product); $progress->advance(); } @@ -80,11 +81,13 @@ class ScrapeWebsite extends Command { /** @var Product $productModel */ $productModel = Product::firstOrNew(['skuID' => $product->skuID]); + //dd($productModel); + $productModel->skuID = $product->skuID; $productModel->name = $product->name; $productModel->availableQuantity = $product->availableQuantity; $productModel->stock = $product->stock; - $productModel->categories = json_encode($product->categories); + $productModel->categories = $product->categories; $productModel->image = $product->image; $productModel->subTitle = $product->subTitle; $productModel->variantCode = $product->variantCode; @@ -92,7 +95,8 @@ class ScrapeWebsite extends Command $productModel->url = $product->url; $productModel->save(); $priceExists = $productModel->price()->whereRaw("strftime('%Y-%m-%d', created_at) = ?", [date('Y-m-d')])->exists(); - if (!$priceExists) { + + if (false === $priceExists) { $price = new Price(); $price->price = $product->productPrice; $price->productStandardPrice = $product->productStandardPrice; @@ -100,7 +104,7 @@ class ScrapeWebsite extends Command $productModel->price()->save($price); } $stockExist = $productModel->stock()->whereRaw("strftime('%Y-%m-%d', created_at) = ?", [date('Y-m-d')])->exists(); - if (!$stockExist) { + if (false === $stockExist) { $stock = new Stock(); $stock->availableQuantity = $product->availableQuantity; $stock->stock = $product->stock; diff --git a/src/Controller/BaseController.php b/src/Controller/BaseController.php new file mode 100644 index 0000000..aa2d626 --- /dev/null +++ b/src/Controller/BaseController.php @@ -0,0 +1,22 @@ +addConnection(['driver' => 'sqlite', 'database' => __DIR__ . '/../../database.sqlite']); + $capsule->setAsGlobal(); + $capsule->bootEloquent(); + $loader = new FilesystemLoader( __DIR__ . '/../../src/templates'); + $this->twig = new Environment($loader); + } +} diff --git a/src/Controller/CategoryController.php b/src/Controller/CategoryController.php new file mode 100644 index 0000000..9f0e83b --- /dev/null +++ b/src/Controller/CategoryController.php @@ -0,0 +1,20 @@ +selectRaw('products.*') + ->fromRaw('products, json_each(products.categories)') + ->whereRaw('json_each.value = ?', [$category]) + ->orderByDesc('starred') + ->orderByDesc('created_by') + ->get(); + $this->twig->display('productList.html.twig', ['products' => $products]); + } +} diff --git a/src/Controller/IndexController.php b/src/Controller/IndexController.php new file mode 100644 index 0000000..3aa0bd1 --- /dev/null +++ b/src/Controller/IndexController.php @@ -0,0 +1,14 @@ +orderByDesc('starred')->orderByDesc('created_by')->get(); + $this->twig->display('productList.html.twig', ['products' => $products]); + } +} diff --git a/src/Controller/ProductController.php b/src/Controller/ProductController.php new file mode 100644 index 0000000..eddeba5 --- /dev/null +++ b/src/Controller/ProductController.php @@ -0,0 +1,14 @@ +find($productId); + $this->twig->display('product.html.twig', ['product' => $product ?? []]); + } +} diff --git a/src/Controller/SearchController.php b/src/Controller/SearchController.php new file mode 100644 index 0000000..1235d43 --- /dev/null +++ b/src/Controller/SearchController.php @@ -0,0 +1,17 @@ +orWhere([['name', 'like', "%$search%"]]) + ->orWhere([['subTitle', 'like', "%$search%"]])->get(); + + $this->twig->display('productList.html.twig', ['products' => $products ?? [], 'search' => $search]); + } +} diff --git a/src/Models/Product.php b/src/Models/Product.php index 0c50d33..1fdc49b 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -39,10 +39,11 @@ class Product extends Model return $this; } - protected function categories(): Attribute + public function categories(): Attribute { return Attribute::make( get: fn(string $value) => array_reverse(json_decode($value, 1)), + set: fn(array $value) => json_encode($value), ); } } diff --git a/src/templates/product.html.twig b/src/templates/product.html.twig index 23c3ce1..8634ec6 100644 --- a/src/templates/product.html.twig +++ b/src/templates/product.html.twig @@ -4,12 +4,12 @@
| {{ product.name }} | +{{ product.name }} | {{ product.subTitle }} | diff --git a/src/templates/productList.html.twig b/src/templates/productList.html.twig index 074080a..9d3a418 100644 --- a/src/templates/productList.html.twig +++ b/src/templates/productList.html.twig @@ -6,12 +6,12 @@ | ||
| {% if product.starred %}★{% else %} ☆ {% endif %} | {{ product.name }} | +{{ product.name }} | {{ product.subTitle }} |