Szerveroldali webprogramozás

A tárgy ismertetése. Ismétlés. Függvénykönyvtárak használata.

Tűri Erik
doktorandusz
1117 Budapest, Pázmány Péter sétány 1/c., 4.725-ös szoba
turierik@inf.elte.hu

Kontextus

Előzmények

  • Webprogramozás tárgy
  • Statikus vs dinamikus
  • Kliens és szerver
  • Alap szintű, natív
  • Függvénykönyvtárak nélkül
  • Keretrendszerek nélkül

Kliens evolúció

  • Statikus weboldalak
  • 1995-2000
    • dinamikus weboldalak
    • JS, Java, Flash
    • 1. böngészőháború
  • 2000-2006
    • Sötét középkor
    • 2. böngészőháború

Kliens evolúció

  • 2006-2010
    • AJAX
    • JS nyelv újrafelfedezése
    • programozási minták
    • Cross-browser
    • Progresszív fejlesztés (unobtrusive JS)
    • 3. böngészőháború
  • 2010-2014
    • tervezési minták
    • MV*
  • 2014-
    • Komponensalapú fejlesztés

Szerver evolúció

  • Statikus weboldalak
  • 1995-2000
    • dinamikus weboldalak
    • JS (!), PHP, Perl
  • 2000-
    • hatalmas fejlődés, vállalati rendszerek
    • tervezési minták
    • Java, ASP.NET
    • webszolgáltatások
  • 2000: REST
  • 2001: Drupal, Wordpress

Szerver evolúció

  • 2005: Microszervíz architektúrák
  • 2008: Node.js
  • 2011: WebSocket
  • 2015
    • HTTP/2
    • GraphQL

Érdekességek

Modern web architektúra

Forrás

A tárgy ismertetése

Információk

https://canvas.elte.hu/

  • általános információk
  • előadás anyagok
  • gyakorlati feladatsorok
  • feladatok, számonkérés
  • katalógus
  • kommunikáció

A tárgyról

  • Szerveroldali webprogramozás
  • Architektúra
  • Tervezési minták
  • Technológia
  • Eszközök
  • Fogalmak
  • Nyelvfüggetlen/implementáció

Ismétlés

Web komponensei, szabványai

  • HTML
  • URL
  • HTTP

URL

URL az MDN-en

HTTP protokoll

Dinamikus szerveroldali tartalom

A leküldendő tartalmat egy program állítja elő.

Architektúrák

Common Gateway Interface (CGI)

Azt határozza meg, hogy egy webszerver hogyan indíthat el egy programot és milyen módon cserél adatot vele.

  • Indítás: program futtatása
  • Adatok
    • környezeti változók
    • standard I/O
  • Program eredménye
    • standard kimeneten

Dinamikus szerveroldali webprogramozás

  • Program → HTML
  • Output (HTML generálás)
  • Input (link, űrlap)
  • Adattárolás: fájlok
  • Munkamenet-kezelés
  • Hitelesítés
  • Kódszervezés
  • AJAX kiszolgálás, JSON kommunikáció

PHP

  • alacsony belépési küszöb
  • széles körben használt
  • folyamatosan fejlődő nyelv

Kimenet generálás

<?php 
// input (?)
$tracks = [
  ["id" => 1, "name" => "guitar", "muted" => false],
  ["id" => 2, "name" => "bass",   "muted" => true ],
  ["id" => 3, "name" => "vocal",  "muted" => false],
];

// processing
$enabledTracks = array_filter($tracks, function ($track) {
  return !$track["muted"];
});

// output
?>
<div id="tracks">
  <?php foreach($enabledTracks as $t) : ?>
    <div class="track">
      <?= $t["name"] ?>
    </div>
  <?php endforeach ?>
</div>

Input, űrlapfeldolgozás

<?php
// debug
print_r($_GET);
print_r($_POST);

// Segédfüggvény
function is_empty($input, $key) {
  return !(isset($input[$key]) && trim($input[$key]) !== '');
}

// business logic
function kerulet(float $sugar): float {
  return 2 * $sugar * pi();
}

