Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Windows Tipps & SecurityDave Plummer Has Made the Task Manager of Your Dreams(21.09.2026 um 21:20 Uhr)
Sichere ProgrammierungSubqueries and CTEs: Asking a Question Inside a Question(21.09.2026 um 21:00 Uhr)
Sichere ProgrammierungTVL Trend Analysis & Liquidity Risk Assessment: Lido(21.09.2026 um 21:00 Uhr)
Sichere ProgrammierungReact is Officially Dead in 2026 (Thanks to AI)(21.09.2026 um 21:01 Uhr)
Sichere ProgrammierungUsing SHA256 to Build Trustworthy Data Portals in Brazil(21.09.2026 um 21:01 Uhr)
Sichere Programmierung🚀 I reached 1,001 views on DEV!(21.09.2026 um 21:03 Uhr)
Sichere ProgrammierungReact Mental Models 2(21.09.2026 um 21:05 Uhr)
Sichere ProgrammierungAustralian RAM and SSD prices climb as stock tightens(21.09.2026 um 21:09 Uhr)
Windows Tipps & SecurityDave Plummer Has Made the Task Manager of Your Dreams(21.09.2026 um 21:20 Uhr)
Sichere ProgrammierungSubqueries and CTEs: Asking a Question Inside a Question(21.09.2026 um 21:00 Uhr)
Sichere ProgrammierungTVL Trend Analysis & Liquidity Risk Assessment: Lido(21.09.2026 um 21:00 Uhr)
Sichere ProgrammierungReact is Officially Dead in 2026 (Thanks to AI)(21.09.2026 um 21:01 Uhr)
Sichere ProgrammierungUsing SHA256 to Build Trustworthy Data Portals in Brazil(21.09.2026 um 21:01 Uhr)
Sichere Programmierung🚀 I reached 1,001 views on DEV!(21.09.2026 um 21:03 Uhr)
Sichere ProgrammierungReact Mental Models 2(21.09.2026 um 21:05 Uhr)
Sichere ProgrammierungAustralian RAM and SSD prices climb as stock tightens(21.09.2026 um 21:09 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

How I Built SirPhire — A Production-Grade PHP eCommerce Platform from Scratch

Introduction — Why Build Your Own Platform? I've been a backend developer for over six years. I've worked with WooCommerce, Shopify, and Magento — and every time, I felt like I was fighting the platform instead of solving the business pro…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Introduction — Why Build Your Own Platform?

I've been a backend developer for over six years. I've worked with WooCommerce, Shopify, and Magento — and every time, I felt like I was fighting the platform instead of solving the business problem. They're brilliant products, but when your requirements diverge even slightly from the happy path, you end up deep in plugin hell, monkey-patching template overrides, or paying for enterprise tiers you don't need.



So I built SirPhire — a fully custom, production-grade eCommerce platform using PHP 8.3 as its backbone, paired with MySQL 8, Redis, Nginx, vanilla JavaScript, Tailwind CSS, and Stripe for payments. The initial use case was a store selling personalized accessories — things like a customize mobile cover configurator where customers upload artwork and pick device models, or browse ready-made listings like an iPhone 15 cover with multiple finish options. No frameworks. No ORM magic hiding the SQL. Just clean, well-structured code that I understand entirely, can debug at 2 AM, and can extend without reading 400 pages of documentation.



This post is a full, honest deep-dive: architecture decisions, actual code, schema design, payment integration, Docker setup, testing, and deployment. By the end, you should have a complete mental model of how a real PHP eCommerce backend is structured — and enough code snippets to build your own.



Technology Stack (Simple Overview)




  1. PHP 8.3 → Main backend language (handles all the website logic)

  2. MySQL 8.0 → Database to store products, orders, users, and inventory

  3. Redis 7 → Super fast storage for cart, sessions, and caching

  4. Nginx 1.25 → Web server (handles requests and serves files)

  5. Tailwind CSS 3.4 → Makes the website look nice and modern

  6. Vanilla JavaScript (ES2024) → Adds interactive features like cart and galleries

  7. Stripe API → Handles all payments, refunds, and subscriptions

  8. Composer → Manages PHP libraries and packages

  9. Docker → Runs the app in containers (easy to develop and deploy)

  10. JWT (Firebase) → Secure login system for the API

  11. PHPUnit 11 → Used for testing the code

  12. PHPMailer / SES → Sends emails (order confirmations, password resets, etc.)



03

Project Architecture & Folder Structure



SirPhire follows a lightweight MVC pattern. There's no full framework — the router is ~120 lines of PHP, the templating engine is basic include with output buffering, and the database layer is a thin PDO wrapper. This makes the codebase extremely readable by anyone with core PHP knowledge.




# SirPhire Root Structure
sirphire/
├── app/
│ ├── Controllers/
│ │ ├── ProductController.php
│ │ ├── CartController.php
│ │ ├── OrderController.php
│ │ ├── AuthController.php
│ │ └── AdminController.php
│ ├── Models/
│ │ ├── Product.php
│ │ ├── User.php
│ │ ├── Order.php
│ │ ├── Cart.php
│ │ └── Inventory.php
│ ├── Services/
│ │ ├── PaymentService.php
│ │ ├── MailService.php
│ │ ├── CacheService.php
│ │ └── ImageService.php
│ ├── Middleware/
│ │ ├── AuthMiddleware.php
│ │ ├── RateLimiter.php
│ │ └── CsrfMiddleware.php
│ └── Api/
│ ├── ProductApiController.php
│ └── OrderApiController.php
├── core/
│ ├── Router.php
│ ├── Database.php
│ ├── Request.php
│ ├── Response.php
│ ├── Session.php
│ └── View.php
├── config/
│ ├── database.php
│ ├── app.php
│ └── mail.php
├── public/
│ ├── index.php # front controller
│ ├── assets/
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
├── resources/
│ └── views/
│ ├── layouts/
│ ├── storefront/
│ └── admin/
├── database/
│ ├── migrations/
│ └── seeds/
├── tests/
│ ├── Unit/
│ └── Integration/
├── docker/
├── .env
├── composer.json
└── docker-compose.yml






04

Database Design with MySQL 8


The schema is normalized to 3NF for most tables, with strategic denormalization on order_items to preserve a historical snapshot of pricing at the time of purchase. MySQL 8's window functions, CTEs, and JSON column support all get used heavily.




-- Users table with JSONB-like metadata
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) NOT NULL UNIQUE DEFAULT (UUID()),
name VARCHAR(150) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role ENUM('customer','admin','manager') DEFAULT 'customer',
address JSON,
phone VARCHAR(20),
email_verified_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_role (role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Products with full-text search support
CREATE TABLE products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
slug VARCHAR(280) NOT NULL UNIQUE,
description TEXT,
price DECIMAL(10,2) NOT NULL,
sale_price DECIMAL(10,2) NULL,
category_id BIGINT UNSIGNED NOT NULL,
brand VARCHAR(100),
images JSON, -- array of image paths/CDN URLs
attributes JSON, -- {color, size, weight, ...}
stock_qty INT UNSIGNED DEFAULT 0,
status ENUM('active','draft','archived') DEFAULT 'draft',
meta_title VARCHAR(255),
meta_desc VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id),
FULLTEXT INDEX ft_product (name, description),
INDEX idx_slug (slug),
INDEX idx_status_price (status, price)
) ENGINE=InnoDB;

