This commit is contained in:
6
webseite-react-php/slim-php/.env.example
Normal file
6
webseite-react-php/slim-php/.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=8889
|
||||
DB_NAME=nerdshop
|
||||
DB_USER=root
|
||||
DB_PASS=root
|
||||
CORS_ORIGIN=http://localhost:5173
|
||||
2
webseite-react-php/slim-php/.gitignore
vendored
Normal file
2
webseite-react-php/slim-php/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/vendor/
|
||||
.env
|
||||
100
webseite-react-php/slim-php/INSTALLATION.md
Normal file
100
webseite-react-php/slim-php/INSTALLATION.md
Normal file
@@ -0,0 +1,100 @@
|
||||
## MacOS Installation
|
||||
|
||||
Um die PHP-Version und Composer aus MAMP standardmäßig in deinem Terminal über die `~/.bashrc` zu nutzen, fügst du den Pfad zur gewünschten PHP-Version deinem `$PATH` voran.
|
||||
|
||||
**1. MAMP PHP-Version ermitteln**
|
||||
Prüfe im Finder unter `/Applications/MAMP/bin/php/`, welche Versionen installiert sind (z. B. `php8.2.0`).
|
||||
|
||||
**2. `.bashrc` bearbeiten**
|
||||
Öffne die Datei im Editor:
|
||||
|
||||
```bash
|
||||
nano ~/.bashrc
|
||||
|
||||
```
|
||||
|
||||
Füge am Ende folgende Zeilen ein (ersetze `php8.2.0` durch deine tatsächliche Version):
|
||||
|
||||
```bash
|
||||
# MAMP PHP zum PATH hinzufügen
|
||||
export PATH="/Applications/MAMP/bin/php/php8.2.0/bin:$PATH"
|
||||
|
||||
# Falls Composer separat als .phar oder Skript liegt, optional ein Alias setzen:
|
||||
# alias composer="/Applications/MAMP/bin/php/php8.2.0/bin/php /usr/local/bin/composer"
|
||||
|
||||
```
|
||||
|
||||
**3. Änderungen anwenden**
|
||||
Lade die Konfiguration neu:
|
||||
|
||||
```bash
|
||||
source ~/.bashrc
|
||||
|
||||
```
|
||||
|
||||
**4. Überprüfen**
|
||||
Teste, ob die Pfade korrekt geladen werden:
|
||||
|
||||
```bash
|
||||
which php
|
||||
php -v
|
||||
composer -v
|
||||
|
||||
```
|
||||
|
||||
> **Hinweis für macOS:** Standardmäßig nutzt das Terminal unter macOS seit macOS Catalina `zsh` statt `bash`. Falls deine Änderungen nach einem Terminal-Neustart nicht greifen, trage die gleichen Zeilen stattdessen in deine `~/.zshrc` ein.
|
||||
|
||||
|
||||
## Windows Installation
|
||||
|
||||
Unter Windows nutzt du für Bash in der Regel **Git Bash** oder **WSL**. In Git Bash entspricht `~/.bashrc` der Datei `C:\Users\<DeinBenutzer>\.bashrc`.
|
||||
|
||||
**1. MAMP PHP-Version ermitteln**
|
||||
Prüfe unter `C:\MAMP\bin\php\`, welche PHP-Version installiert ist (z. B. `php8.2.0`).
|
||||
|
||||
**2. `.bashrc` in Git Bash anpassen**
|
||||
Öffne die Datei in Git Bash:
|
||||
|
||||
```bash
|
||||
code ~/.bashrc
|
||||
|
||||
```****
|
||||
|
||||
Füge folgende Zeilen ein (Pfad an deine PHP-Version anpassen):
|
||||
|
||||
```bash
|
||||
# MAMP PHP zum PATH hinzufügen (Windows-Pfad im POSIX-Format)
|
||||
export PATH="/c/MAMP/bin/php/php8.2.0:$PATH"
|
||||
|
||||
# Composer Alias (falls composer.phar im MAMP-Verzeichnis oder Benutzerverzeichnis liegt)
|
||||
alias composer="/c/MAMP/bin/php/php8.2.0/php.exe /c/ProgramData/ComposerSetup/bin/composer.phar"
|
||||
|
||||
```
|
||||
|
||||
> Falls Composer als Windows-Batch-Datei (`composer.bat`) installiert ist, reicht bereits der PHP-Eintrag im `PATH`, da Git Bash installierte Windows-Programme direkt aufrufen kann.
|
||||
|
||||
**3. Änderungen anwenden**
|
||||
|
||||
```bash
|
||||
source ~/.bashrc
|
||||
|
||||
```
|
||||
|
||||
**4. Überprüfen**
|
||||
|
||||
```bash
|
||||
which php
|
||||
php -v
|
||||
composer --version
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Alternative: Systemweit für CMD / PowerShell setzen**
|
||||
Falls du PHP auch in der Windows Eingabeaufforderung oder PowerShell nutzen möchtest:
|
||||
|
||||
1. `Win + R` drücken, `sysdm.cpl` eingeben und Enter drücken.
|
||||
2. Reiter **Erweitert** → **Umgebungsvariablen**.
|
||||
3. Unter **Systemvariablen** (oder **Benutzervariablen**) die Variable `Path` auswählen und auf **Bearbeiten** klicken.
|
||||
4. Neuen Eintrag hinzufügen: `C:\MAMP\bin\php\php8.2.0` und ganz nach oben schieben.
|
||||
28
webseite-react-php/slim-php/README.md
Normal file
28
webseite-react-php/slim-php/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
Die MySQL-Datenbank und die Slim-REST-API sind eingerichtet und getestet.
|
||||
|
||||
**Datenbank `nerdshop`** (MAMP, Port 8889) enthält die Tabelle `products` mit den 13 Einträgen aus der JSON-Datei. Die Mongo-IDs bleiben als `id` erhalten; die API liefert sie als `_id`, damit die React-App kompatibel bleibt.
|
||||
|
||||
**API läuft auf** [http://localhost:8080](http://localhost:8080)
|
||||
|
||||
| Methode | URL | Status |
|
||||
| -------- | ---------------- | ------------- |
|
||||
| `GET` | `/products` | alle Produkte |
|
||||
| `GET` | `/products/{id}` | ein Produkt |
|
||||
| `POST` | `/products` | anlegen |
|
||||
| `PUT` | `/products/{id}` | aktualisieren |
|
||||
| `DELETE` | `/products/{id}` | löschen |
|
||||
|
||||
CORS ist für `http://localhost:5173` (Vite) gesetzt. In der React-App reicht später:
|
||||
|
||||
```js
|
||||
fetch('http://localhost:8080/products');
|
||||
```
|
||||
|
||||
Server neu starten:
|
||||
|
||||
```bash
|
||||
cd slim-php-framework
|
||||
php -S localhost:8080 -t public public/router.php
|
||||
```
|
||||
|
||||
Falls MAMP andere Zugangsdaten nutzt, stehen sie in `slim-php-framework/.env`.
|
||||
22
webseite-react-php/slim-php/composer.json
Normal file
22
webseite-react-php/slim-php/composer.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "nerdshop/slim-api",
|
||||
"version": "1.0.0",
|
||||
"description": "Nerdshop REST API",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-pdo": "*",
|
||||
"ext-pdo_mysql": "*",
|
||||
"php-di/php-di": "^7.0",
|
||||
"slim/psr7": "^1.7",
|
||||
"slim/slim": "^4.14",
|
||||
"vlucas/phpdotenv": "^5.6"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
1354
webseite-react-php/slim-php/composer.lock
generated
Normal file
1354
webseite-react-php/slim-php/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
webseite-react-php/slim-php/public/.htaccess
Normal file
4
webseite-react-php/slim-php/public/.htaccess
Normal file
@@ -0,0 +1,4 @@
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
33
webseite-react-php/slim-php/public/index.php
Normal file
33
webseite-react-php/slim-php/public/index.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Config;
|
||||
use App\Middleware\CorsMiddleware;
|
||||
use App\Repository\ProductRepository;
|
||||
use DI\Container;
|
||||
use Slim\Factory\AppFactory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$container = new Container();
|
||||
$container->set(PDO::class, static fn () => Config::pdo());
|
||||
$container->set(
|
||||
ProductRepository::class,
|
||||
static fn ($c) => new ProductRepository($c->get(PDO::class))
|
||||
);
|
||||
|
||||
AppFactory::setContainer($container);
|
||||
$app = AppFactory::create();
|
||||
|
||||
$app->addBodyParsingMiddleware();
|
||||
$app->addRoutingMiddleware();
|
||||
$app->addErrorMiddleware(true, true, true);
|
||||
$app->add(new CorsMiddleware());
|
||||
|
||||
(require __DIR__ . '/../src/Routes/productRoutes.php')($app);
|
||||
|
||||
$app->run();
|
||||
11
webseite-react-php/slim-php/public/router.php
Normal file
11
webseite-react-php/slim-php/public/router.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$uri = urldecode(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/');
|
||||
|
||||
if ($uri !== '/' && file_exists(__DIR__ . $uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
require __DIR__ . '/index.php';
|
||||
@@ -32,3 +32,26 @@ INSERT INTO products (id, title, sku, stock, price, description, tagline) VALUES
|
||||
('675bee96bb39023b77e6cda3', 'Automatic Plant Waterer with Compliment Dispenser', 'GRD0334', 17, 69.99, 'Keep your plants happy and hydrated with automatic watering and daily compliments.', 'Nurture your plants, boost their self-esteem.'),
|
||||
('675bee96bb39023b77e6cda4', 'Self-Stirring Cereal Bowl', 'KIT0445', 55, 24.95, 'Enjoy perfectly crunchy cereal every time with this self-stirring bowl.', 'No more soggy surprises.'),
|
||||
('675bee96bb39023b77e6cda5', 'Polygon Planet Lamp', 'LMP0556', 4, 39.90, 'Transform your space with the enchanting glow of a planetarium-inspired lamp.', 'Bring the cosmos into your home.');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
username VARCHAR(64) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
token VARCHAR(64) NULL DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_users_username (username),
|
||||
UNIQUE KEY uq_users_email (email),
|
||||
KEY idx_users_token (token)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO users (username, email, password_hash)
|
||||
VALUES (
|
||||
'megaman',
|
||||
'order@gkontra.de',
|
||||
'$2y$12$CdOg.e08jP968kQoA298Ne92r9pyEYBfSya4gFWQCh.X8gJhmDaVW'
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
email = VALUES(email),
|
||||
password_hash = VALUES(password_hash);
|
||||
|
||||
24
webseite-react-php/slim-php/sql/users.sql
Normal file
24
webseite-react-php/slim-php/sql/users.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
USE nerdshop;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
username VARCHAR(64) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
token VARCHAR(64) NULL DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_users_username (username),
|
||||
UNIQUE KEY uq_users_email (email),
|
||||
KEY idx_users_token (token)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO users (username, email, password_hash)
|
||||
VALUES (
|
||||
'megaman',
|
||||
'order@gkontra.de',
|
||||
'$2y$12$CdOg.e08jP968kQoA298Ne92r9pyEYBfSya4gFWQCh.X8gJhmDaVW'
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
email = VALUES(email),
|
||||
password_hash = VALUES(password_hash);
|
||||
30
webseite-react-php/slim-php/src/Config.php
Normal file
30
webseite-react-php/slim-php/src/Config.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use PDO;
|
||||
|
||||
final class Config
|
||||
{
|
||||
public static function pdo(): PDO
|
||||
{
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_NAME'] ?? 'nerdshop'
|
||||
);
|
||||
|
||||
return new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USER'] ?? 'root',
|
||||
$_ENV['DB_PASS'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
46
webseite-react-php/slim-php/src/Domain/Product.php
Normal file
46
webseite-react-php/slim-php/src/Domain/Product.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain;
|
||||
|
||||
final class Product
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $title,
|
||||
public string $sku,
|
||||
public int $stock,
|
||||
public float $price,
|
||||
public string $description,
|
||||
public string $tagline,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromRow(array $row): self
|
||||
{
|
||||
return new self(
|
||||
id: $row['id'],
|
||||
title: $row['title'],
|
||||
sku: $row['sku'],
|
||||
stock: (int) $row['stock'],
|
||||
price: (float) $row['price'],
|
||||
description: $row['description'],
|
||||
tagline: $row['tagline'],
|
||||
);
|
||||
}
|
||||
|
||||
/** Kompatibel mit der React-App (_id statt id). */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'_id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'sku' => $this->sku,
|
||||
'stock' => $this->stock,
|
||||
'price' => $this->price,
|
||||
'description' => $this->description,
|
||||
'tagline' => $this->tagline,
|
||||
];
|
||||
}
|
||||
}
|
||||
40
webseite-react-php/slim-php/src/Domain/User.php
Normal file
40
webseite-react-php/slim-php/src/Domain/User.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain;
|
||||
|
||||
final class User
|
||||
{
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $username,
|
||||
public string $email,
|
||||
public string $passwordHash,
|
||||
public ?string $token,
|
||||
public string $createdAt,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromRow(array $row): self
|
||||
{
|
||||
return new self(
|
||||
id: (int) $row['id'],
|
||||
username: $row['username'],
|
||||
email: $row['email'],
|
||||
passwordHash: $row['password_hash'],
|
||||
token: $row['token'] ?: null,
|
||||
createdAt: $row['created_at'],
|
||||
);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'username' => $this->username,
|
||||
'email' => $this->email,
|
||||
'created_at' => $this->createdAt,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
use Slim\Psr7\Response as SlimResponse;
|
||||
|
||||
final class CorsMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
$origin = $_ENV['CORS_ORIGIN'] ?? 'http://localhost:5173';
|
||||
|
||||
if ($request->getMethod() === 'OPTIONS') {
|
||||
$response = new SlimResponse(204);
|
||||
} else {
|
||||
$response = $handler->handle($request);
|
||||
}
|
||||
|
||||
return $response
|
||||
->withHeader('Access-Control-Allow-Origin', $origin)
|
||||
->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
||||
}
|
||||
}
|
||||
115
webseite-react-php/slim-php/src/Repository/ProductRepository.php
Normal file
115
webseite-react-php/slim-php/src/Repository/ProductRepository.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Domain\Product;
|
||||
use PDO;
|
||||
|
||||
final class ProductRepository
|
||||
{
|
||||
public function __construct(private PDO $pdo)
|
||||
{
|
||||
}
|
||||
|
||||
/** @return Product[] */
|
||||
public function findAll(): array
|
||||
{
|
||||
$stmt = $this->pdo->query('SELECT * FROM products ORDER BY sku');
|
||||
|
||||
return array_map(Product::fromRow(...), $stmt->fetchAll());
|
||||
}
|
||||
|
||||
public function findById(string $id): ?Product
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM products WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return $row ? Product::fromRow($row) : null;
|
||||
}
|
||||
|
||||
public function create(array $data): Product
|
||||
{
|
||||
$id = $data['_id'] ?? $this->generateId();
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO products (id, title, sku, stock, price, description, tagline)
|
||||
VALUES (:id, :title, :sku, :stock, :price, :description, :tagline)'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $id,
|
||||
'title' => $data['title'],
|
||||
'sku' => $data['sku'],
|
||||
'stock' => (int) $data['stock'],
|
||||
'price' => (float) $data['price'],
|
||||
'description' => $data['description'],
|
||||
'tagline' => $data['tagline'],
|
||||
]);
|
||||
|
||||
$product = $this->findById($id);
|
||||
|
||||
if ($product === null) {
|
||||
throw new \RuntimeException('Product could not be created');
|
||||
}
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
public function update(string $id, array $data): ?Product
|
||||
{
|
||||
if ($this->findById($id) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE products
|
||||
SET title = :title, sku = :sku, stock = :stock,
|
||||
price = :price, description = :description, tagline = :tagline
|
||||
WHERE id = :id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $id,
|
||||
'title' => $data['title'],
|
||||
'sku' => $data['sku'],
|
||||
'stock' => (int) $data['stock'],
|
||||
'price' => (float) $data['price'],
|
||||
'description' => $data['description'],
|
||||
'tagline' => $data['tagline'],
|
||||
]);
|
||||
|
||||
return $this->findById($id);
|
||||
}
|
||||
|
||||
public function patch(string $id, array $data): ?Product
|
||||
{
|
||||
$current = $this->findById($id);
|
||||
|
||||
if ($current === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->update($id, [
|
||||
'title' => $data['title'] ?? $current->title,
|
||||
'sku' => $data['sku'] ?? $current->sku,
|
||||
'stock' => array_key_exists('stock', $data) ? (int) $data['stock'] : $current->stock,
|
||||
'price' => array_key_exists('price', $data) ? (float) $data['price'] : $current->price,
|
||||
'description' => $data['description'] ?? $current->description,
|
||||
'tagline' => $data['tagline'] ?? $current->tagline,
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(string $id): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM products WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
|
||||
return $stmt->rowCount() > 0;
|
||||
}
|
||||
|
||||
private function generateId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(12));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Domain\User;
|
||||
use PDO;
|
||||
|
||||
final class UserRepository
|
||||
{
|
||||
public function __construct(private PDO $pdo)
|
||||
{
|
||||
}
|
||||
|
||||
/** @return User[] */
|
||||
public function findAll(): array
|
||||
{
|
||||
$stmt = $this->pdo->query('SELECT * FROM users ORDER BY username');
|
||||
|
||||
return array_map(User::fromRow(...), $stmt->fetchAll());
|
||||
}
|
||||
|
||||
public function findById(int $id): ?User
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return $row ? User::fromRow($row) : null;
|
||||
}
|
||||
|
||||
public function findByUsernameOrEmail(string $login): ?User
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT * FROM users WHERE username = :login OR email = :login LIMIT 1'
|
||||
);
|
||||
$stmt->execute(['login' => $login]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return $row ? User::fromRow($row) : null;
|
||||
}
|
||||
}
|
||||
155
webseite-react-php/slim-php/src/Routes/productRoutes.php
Normal file
155
webseite-react-php/slim-php/src/Routes/productRoutes.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Repository\ProductRepository;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\App;
|
||||
|
||||
return function (App $app): void {
|
||||
$container = $app->getContainer();
|
||||
|
||||
if ($container === null) {
|
||||
throw new RuntimeException('DI container is not configured');
|
||||
}
|
||||
|
||||
$json = static function (Response $response, mixed $data, int $status = 200): Response {
|
||||
$response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withStatus($status);
|
||||
};
|
||||
|
||||
$requiredFields = ['title', 'sku', 'stock', 'price', 'description', 'tagline'];
|
||||
|
||||
$validate = static function (array $data) use ($requiredFields): ?string {
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($data[$field]) || $data[$field] === '') {
|
||||
return "Missing field: {$field}";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
$app->get('/', function (Request $request, Response $response) use ($json): Response {
|
||||
|
||||
return $json($response, [
|
||||
'name' => 'Nerdshop API',
|
||||
'endpoints' => [
|
||||
'GET /products',
|
||||
'GET /products/{id}',
|
||||
'POST /products',
|
||||
'PUT /products/{id}',
|
||||
'PATCH /products/{id}',
|
||||
'DELETE /products/{id}',
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
// app.get('/products', (req,res) => {
|
||||
// const products = productManager.getAll();
|
||||
// res.send(products);
|
||||
// })
|
||||
|
||||
$app->get('/products', function (Request $request, Response $response) use ($container, $json): Response {
|
||||
$repo = $container->get(ProductRepository::class);
|
||||
$items = array_map(static fn($product) => $product->toArray(), $repo->findAll());
|
||||
|
||||
return $json($response, $items);
|
||||
});
|
||||
|
||||
$app->get('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json): Response {
|
||||
$product = $container->get(ProductRepository::class)->findById($args['id']);
|
||||
|
||||
if ($product === null) {
|
||||
return $json($response, ['error' => 'Product not found'], 404);
|
||||
}
|
||||
|
||||
return $json($response, $product->toArray());
|
||||
});
|
||||
|
||||
$app->post('/products', function (Request $request, Response $response) use ($container, $json, $validate): Response {
|
||||
$data = (array) $request->getParsedBody();
|
||||
$error = $validate($data);
|
||||
|
||||
if ($error !== null) {
|
||||
return $json($response, ['error' => $error], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$product = $container->get(ProductRepository::class)->create($data);
|
||||
} catch (\PDOException $exception) {
|
||||
if ((int) $exception->getCode() === 23000) {
|
||||
return $json($response, ['error' => 'SKU already exists'], 409);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $json($response, $product->toArray(), 201);
|
||||
});
|
||||
|
||||
$app->put('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json, $validate): Response {
|
||||
$data = (array) $request->getParsedBody();
|
||||
$error = $validate($data);
|
||||
|
||||
if ($error !== null) {
|
||||
return $json($response, ['error' => $error], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$product = $container->get(ProductRepository::class)->update($args['id'], $data);
|
||||
} catch (\PDOException $exception) {
|
||||
if ((int) $exception->getCode() === 23000) {
|
||||
return $json($response, ['error' => 'SKU already exists'], 409);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if ($product === null) {
|
||||
return $json($response, ['error' => 'Product not found'], 404);
|
||||
}
|
||||
|
||||
return $json($response, $product->toArray());
|
||||
});
|
||||
|
||||
$app->patch('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json): Response {
|
||||
$data = (array) $request->getParsedBody();
|
||||
$allowed = ['title', 'sku', 'stock', 'price', 'description', 'tagline'];
|
||||
$patch = array_intersect_key($data, array_flip($allowed));
|
||||
|
||||
if ($patch === []) {
|
||||
return $json($response, ['error' => 'No fields to update'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$product = $container->get(ProductRepository::class)->patch($args['id'], $patch);
|
||||
} catch (\PDOException $exception) {
|
||||
if ((int) $exception->getCode() === 23000) {
|
||||
return $json($response, ['error' => 'SKU already exists'], 409);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if ($product === null) {
|
||||
return $json($response, ['error' => 'Product not found'], 404);
|
||||
}
|
||||
|
||||
return $json($response, $product->toArray());
|
||||
});
|
||||
|
||||
$app->delete('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json): Response {
|
||||
$deleted = $container->get(ProductRepository::class)->delete($args['id']);
|
||||
|
||||
if (!$deleted) {
|
||||
return $json($response, ['error' => 'Product not found'], 404);
|
||||
}
|
||||
|
||||
return $response->withStatus(204);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user