function validate($input, &$data, &$errors) {
    // sugár vizsgálata
  $data['sugar'] = null;
  if (is_empty($input, 'sugar')) {
    $errors[] = 'A sugár megadása kötelező';
  } 
  else if (!is_numeric($input['sugar'])) {
    $errors[] = 'A sugár nem szám!';
  }
  else {
    $data['sugar'] = (float)$input['sugar'];
  } 
  
  return !(bool)$errors;
}

$errors = [];
$input = $_GET;

if (count($_GET) !== 0) {
  // validation
  if (validate($input, $data, $errors)) {
    // input
    $sugar = $data['sugar'];
    // processing
    $ker = kerulet($sugar);
  }    
}

// output
?>
<?php if ($errors) : ?>
    <ul>
        <?php foreach($errors as $error) : ?>
            <li><?= $error ?></li>
        <?php endforeach; ?>
    </ul>
<?php endif; ?>

<form action="" method="GET">
    Radius: 
    <input type="text" name="sugar" value="<?= $input["sugar"] ?? "10" ?>">
    <button>Calculate</button>
</form>

<?php if (isset($ker)) : ?>
  <p>Sugár = <?php echo $sugar; ?></p>
  <p>Kerület = <?php echo $ker; ?></p>
<?php endif; ?>

Adattárolás

  • File-alapú
  • JSON formátumban
  • Sorosítva: json_encode, json_decode
function load_from_file(string $filename, bool $array_result = false, $default_data = []) {
  $s = @file_get_contents($filename);
  return ($s === false 
    ? $default_data 
    : json_decode($s, $array_result));
}

function save_to_file(string $filename, $data) {
  $s = json_encode($data);
  return file_put_contents($filename, $s, LOCK_EX);
}

Munkamenet-kezelés, hitelesítés

  • Munkamenet
    • kliensek megkülönböztetése
    • kliensenkénti adattárolás
  • $_SESSION
  • Hitelesítés

Függvénykönyvtárak használata

Névterek

  • Definiálás
    • namespace \A\B\C
  • Használat
    • \A\B\C\func()

Névterek

// definition and local use
namespace Tools\Html;
class Table { /* ... */ }
$table = new Table();
// Use outside the namespace
$table = new Tools\Html\Table();

// Use inside the namespace
namespace Tools\Html;
$table = new Table();

// class alias
use Tools\Html\Table as T;
$table = new T();

// namespace alias
use Tools\Html as H;
$table = new H\Table();

// namespace alias
use Tools\Html;
$table = new Html\Table();

Függvénykönyvtárak

  • Külső függvénykönyvtárak használata
  • Nem kell mindent nekünk megírni
  • Jól megírt, tesztelt megoldások (általában)

Composer

  • PHP csomagkezelője
  • Packagist: fő csomagtároló
  • composer require csomagnév
  • vendor mappába (.gitignore❗)
  • composer.json
  • composer install: függőségek telepítése
  • Composer autoload
require 'vendor/autoload.php';

Csomagok használata

  • névterekbe rendezve
  • namespace, use
// SleekDb.php
namespace SleekDB;
class SleekDB { /* ... */ }
// index.php
$myStore = \SleekDB\SleekDB::store('something', $dataDir);
// Or
// index.php, with using namespace
use \SleekDB\SleekDB;
$myStore = SleekDB::store('something', $dataDir);

Példa – tracklista

<?php
function get_all_tracks() {
  $tracks = json_decode(file_get_contents('tracks.array.json'), true);
  return $tracks;
}

$tracks = get_all_tracks();
?>
<ul>
  <?php foreach($tracks as $track) : ?>
    <li style="background-color: <?= $track['color'] ?>">
      <span>🎵 <?= $track['category'] ?></span>
      <?= $track['name'] ?> (<?= $track['instrument'] ?>)
    </li>
  <?php endforeach ?>
</ul>

Példa – tracklista

SleekDB: Flat file NoSQL-like database

<?php
require __DIR__ . '/vendor/autoload.php';

$dataDir = "./data";
$trackStore = \SleekDB\SleekDB::store('tracks', $dataDir);