-- Orders with snapshot pricing
CREATE TABLE orders (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_number VARCHAR(30) NOT NULL UNIQUE,
user_id BIGINT UNSIGNED NOT NULL,
status ENUM('pending','paid','processing','shipped','delivered','refunded','cancelled'),
subtotal DECIMAL(10,2) NOT NULL,
discount_total DECIMAL(10,2) DEFAULT 0.00,
shipping_total DECIMAL(10,2) DEFAULT 0.00,
tax_total DECIMAL(10,2) DEFAULT 0.00,
grand_total DECIMAL(10,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
shipping_addr JSON,
billing_addr JSON,
stripe_pi_id VARCHAR(100), -- Stripe PaymentIntent ID
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_user_status (user_id, status),
INDEX idx_order_number (order_number)
) ENGINE=InnoDB;

CREATE TABLE order_items (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
product_snapshot JSON NOT NULL, -- name, sku, price at order time
quantity SMALLINT UNSIGNED NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
line_total DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
) ENGINE=InnoDB;






05

PHP 8.3 Backend — Core Application


Router




<?php
declare(strict_types=1);

namespace SirPhire\Core;

class Router
{
private array $routes = [];
private array $middlewares = [];

public function get(string $path, array|callable $action): static
{
return $this->addRoute('GET', $path, $action);
}

public function post(string $path, array|callable $action): static
{
return $this->addRoute('POST', $path, $action);
}

public function middleware(string ...$names): static
{
$last = array_key_last($this->routes);
$this->routes[$last]['middlewares'] = $names;
return $this;
}

private function addRoute(string $method, string $path, mixed $action): static
{
$pattern = '#^' . preg_replace('/\{([a-z]+)\}/', '(?P<$1>[^/]+)', $path) . '$#';
$this->routes[] = [
'method' => $method,
'pattern' => $pattern,
'action' => $action,
'middlewares' => [],
];
return $this;
}

public function dispatch(Request $request): Response
{
foreach ($this->routes as $route) {
if ($route['method'] !== $request->method()) continue;
if (! preg_match($route['pattern'], $request->path(), $matches)) continue;

$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
$request->setParams($params);

foreach ($route['middlewares'] as $mw) {
app($mw)->handle($request);
}

$action = $route['action'];
if (is_array($action)) {
[$controller, $method] = $action;
return app($controller)->{$method}($request);
}
return $action($request);
}
return Response::notFound();
}
}






Database Layer — PDO Wrapper




namespace SirPhire\Core;

use PDO, PDOStatement, PDOException;

class Database
{
private static ?PDO $pdo = null;

public static function connection(): PDO
{
if (static::$pdo === null) {
$dsn = sprintf(
'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
env('DB_HOST'), env('DB_PORT', '3306'), env('DB_NAME')
);
static::$pdo = new PDO($dsn, env('DB_USER'), env('DB_PASS'), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone='+00:00'",
]);
}
return static::$pdo;
}

public static function run(string $sql, array $params = []): PDOStatement
{
$stmt = static::connection()->prepare($sql);
$stmt->execute($params);
return $stmt;
}

public static function transaction(callable $callback): mixed
{
$pdo = static::connection();
$pdo->beginTransaction();
try {
$result = $callback($pdo);
$pdo->commit();
return $result;
} catch (PDOException $e) {
$pdo->rollBack();
throw $e;
}
}
}






