TuxCore Skillset

PHP/MySQL Simply.com Skill

skill php-mysql Guld

skills/php-mysql-simply/SKILL.md

PHP/MySQL Simply.com Skill

Formål

Denne skill bruges når TuxCore hjælper Thomas med PHP/MySQL-projekter, især projekter der skal køre på Simply.com uden tunge frameworks.

Den er relevant for:

Standardprincipper

Typisk struktur

project/
├── index.php
├── login.php
├── logout.php
├── dashboard.php
├── admin/
│   └── index.php
├── assets/
│   ├── style.css
│   └── app.js
├── includes/
│   ├── config.php
│   ├── db.php
│   ├── auth.php
│   ├── functions.php
│   └── csrf.php
├── uploads/
│   └── .htaccess
└── .htaccess

Hvis config kan ligge udenfor public webroot, foretrækkes det:

/private/
├── db_config.php
├── mail_config.php
└── vendor/
    └── autoload.php

/public_html/
└── project/
    ├── index.php
    └── includes/

Databaseforbindelse

Brug PDO som standard:

<?php
declare(strict_types=1);

$DB_HOST = 'localhost';
$DB_NAME = 'database_name';
$DB_USER = 'database_user';
$DB_PASS = 'database_password';
$DB_CHARSET = 'utf8mb4';

$dsn = "mysql:host={$DB_HOST};dbname={$DB_NAME};charset={$DB_CHARSET}";

$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
];

try {
    $pdo = new PDO($dsn, $DB_USER, $DB_PASS, $options);
} catch (PDOException $e) {
    http_response_code(500);
    exit('Databaseforbindelse fejlede.');
}

Prepared statements

Alle brugerinput skal gennem prepared statements:

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
$stmt->execute([
    ':email' => $email,
]);
$user = $stmt->fetch();

Indsættelse:

$stmt = $pdo->prepare('INSERT INTO users (name, email, created_at) VALUES (:name, :email, NOW())');
$stmt->execute([
    ':name' => $name,
    ':email' => $email,
]);

Opdatering:

$stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
$stmt->execute([
    ':name' => $name,
    ':id' => $id,
]);

Login og sessions

Sessions skal startes sikkert:

<?php
declare(strict_types=1);

if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

Login-check:

function require_login(): void
{
    if (empty($_SESSION['user_id'])) {
        header('Location: /login.php');
        exit;
    }
}

Efter login:

session_regenerate_id(true);

$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['user_name'] = $user['name'];
$_SESSION['user_role'] = $user['role'];

Logout:

$_SESSION = [];

if (ini_get('session.use_cookies')) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(),
        '',
        time() - 42000,
        $params['path'],
        $params['domain'],
        $params['secure'],
        $params['httponly']
    );
}

session_destroy();

header('Location: /login.php');
exit;

Passwords

Brug altid PHPs password-funktioner:

$hash = password_hash($password, PASSWORD_DEFAULT);

Verificering:

if (!password_verify($password, $user['password_hash'])) {
    $error = 'Forkert login.';
}

Roller og rettigheder

Brug simple roller som standard:

admin
suadmin
user
guest
crew
dj
tekniker
dc
kontor

Rolle-check:

function has_role(array|string $roles): bool
{
    $roles = (array)$roles;
    return isset($_SESSION['user_role']) && in_array($_SESSION['user_role'], $roles, true);
}

CSRF-beskyttelse

Forms der ændrer data bør bruge CSRF-token:

function csrf_token(): string
{
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }

    return $_SESSION['csrf_token'];
}

function csrf_validate(?string $token): bool
{
    return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], (string)$token);
}

Form input:

<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') ?>">

POST-check:

if (!csrf_validate($_POST['csrf_token'] ?? null)) {
    http_response_code(403);
    exit('Ugyldig forespørgsel.');
}

Output escaping

Alt output fra database/brugerinput skal escapes:

function e(?string $value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}

Brug:

<?= e($row['title']) ?>

Uploads

Uploadfunktioner skal validere:

Uploads bør ikke kunne køre PHP.

Eksempel .htaccess i uploadmappe:

Options -Indexes

<FilesMatch "\.(php|php3|php4|php5|phtml|phar)$">
    Require all denied
</FilesMatch>

Gem upload med sikkert navn:

$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'webp', 'gif'];

if (!in_array($ext, $allowed, true)) {
    exit('Ugyldig filtype.');
}

$filename = bin2hex(random_bytes(16)) . '.' . $ext;
$target = __DIR__ . '/../uploads/' . $filename;

move_uploaded_file($_FILES['file']['tmp_name'], $target);

.htaccess

Standard for at beskytte mapper:

Options -Indexes

Hvis clean URLs bruges:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

Mailintegration

Ved mail skal denne skill kombineres med:

skills/php-simply-mail/SKILL.md

Standardregel:

Standard UI

PHP-projekter bør have:

Fejlhåndtering

Brug ikke rå PHP-fejl på produktion.

I udvikling:

ini_set('display_errors', '1');
error_reporting(E_ALL);

I produktion:

ini_set('display_errors', '0');
error_reporting(E_ALL);

Log fejl server-side:

error_log('Fejlbesked her');

Vis brugervenlig fejl:

exit('Der opstod en fejl. Prøv igen senere.');

Når TuxCore laver $php $sql $full

Lever som udgangspunkt:

  1. Komplet filstruktur.
  2. Komplet database schema.
  3. Komplet config/database include.
  4. Komplet login/session-flow hvis relevant.
  5. Alle nødvendige PHP-filer.
  6. CSS og JS hvis relevant.
  7. Installationsvejledning.
  8. Testtrin.
  9. Sikkerhedsnoter.

Når TuxCore laver $php $patch

Lever kun:

  1. De nødvendige ændringer.
  2. Filnavn og placering.
  3. Kodeblok til udskiftning/indsættelse.
  4. Kort forklaring.
  5. Testtrin.

Når TuxCore laver $debug

Svar med:

  1. Hvad fejlen betyder.
  2. Mest sandsynlige årsag.
  3. Testkommandoer eller testtrin.
  4. Konkret fix.
  5. Næste skridt.

Typiske fejl

Blank side

Mulige årsager:

Test:

ini_set('display_errors', '1');
error_reporting(E_ALL);

Headers already sent

Mulige årsager:

Fix:

Database login fejler

Mulige årsager:

Fix:

Session virker ikke

Mulige årsager:

Fix:

Acceptkriterier

En PHP/MySQL løsning er godkendt når:

Tags

php, mysql, mariadb, pdo, simply, tuxcore, tuxinet, login, adminpanel, csrf, upload
php mysql pdo simply adminpanel