$tracks = $trackStore->fetch();
?>
<ul>
  <?php foreach($tracks as $track) : ?>
    <li style="background-color: <?= $track['color'] ?>">
      <span>🎵 <?= $track['category'] ?></span>
      <?= $track['name'] ?> (<?= $track['instrument'] ?>)
    </li>
  <?php endforeach ?>
</ul>

Seed

require __DIR__ . '/vendor/autoload.php';

$dataDir = "./data";
$trackStore = \SleekDB\SleekDB::store('tracks', $dataDir);

// Delete store
$trackStore->deleteStore();
$trackStore = \SleekDB\SleekDB::store('tracks', $dataDir);

// Prepare users data.
$tracks = json_decode(file_get_contents('tracks.array.orig.json'), true);
$tracks = array_map(function ($track) {
    unset($track['id']);
    return $track;
}, $tracks);
// Insert all data.
$trackStore->insertMany($tracks);

echo "Seeded";

Példa – tracklista

Plates: Natív PHP sablonok

<?php
require __DIR__ . '/vendor/autoload.php';

// Data
$dataDir = "./data";
$trackStore = \SleekDB\SleekDB::store('tracks', $dataDir);
$tracks = $trackStore->fetch();

// Template
$templates = new League\Plates\Engine('./templates');
echo $templates->render('track_list', ['tracks' => $tracks]);

Példa – tracklista

Sablon és layout

<?php $this->layout('layouts/default') ?>
<ul>
    <?php foreach($tracks as $track) : ?>
    <li style="background-color: <?= $track['color'] ?>">
        <span>🎵 <?= $track['category'] ?></span>
        <?= $track['name'] ?> (<?= $track['instrument'] ?>)
    </li>
    <?php endforeach ?>
</ul>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>MIDI editor</title>
</head>
<body>
    <?=$this->section('content')?>
</body>
</html>

Példa – űrlap

<?php

function validate($post, &$data, &$errors) {
  if (!(isset($post['name']) && trim($post['name'])!=='')) {
    $errors['name'] = "The track name is required";
  } else {
    $data['name'] = $post['name'];
  }

  if (!(isset($post['color']) && trim($post['color'])!=='')) {
    $errors['color'] = "The track color is required";
  } 
  else if (filter_var($post['color'], FILTER_VALIDATE_REGEXP, [
    "options"=>[
      "regexp"=>"/^#[0-9a-f]{6}$/",
    ],
  ]) === false) {
    $errors['color'] = "The track color has a wrong format";
  }
  else {
    $data['color'] = $post['color'];
  }

  if (!(isset($post['category']) && trim($post['category'])!=='')) {
    $errors['category'] = "The category is required";
  } else {
    $data['category'] = $post['category'];
  }

  if (!(isset($post['instrument']) && trim($post['instrument'])!=='')) {
    $errors['instrument'] = "The instrument is required";
  }
  else if (filter_var($post['instrument'], FILTER_VALIDATE_INT) === false) {
    $errors['instrument'] = "The instrument has to be an integer";
  } else {
    $data['instrument'] = $post['instrument'];
  }
  
  return count($errors) === 0;
}

function add_track($data) {
  $data["id"] = uniqid();
  $data["notes"] = [];

  $tracks = json_decode(file_get_contents('tracks.array.json'), true);
  $tracks[] = $data;
  file_put_contents('tracks.array.json', json_encode($tracks), LOCK_EX);
}

$data = [];
$errors = [];
if ($_POST) {
  if (validate($_POST, $data, $errors)) {
    add_track($data);
    header('Location: index.php');
    exit();
  }
}
?>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>MIDI editor - Add new track</title>
  <link rel="stylesheet" href="https://webprogramozas.inf.elte.hu/webprog/zh/midi/midi.css">
</head>