06

Authentication System with JWT


SirPhire uses a hybrid auth approach: traditional PHP sessions for the storefront (cookie-based), and JWT tokens for the REST API. The firebase/php-jwt package handles token signing and verification.




namespace SirPhire\Controllers;

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use SirPhire\Core\{Database, Request, Response};
use SirPhire\Models\User;

class AuthController
{
public function login(Request $request): Response
{
$email = filter_var($request->input('email'), FILTER_SANITIZE_EMAIL);
$password = $request->input('password');

$user = User::findByEmail($email);

if (! $user || ! password_verify($password, $user['password_hash'])) {
return Response::json(['error' => 'Invalid credentials'], 401);
}

$payload = [
'iss' => env('APP_URL'),
'sub' => $user['uuid'],
'iat' => time(),
'exp' => time() + 3600 * 24,
'role' => $user['role'],
];

$token = JWT::encode($payload, env('JWT_SECRET'), 'HS256');

return Response::json([
'token' => $token,
'user' => [
'name' => $user['name'],
'email' => $user['email'],
'role' => $user['role'],
],
]);
}

public function register(Request $request): Response
{
$data = $request->validated([
'name' => 'required|min:2|max:100',
'email' => 'required|email|unique:users',
'password' => 'required|min:8',
]);

$userId = User::create([
'name' => $data['name'],
'email' => strtolower($data['email']),
'password_hash' => password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => 12]),
]);

// Dispatch welcome email asynchronously via Redis queue
queue('mail:welcome', ['user_id' => $userId]);

return Response::json(['message' => 'Account created. Please verify your email.'], 201);
}
}






07

Product & Inventory Management


SirPhire's product model is designed to handle both simple and configurable products. In practice, this means a basic listing like an iPhone 15 cover in fixed colors can live alongside a fully configurable customize mobile cover product — where the customer uploads a photo, selects a material (matte, glossy, leather), and picks from device variants. Both are stored in the same products table; the attributes JSON column carries the variant data, and a separate product_customizations table holds uploaded artwork and configuration per order item.




namespace SirPhire\Models;

use SirPhire\Core\Database;

class Product
{
public static function all(array $filters = [], int $page = 1, int $per = 24): array
{
$where = ['p.status = :status'];
$params = ['status' => 'active'];

if (! empty($filters['q'])) {
$where[] = 'MATCH(p.name, p.description) AGAINST (:q IN BOOLEAN MODE)';
$params['q'] = $filters['q'] . '*';
}
if (! empty($filters['category'])) {
$where[] = 'p.category_id = :cat';
$params['cat'] = (int) $filters['category'];
}
if (! empty($filters['max_price'])) {
$where[] = 'p.price <= :max';
$params['max'] = (float) $filters['max_price'];
}

$sql = "
SELECT p.*, c.name AS category_name,
(p.sale_price IS NOT NULL AND p.sale_price < p.price) AS on_sale
FROM products p
JOIN categories c ON c.id = p.category_id
WHERE "
. implode(' AND ', $where) . "
ORDER BY p.created_at DESC
LIMIT :limit OFFSET :offset
"
;

$params['limit'] = $per;
$params['offset'] = ($page - 1) * $per;

return Database::run($sql, $params)->fetchAll();
}

public static function decrementStock(int $productId, int $qty): bool
{
$affected = Database::run(
"UPDATE products SET stock_qty = stock_qty - :qty
WHERE id = :id AND stock_qty >= :qty"
,
['qty' => $qty, 'id' => $productId]
)->rowCount();

return $affected > 0;
}
}






08

Shopping Cart with Redis Session Storage


The cart is stored in Redis as a JSON-encoded hash. This gives us extremely fast reads/writes and TTL-based expiry for abandoned carts. The predis/predis client is used for the Redis connection.




namespace SirPhire\Models;

use SirPhire\Services\CacheService;

class Cart
{
private string $key;
private const int TTL = 86400 * 7; // 7 days

public function __construct(string $sessionId)
{
$this->key = 'cart:' . $sessionId;
}

public function add(int $productId, int $qty = 1, array $options = []): void
{
$cart = $this->get();
$itemKey = $productId . '_' . md5(serialize($options));
$product = Product::findOrFail($productId);

if (isset($cart[$itemKey])) {
$cart[$itemKey]['qty'] += $qty;
} else {
$cart[$itemKey] = [
'product_id' => $productId,
'name' => $product['name'],
'sku' => $product['sku'],
'price' => $product['sale_price'] ?? $product['price'],
'image' => $product['images'][0] ?? null,
'qty' => $qty,
'options' => $options,
];
}

app(CacheService::class)->set($this->key, json_encode($cart), self::TTL);
}

public function total(): float
{
return array_reduce(
$this->get(),
fn($carry, $item) => $carry + ($item['price'] * $item['qty']),
0.0
);
}

private function get(): array
{
$raw = app(CacheService::class)->get($this->key);
return $raw ? json_decode($raw, true) : [];
}
}
09






09

Checkout & Stripe Payment Integration