<body>
  <h2>Add new track</h2>
  <form action="" method="post">
    <?php if ($errors) : ?>
      <div class="errors">
        <?= var_dump($errors) ?>
      </div>
    <?php endif ?>
    <div>
      <label for="name">Track name</label>
      <input type="text" id="name" name="name">
      (required)
    </div>
    <div>
      <label for="color">Color</label>
      <input type="text" id="color" name="color" placeholder="#1234af">
      (required, format: hex color code, e.g. #12af4d)
    </div>
    <div>
      <label for="category">Category</label>
      <input type="text" id="category" name="category" list="category-list">
      (required)
      <datalist id="category-list">
        <option value="Piano">
        <option value="Organ">
        <option value="Accordion">
        <option value="Strings">
        <option value="Guitar">
        <option value="Bass">
        <option value="Choir">
        <option value="Trumpet">
        <option value="Brass">
        <option value="Saxophone">
        <option value="Flute">
        <option value="Synth Lead">
        <option value="Synth Pad">
        <option value="Percussion">
        <option value="World">
        <option value="Synth effects">
        <option value="Sound effects">
      </datalist>
    </div>
    <div>
      <label for="instrument">Instrument</label>
      <select id="instrument" name="instrument">
        <option value="100">Instrument 1</option>
        <option value="200">Instrument 2</option>
        <option value="300">Instrument 3</option>
        <option value="400">Instrument 4</option>
        <option value="500">Instrument 5</option>
      </select>
      (required, number)
    </div>
    <div>
      <button type="submit">Add new track</button>
    </div>
  </form>
  <a href="index.php">Return to editor</a>
</body>

</html>

Példa – űrlap

SleekDB

require __DIR__ . '/vendor/autoload.php';

$dataDir = "./data";
$trackStore = \SleekDB\SleekDB::store('tracks', $dataDir);

// function validate() {}

function add_track($trackStore, $data) {
  $data["notes"] = [];
  $trackStore->insert($data);
}

$data = [];
$errors = [];
if ($_POST) {
  if (validate($_POST, $data, $errors)) {
    add_track($trackStore, $data);
    header('Location: index.php');
    exit();
  }
}

Példa – űrlap

Rakit: űrlapellenőrzés

require __DIR__ . '/vendor/autoload.php';
use Rakit\Validation\Validator;

$dataDir = "./data";
$trackStore = \SleekDB\SleekDB::store('tracks', $dataDir);

function add_track($trackStore, $data) {
  $data["notes"] = [];
  $trackStore->insert($data);
}

$errors = [];
if ($_POST) {
  $validator = new Validator();
  $validation = $validator->validate($_POST, [
      'name'                 => 'required',
      'color'                => 'required|regex:/^#[0-9a-f]{6}$/',
      'category'             => 'required',
      'instrument'           => 'required|integer',
  ]);
  
  if ($validation->fails()) {
    // handling errors
    $errors = $validation->errors()->all();
  } else {
    // validation passes
    $data = $validation->getValidData();
    add_track($trackStore, $data);
    header('Location: index.php');
    exit();
  }
}

Példa – űrlap

Plates: sablon

<?php
require __DIR__ . '/vendor/autoload.php';
use Rakit\Validation\Validator;

$dataDir = "./data";
$trackStore = \SleekDB\SleekDB::store('tracks', $dataDir);

function add_track($trackStore, $data) {
  $data["notes"] = [];
  $trackStore->insert($data);
}

$errors = [];
if ($_POST) {
  $validator = new Validator();
  $validation = $validator->validate($_POST, [
      'name'                 => 'required',
      'color'                => 'required|regex:/^#[0-9a-f]{6}$/',
      'category'             => 'required',
      'instrument'           => 'required|integer',
  ]);
  
  if ($validation->fails()) {
    // handling errors
    $errors = $validation->errors()->all();
  } else {
    // validation passes
    $data = $validation->getValidData();
    add_track($trackStore, $data);
    header('Location: index.php');
    exit();
  }
}

// Template
$templates = new League\Plates\Engine('./templates');
echo $templates->render('new_track', ['errors' => $errors]);

PSR, SPL

Környezet beállítása

Lehetőségek

  • szerver (pl. webprogramozas.inf.elte.hu)
  • teljes lokális környezet (pl. XAMPP, WAMP)
  • konténerizált lokális környezet
  • minimál lokális környezet (ajánlott telepítő)
    • php
    • composer
    • php -S localhost:3000

Végszó

  • Függvénykönyvtárak használata
  • Composer