SirPhire uses Stripe's Payment Intents API rather than the older Charges API. This gives us full 3D Secure support, better SCA compliance, and a cleaner webhook model. The flow: create a PaymentIntent server-side → confirm with Stripe.js client-side → fulfill via webhook.




namespace SirPhire\Services;

use Stripe\StripeClient;
use Stripe\Exception\{ApiErrorException, SignatureVerificationException};
use SirPhire\Core\Database;
use SirPhire\Models\{Order, Product};

class PaymentService
{
private StripeClient $stripe;

public function __construct()
{
$this->stripe = new StripeClient(env('STRIPE_SECRET_KEY'));
}

public function createPaymentIntent(array $cartItems, string $currency = 'usd'): array
{
$amount = collect($cartItems)->sum(fn($i) => (int) round($i['price'] * $i['qty'] * 100));

$intent = $this->stripe->paymentIntents->create([
'amount' => $amount,
'currency' => $currency,
'automatic_payment_methods' => ['enabled' => true],
'metadata' => [
'cart_hash' => md5(serialize($cartItems)),
],
]);

return [
'client_secret' => $intent->client_secret,
'payment_intent_id' => $intent->id,
];
}

public function handleWebhook(string $payload, string $sigHeader): void
{
try {
$event = \Stripe\Webhook::constructEvent(
$payload, $sigHeader, env('STRIPE_WEBHOOK_SECRET')
);
} catch (SignatureVerificationException) {
throw new \RuntimeException('Invalid Stripe webhook signature', 400);
}

match ($event->type) {
'payment_intent.succeeded' => $this->onPaymentSucceeded($event->data->object),
'payment_intent.failed' => $this->onPaymentFailed($event->data->object),
'charge.refunded' => $this->onRefunded($event->data->object),
default => null,
};
}

private function onPaymentSucceeded($intent): void
{
Database::transaction(function() use ($intent) {
$order = Order::findByStripeId($intent->id);
Order::updateStatus($order['id'], 'paid');

foreach ($order['items'] as $item) {
Product::decrementStock($item['product_id'], $item['quantity']);
}

queue('mail:order_confirmation', ['order_id' => $order['id']]);
});
}
}






Stripe.js — Client-Side Payment Form




<div id="payment-form">
<div id="payment-element"></div>
<button id="submit-btn" class="btn-primary w-full mt-6">
<span id="btn-text">Pay $<?= number_format($total, 2) ?></span>
<span id="spinner" class="hidden">Processing...</span>
</button>
<div id="payment-error" class="text-red-500 mt-3 hidden"></div>
</div>

<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('');
const elements = stripe.elements({
clientSecret: '',
appearance: { theme: 'night', variables: { colorPrimary: '#f4a732' } }
});

const paymentEl = elements.create('payment');
paymentEl.mount('#payment-element');

document.getElementById('submit-btn').addEventListener('click', async (e) => {
e.preventDefault();
document.getElementById('spinner').classList.remove('hidden');
document.getElementById('btn-text').classList.add('hidden');

const { error } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: '' }
});

if (error) {
document.getElementById('payment-error').textContent = error.message;
document.getElementById('payment-error').classList.remove('hidden');
document.getElementById('spinner').classList.add('hidden');
document.getElementById('btn-text').classList.remove('hidden');
}
});
</script>






10

REST API Layer





namespace SirPhire\Api;

use SirPhire\Core\{Request, Response};
use SirPhire\Models\Product;
use SirPhire\Services\CacheService;

class ProductApiController
{
public function index(Request $request): Response
{
$cacheKey = 'api:products:' . md5(serialize($request->query()));
$cached = app(CacheService::class)->get($cacheKey);

if ($cached) {
return Response::json(json_decode($cached, true))
->header('X-Cache', 'HIT');
}

$products = Product::all(
filters: $request->query(),
page: (int) $request->query('page', 1),
per: (int) $request->query('per_page', 24),
);

$response = [
'data' => $products,
'meta' => [
'total' => Product::count($request->query()),
'page' => (int) $request->query('page', 1),
'per_page' => (int) $request->query('per_page', 24),
],
];

app(CacheService::class)->set($cacheKey, json_encode($response), 300);

return Response::json($response)->header('X-Cache', 'MISS');
}
}






11

Frontend — HTML5, Tailwind CSS & Vanilla JS





<article class="group relative bg-neutral-900 rounded-xl overflow-hidden border border-white/5 hover:border-amber-400/40 transition-all duration-300">

<a href="/products/" class="block aspect-square overflow-hidden">
<img
src=""
alt=""
loading="lazy"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
<?php if ($product['on_sale']): ?>
<span class="absolute top-3 left-3 bg-amber-400 text-black text-xs font-bold px-2 py-1 rounded">SALE</span>
<?php endif; ?>
</a>

<div class="p-4">
<p class="text-xs text-neutral-500 uppercase tracking-wider mb-1"></p>
<h3 class="text-white font-medium text-sm truncate mb-3"></h3>
<div class="flex items-center justify-between">
<div>
<?php if ($product['on_sale']): ?>
<span class="text-amber-400 font-bold">$</span>
<span class="text-neutral-600 line-through text-xs ml-1">$</span>
<?php else: ?>
<span class="text-white font-bold">$</span>
<?php endif; ?>
</div>
<button
class="add-to-cart bg-amber-400 hover:bg-amber-300 text-black text-xs font-bold px-3 py-1.5 rounded-lg transition-colors"
data-product-id=""
>Add to Cart</button>
</div>
</div>
</article>






Cart JavaScript — AJAX Add to Cart




// Cart module — vanilla JS, no dependencies
const Cart = (() => {
const ENDPOINT = '/api/cart';
let state = { items: [], count: 0, total: 0 };

const updateUI = () => {
document.querySelectorAll('[data-cart-count]')
.forEach(el => el.textContent = state.count);
document.querySelectorAll('[data-cart-total]')
.forEach(el => el.textContent = `$${state.total.toFixed(2)}`);
};

const addItem = async (productId, qty = 1, options = {}) => {
try {
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name=csrf]')?.content,
},
body: JSON.stringify({ product_id: productId, qty, options })
});

if (!res.ok) throw new Error('Cart update failed');

const data = await res.json();
state = data.cart;
updateUI();
showToast('Added to cart ✓', 'success');
} catch (err) {
showToast(err.message, 'error');
}
};

const showToast = (msg, type) => {
const toast = document.createElement('div');
toast.className = `fixed bottom-6 right-6 px-4 py-3 rounded-lg text-sm font-medium z-50
${type === 'success' ? 'bg-amber-400 text-black' : 'bg-red-500 text-white'}`;
toast.textContent = msg;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
};

// Delegate click events for dynamic content
document.addEventListener('click', e => {
const btn = e.target.closest('[data-product-id]');
if (btn?.classList.contains('add-to-cart')) {
e.preventDefault();
addItem(btn.dataset.productId);
}
});

return { addItem, state };
})();

window.Cart = Cart;






12

Nginx Server Configuration





server {
listen 443 ssl http2;
server_name sirphire.com www.sirphire.com;

ssl_certificate /etc/letsencrypt/live/sirphire.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sirphire.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_session_cache shared:SSL:10m;

root /var/www/sirphire/public;
index index.php;

# Gzip compression
gzip on;
gzip_comp_level 5;
gzip_types text/css application/javascript application/json image/svg+xml;

# Cache static assets at the edge
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|woff2|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}

# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()";

# PHP-FPM
location / {
try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
fastcgi_pass php-fpm:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}

# Block access to sensitive files
location ~ /\.(env|git|htaccess) {
deny all;
}
}






13

Docker & Docker Compose Setup

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How I Built SirPhire — A Production-Grade PHP eCommerce Platform from Scratch

Thematisch verwandte Begriffe: Built, SirPhire, ProductionGrade, eCommerce · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-77582 | Tinyauth is an authentication and authorization server. Prior to 5.1.0, …
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick