Webprogramozás

Adattárolás

Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 2.408-as szoba
Tel: (1) 372-2500/8469
horvath.gyozo@inf.elte.hu

Ismétlés

Ismétlés

Dinamikus szerveroldali webprogramozás: program (PHP) állítja elő a tartalmat (HTML)

  • Output
    • statikus prototípus
    • dinamikus sablon
  • Input
    • kliens: link, űrlap
    • PHP: $_GET, $_POST
  • Űrlapfeldolgozás
    • Beolvasás, ellenőrzés ($_POST, $_GET)
    • Adatok (nincsen: $_POST, $_GET, HTML)
    • Kimenet (HTML)

Adatok a klienstől PHP-ig

Szerverprogram bemeneti adatai

Adattárolás

Közös adat mint erőforrás megosztása

Szkriptbeli változók

  • Élettartamuk a szkript futása
  • Utána megszűnnek
if (isset($szamlalo)) {
  $szamlalo += 1;
} else {
  $szamlalo = 0;
}
var_dump($szamlalo);

Külső adattárolás

Fájl

  • bármikor elérhető, nincs szükség plusz szoftverekre
  • könnyen értelmezhető (ember, program)
  • könnyen használható
  • viszonylag kis méretű

Adatbázis

  • biztonságos
  • típusos adatok
  • bonyolult adatszerkezetek
  • összetett kereshetőség
  • fejlett konkurenciakezelés

Fájlkezelés PHP-ban

Fájlműveletek PHP-ban

  • PHP-ban van lehetőség fájlok használatára (írás, olvasás, stb)
  • Mint a legtöbb programozási nyelvben
  • Csak a szerver helyi fájljai érhetők el
  • Sokféle nyelvi lehetőség
  • Tipikus folyamat
    • megnyitás
    • feldolgozás
    • bezárás

Alacsony szintű általános fájlműveletek

  • $f = fopen($fájlnév, $mód)
    • fájl vagy URL megnyitása
    • $f: logikai fájlkezelő
    • $mód: megnyitás módját
  • feof($f)
    • fájl vége?
  • fclose($f)
    • fájl bezárása
// $mód
r (csak olvasás)
r+ (olvasás, írás)
w (csak írás, üres fájl)
w+ (olvasás, írás, üres fájl)
a (hozzáfűzés)
a+ (hozzáfűzés, olvasás)
x (csak írás, üres fájl, ha létezik a fájl, akkor hiba)
x+ (olvasás, írás, üres fájl, ha létezik, akkor hiba)
c (csak írás, ha létezik, akkor elejére áll)
c+ (írás, olvasás, ha létezik, elejére áll)

Alacsony szintű beolvasás/kiírás

  • Beolvasás
    • fread($f, $hossz)
      • $hossz byte beolvasása
    • fscanf($f, $formátum)
      • formátum szerinti beolv.
    • fgets($f[, $hossz])
      • sor beolvasása
    • fgetcsv($f, $hossz[, $elv])
      • CSV sor beolvasása
  • Kiírás
    • fwrite($f, $s)
      • $s kiírása
    • fputs($f, $s)
      • ld. fwrite()
    • fprintf($f, $formátum, $változók)
      • formázott kiírás
    • fputcsv($f, $tömb[, $elv])
      • CSV sor kiírása

Magas szintű beolvasás/kiírás

  • Beolvasás
    • $tömb = file($fájlnév[, módosítók])
      • fájl soronként a tömbbe
    • $s = file_get_contents($fájlnév)
      • egész fájl szövegként
    • readfile($fájlnév)
      • fájl kimenetre írása (pl. képek kiírása)
    • fpassthru($f)
      • megnyitott fájl maradék részének kimenetre írása (pl. képek)
  • Kiírás
    • file_put_contents($fájlnév, $s)
      • szöveg fájlba írása

További fájlműveletek

  • mkdir($útvonal)
    • könyvtár létrehozása
  • rmdir($könyvtárnév)
    • könyvtár törlése
  • copy($forrás, $cél)
    • másolás
  • rename($mit, $mire)
    • átnevezés, mozgatás
  • unlink($fájlnév)
    • törlés
  • is_dir($fájlnév)
  • is_file($fájlnév)
  • is_readable($fájlnév)
  • is_writable($fájlnév)
  • is_link($fájlnév)
  • is_executable($fájlnév)

További fájlműveletek

  • basename($útvonal)
    • fájlnevet adja vissza
  • chown($fájl, $user)
  • chmod($fájl, $mód)
  • chgrp($fájl, $group)
  • stat($fájl)
    • fájl adatai tömbben
  • fseek($f, $offset)
    • fájlmutató mozgatása

Hibakezelés

  • Hiba esetén → hamis visszatérési érték
  • @ operátor: hibaüzenet kiírásának elnyomása
$f = @fopen('nem_letezik.txt', 'r');
if ($f) {
  /* ... */
  fclose($f);
}

Fájlszerkezet-vezérelt tárolás

Használati esetek

  • Fájl szerkezete kötött
    • mikroformátum
    • áttekinthetőség
    • interoperabilitás
  • → Magas és alacsony szintű fájlműveletek

1. példa

Adott filmcímek listája egy fájlban, soronként egy filmmel. Olvassuk ezt be egy tömbbe!

$filmek = file('lista.txt', 
               FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
/*
Array
(
  [0] => A hobbit
  [1] => A Gyűrűk Ura
  [2] => Út a vadonba
  [3] => Passió
)
*/
A hobbit
A Gyűrűk Ura
Út a vadonba
Passió
    

1. példa – hibaellenőrzés

// Required
$filmek = @file('lista_.txt', 
                FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) 
          or die('Nincs meg a fájl');

vagy

// Default data
$filmek = @file('lista_.txt', 
                FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!$filmek) {
  $filmek = [];
}

2. példa

Egy filmcímeket tartalmazó tömb fájlba mentése (egy cím egy sor)

// A tömb filmcímekkel
$filmek = [
  'Vuk',
  'Lolka és Bolka',
  'Macskafogó',
  'Kisvakond és barátai',
];

// Kiírás alacsony szintű műveletekkel
define('SORVEG', "\n");
$f = @fopen('mesek.txt', 'w') 
     or die('Hiba!');
if ($f) {
  foreach ($filmek as $film) {
    fputs($f, $film . SORVEG);
  }
  fclose($f);
}
// VAGY
define('SORVEG', "\n");
// Elemek összefűzése
$s = implode(SORVEG, $filmek) 
       . SORVEG;
// Kiírás magas szintű művelettel
$siker = @file_put_contents(
  'mesek.txt', $s);

3. példa

Adott egy rekordokból álló tömb. Végezzük el a kiírását úgy, hogy egy sorban egy rekordnyi információ legyen, az egyes értékeket soron belül tabulátorral válasszuk el!

$filmek = [
  [
    'cim'     => 'Passió',
    'rendezo' => 'Mel Gibson',
    'ev'      => '2004',
  ],
  [
    'cim'     => 'Pio atya - A csodák embere',
    'rendezo' => 'Carlo Carlei',
    'ev'      => '2000',
  ],
];

3. példa

define('SORVEG', "\n");
$f = @fopen('filmek.txt', 'w')
     or die('Hiba!');
if ($f) {
  foreach ($filmek as $film) {
    fputcsv($f, $film, "\t");
  }
  fclose($f);
}
$filmek = [
  [
    'cim'     => 'Passió',
    'rendezo' => 'Mel Gibson',
    'ev'      => '2004',
  ],
  [
    'cim'     => 'Pio atya - A csodák embere',
    'rendezo' => 'Carlo Carlei',
    'ev'      => '2000',
  ],
];
Passió  Mel Gibson  2004
Pio atya - A csodák embere  Carlo Carlei    2000
    

4. példa

Az előző példában kapott fájlt olvassuk be rekordok tömbjeként!

Passió  Mel Gibson  2004
Pio atya - A csodák embere  Carlo Carlei    2000
    
//Beolvasás a file paranccsal, és az utólagos soronkénti bontás
$filmek = file('filmek.txt', 
               FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach($filmek as &$film) {
  list($c, $r, $e) = explode("\t", $film);
  $film = [
    'cim'     => $c,
    'rendezo' => $r,
    'ev'      => $e,
  ];
} 

4. példa

Az előző példában kapott fájlt olvassuk be rekordok tömbjeként!

Passió  Mel Gibson  2004
Pio atya - A csodák embere  Carlo Carlei    2000
    
//Beolvasás soronként az fgetcsv-vel 
$filmek = [];
$f = @fopen('filmek.txt', 'r');
if ($f) {
  while ($sor = fgetcsv($f, 0, "\t")) {
    $filmek[] = [
      'cim'     => $sor[0],
      'rendezo' => $sor[1],
      'ev'      => $sor[2],
    ];
  }
  fclose($f);
}

5. példa

  • Az előző feladatbeli rekordok tömbjét tároljuk úgy a fájlban, hogy a rekord minden egyes mezeje külön sorba kerüljön, és az egyes rekordokat üres sor válassza el egymástól.
  • Oldjuk meg a tömb beolvasását is!
  • Alacsony szintű fájlműveletek

5. példa – kiírás

//Mentés fájlba
define('SORVEG', "\n");
$f = @fopen('filmek_tobbsor.txt', 'w')
     or die('Hiba!');
if ($f) {
  foreach ($filmek as $film) {
    fputs($f, $film['cim'] . SORVEG);
    fputs($f, $film['rendezo'] . SORVEG);
    fputs($f, $film['ev'] . SORVEG);
    fputs($f, SORVEG);
  }
  fclose($f);
}
$filmek = [
  [
    'cim'     => 'Passió',
    'rendezo' => 'Mel Gibson',
    'ev'      => '2004',
  ],
  // ...
];
Passió
Mel Gibson
2004

Pio atya - A csodák embere
Carlo Carlei
2000
    

5. példa – beolvasás

Passió
Mel Gibson
2004

Pio atya - A csodák embere
Carlo Carlei
2000
    
//Beolvasás fájlból
$filmek = [];
$f = @fopen('filmek_tobbsor.txt', 'r');
if ($f) {
  while (!feof($f)) {
    $cim = trim(fgets($f));
    $rendezo = trim(fgets($f));
    $ev = trim(fgets($f));
    $ures = fgets($f);
    if ($cim != '') {
      $filmek[] = [
        'cim'       => $cim,
        'rendezo'   => $rendezo,
        'ev'        => $ev,
      ];
    }
  }
  fclose($f);
}

Konkurens fájlhasználat

  • Ha egyszerre többen hívják meg a szkriptet → konkurens használat
  • flock($f, $op)
    • $op
      • LOCK_SH (olvasáshoz)
      • LOCK_EX (íráshoz)
      • LOCK_UN (kioldáshoz)
  • fflush($f)
    • fájlpuffer ürítése
define('SORVEG', "\n");
$f = @fopen('mesek.txt', 'w')
     or die('Hiba!');
if ($f) {
  if (flock($f, LOCK_EX)) {
    foreach ($filmek as $film) {
      fputs($f, $film . SORVEG);
    }
    flock($f, LOCK_UN);
  }
  fclose($f);
}

Konkurens fájlhasználat

  • Olvasáshoz is érdemes lockolni a fájlt, hogy közben ne kezdődhessen el egy írási folyamat.
$filmek = [];
$f = @fopen('lista.txt', 'r');
if ($f) {
  if (flock($f, LOCK_SH)) {
    while (!feof($f)) {
      $sor = trim(fgets($f));
      if ($sor != '') {
        $filmek[] = $sor;
      }
    }
    flock($f, LOCK_UN);
  }
  fclose($f);
}

Adatszerkezet-vezérelt tárolás

Adatszerkezet

  • Adatszerkezet mentése és betöltése
  • Fájl tartalma nem érdekes
  • Az adat rendelkezésre állása az érdekes
  • Adatszerkezet → szöveges formátum
  • → Sorosítás

Sorosítás

  • Egy adatszerkezet visszaalakítható szöveges megfelelője.
  • Használata
    • tárolás
    • átküldés
  • Sorosító függvények
    • serialize($érték) → szöveg
    • unserialize($szöveg) → érték
    • json_encode($érték) → szöveg
    • json_decode($szöveg) → érték

Példa

$filmek = [
  [
    'cim'      => 'Passió',
    'rendezo'  => 'Mel Gibson',
    'ev'       => '2004',
    'szereplok'=> [
      'Jim Caviezel',
      'Maia Morgenstern',
      'Christo Jivkov',
    ],
  ],
  [
    'cim'      => 'Feltámadás',
    'rendezo'  => 'Kevin Reynolds',
    'ev'       => '2016',
    'szereplok'=> [
      'Joseph Fiennes',
      'Tom Felton',
      'Cliff Curtis',
    ],
  ],
];

Serialize, unserialize

$s = serialize($filmek);
//a:2:{i:0;a:4:{s:3:"cim";s:7:"Passió";s:7:"rendezo";s:10:"Mel Gibson";s:2:"ev";s:4:"2004";s:9:"szereplok";a:3:{i:0;s:12:"Jim Caviezel";i:1;s:16:"Maia Morgenstern";i:2;s:14:"Christo Jivkov";}}i:1;a:4:{s:3:"cim";s:12:"Feltámadás";s:7:"rendezo";s:14:"Kevin Reynolds";s:2:"ev";s:4:"2016";s:9:"szereplok";a:3:{i:0;s:14:"Joseph Fiennes";i:1;s:10:"Tom Felton";i:2;s:12:"Cliff Curtis";}}}
$filmek2 = unserialize($s);
/*
Array
(
  [0] => Array
    (
      [cim] => Passió
      [rendezo] => Mel Gibson
      [ev] => 2004
      [szereplok] => Array
        (
          [0] => Jim Caviezel
          [1] => Maia Morgenstern
          [2] => Christo Jivkov
        )
    )
  [1] => Array
    (
      [cim] => Feltámadás
      [rendezo] => Kevin Reynolds
      [ev] => 2016
      [szereplok] => Array
        (
          [0] => Joseph Fiennes
          [1] => Tom Felton
          [2] => Cliff Curtis
        )
    )
)*/

json_encode

$s = json_encode($filmek);
//[{"cim":"Passi\u00f3","rendezo":"Mel Gibson","ev":"2004","szereplok":["Jim Caviezel","Maia Morgenstern","Christo Jivkov"]},{"cim":"Felt\u00e1mad\u00e1s","rendezo":"Kevin Reynolds","ev":"2016","szereplok":["Joseph Fiennes","Tom Felton","Cliff Curtis"]}]
[
  {
    "cim": "Passi\u00f3",
    "rendezo": "Mel Gibson",
    "ev": "2004",
    "szereplok": [
      "Jim Caviezel",
      "Maia Morgenstern",
      "Christo Jivkov"
    ]
  },
  {
    "cim": "Felt\u00e1mad\u00e1s",
    "rendezo": "Kevin Reynolds",
    "ev": "2016",
    "szereplok": [
      "Joseph Fiennes",
      "Tom Felton",
      "Cliff Curtis"
    ]
  }
]

json_decode

$filmek3 = json_decode($s, true);
/*
Array
(
  [0] => Array
    (
      [cim] => Passió
      [rendezo] => Mel Gibson
      [ev] => 2004
      [szereplok] => Array
        (
          [0] => Jim Caviezel
          [1] => Maia Morgenstern
          [2] => Christo Jivkov
        )
    )
  [1] => Array
    (
      [cim] => Feltámadás
      [rendezo] => Kevin Reynolds
      [ev] => 2016
      [szereplok] => Array
        (
          [0] => Joseph Fiennes
          [1] => Tom Felton
          [2] => Cliff Curtis
        )
    )
)*/

json_decode

$filmek3 = json_decode($s, false);
/*
Array
(
  [0] => stdClass Object
    (
      [cim] => Passió
      [rendezo] => Mel Gibson
      [ev] => 2004
      [szereplok] => Array
        (
          [0] => Jim Caviezel
          [1] => Maia Morgenstern
          [2] => Christo Jivkov
        )
    )
  [1] => stdClass Object
    (
      [cim] => Feltámadás
      [rendezo] => Kevin Reynolds
      [ev] => 2016
      [szereplok] => Array
        (
          [0] => Joseph Fiennes
          [1] => Tom Felton
          [2] => Cliff Curtis
        )
    )
)*/

Segédfüggvények

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);
}

Akármilyen adatszerkezetre működik

Példa – új szereplő hozzáadása

$filmek = load_from_file('filmek.json');

$filmek[0]['szereplok'][] = 'Monica Bellucci';

save_to_file('filmek.json', $filmek);
[
  {"cim":"Passi\u00f3","rendezo":"Mel Gibson","ev":"2004",
   "szereplok":["Jim Caviezel","Maia Morgenstern","Christo Jivkov","Monica Bellucci"]},
  {"cim":"Pio atya - A csod\u00e1k embere","rendezo":"Carlo Carlei","ev":"2000",
   "szereplok":["Sergio Castellitto","Sergio Albelli"]}
]

Hátrányok

  • Sok adat mozgatása
  • Rossz konkurrencia-kezelés
  • Manuális szűrés

Adatok feldolgozása

Tömbműveletek

  • array_map(callable $fn, array $x): másolás
  • array_filter(array $x, callable $fn): kiválogatás
  • array_reduce(array $x, callable $fn, $initial): általános összegzés
  • array_sum(array $x): összegzés
  • array_walk(array $x, callable $fn): iteráció
$numbers = [1, 2, 3, 4, 5];
$evens = array_filter($numbers, function ($e) {
  return $e % 2 === 0;
});

Tömbműveletek

  • in_array($mit, $tömb): eldöntés (logikai)
  • array_search($mit, $tömb): lineáris keresés (kulcs)
  • sort($tömb): rendezés (többféle van)
  • shuffle($tömb): keverés
  • array_keys($tömb): $tömb kulcsai tömbként
  • array_values($tömb): $tömb értékeit tömbként
  • array_key_exists($kulcs, $tömb): $kulcs szerepel-e a $tömb tömb kulcsai között.

Példa

Jelenítsük meg a filmeket felsorolásban! A lista fölött szűrőmező.

<?php
// Data
function getMoviesByYear(int $year = NULL): array {
  // ...
}
// Main
$year = (int)$_GET["year_filter"] ?? NULL;

$movies = getMoviesByYear($year);
?>
<h1>Movies</h1>
<form action="">
  <input name="year_filter">
  <button>Filter</button>
</form>
<ul>
  <?php foreach ($movies as $movie) : ?>
    <li>
      <?= $movie["title"] ?>
      (<?= $movie["year"] ?>)
    </li> 
  <?php endforeach ?>
</ul>

Adatok

movies.json

{
  "1": {
    "title": "The Passion of the Christ",
    "year": 2004,
    "director": "Mel Gibson"
  },
  "2": {
    "title": "Risen",
    "year": 2016,
    "director": "Kevin Reynolds"
  }
}

Példa

function getMoviesByYear(int $year = NULL): array {
  $all = load_from_file('movies.json', true);
  return array_filter($all, function ($movie) use ($year) {
    return $movie['year'] === $year;
  });
}

$movies = getMoviesByYear(2004);

JsonIO

interface FileIO {
  public function save_to_file(string $filename, $data);
  public function load_from_file(string $filename, bool $array_result = false, $default_data = []);
};
class JsonIO implements FileIO {
  public function save_to_file(string $filename, $data) {
    $s = json_encode($data);
    return file_put_contents($filename, $s, LOCK_EX);
  }
  public 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));
  }
}

JsonStorage (tömb)

class JsonStorage {
  private $io;
  private $filename;
  public function __construct(string $filename) {
    $this->io = new JsonIO();
    $this->filename = $filename;
  }
  public function all() {
    return $this->io->load_from_file($this->filename, true);
  }
  public function filter(callable $fn) {
    $all = $this->all();
    return array_filter($all, $fn);
  }
  public function insert(array $item): string {
    $all = $this->all();
    $id = uniqid('', true);
    $item['_id'] = $id;
    $all[$id] = $item;
    $this->io->save_to_file($this->filename, $all);
    return $id;
  }
  public function update(callable $filter, callable $updater) {
    $all = $this->all();
    array_walk($all, function (&$item) use ($filter, $updater) {
      if ($filter($item)) {
        $updater($item);
      }
    });
    $this->io->save_to_file($this->filename, $all);
  }
  public function delete(callable $filter) {
    $remaining = $this->filter(function ($elem) use ($filter) {
      return !$filter($elem);
    });
    $this->io->save_to_file($this->filename, $remaining);
  }
}

JsonStorage (tömb)

function getMoviesByYear(int $year = NULL): array {
  $movieRepository = new JsonStorage('movies.json');
  return $movieRepository->filter(function ($movie) use ($year) {
    return $movie['year'] === $year;
  });
}
function changeDirectorName(string $old, string $new) {
  $movieRepository = new JsonStorage('movies.json');
  $movieRepository->update(
    function ($movie) use ($old) {
      return $movie['director'] === $old;
    },
    function (&$movie) use ($new) {
      $movie['director'] = $new;
    }
  );
}

JsonStorage (objektum)

function getMoviesByYear(int $year = NULL): array {
  $movieRepository = new JsonStorage('movies.json');
  return $movieRepository->filter(function ($movie) use ($year) {
    return $movie->year === $year;
  });
}
class JsonStorage {
  // ...
  public function all() {
    return (array)$this->io->load_from_file($this->filename, false);
  }
  public function insert(object $item): string {
    $all = $this->all();
    $id = uniqid('', true);
    $item->_id = $id;
    $all[$id] = $item;
    $this->io->save_to_file($this->filename, $all);
    return $id;
  }
  // ...
}

Repository

function getMoviesByYear(int $year = NULL): array {
  $movieRepository = new MovieRepository();
  return $movieRepository->filter(function ($movie) use ($year) {
    return $movie->year === $year;
  });
}
// OR
$movies = $movieRepository->getMoviesByYear(2016);
class MovieRepository extends JsonStorage {
  public function __construct() {
    parent::__construct('movies.json');
  }
  public function getMoviesByYear(int $year = NULL): array {
    return $this->filter(function ($movie) use ($year) {
      return $movie->year === $year;
    });
  }
}

Repository (típushelyes)

$movieRepository = new MovieRepository();
$movieRepository->add(new Movie("God's Not Dead", 2014, 'Harold Cronk'));
$movies = $movieRepository->getMoviesByYear(2016);
$movieRepository->changeDirectorName('Mel Gibson', 'Gibson, Mel');
class Movie {
  public $_id = NULL;
  public $title;
  public $year;
  public $director;

  public function __construct($title = NULL, $year = NULL, $director = NULL) {
    $this->title = $title;
    $this->year = $year;
    $this->director = $director;
  }

  public static function from_array(array $arr): Movie {
    $instance = new Movie();
    $instance->_id = $arr['_id'] ?? NULL;
    $instance->title = $arr['title'] ?? NULL;
    $instance->year = $arr['year'] ?? NULL;
    $instance->director = $arr['director'] ?? NULL;
    return $instance;
  }

  public static function from_object(object $obj): Movie {
    return self::from_array((array)$obj);
  }
}

class MovieRepository {
  private $storage;
  public function __construct() {
    $this->storage = new JsonStorage('movies.json');
  }
  private function convert(array $arr): array {
    return array_map([Movie::class, 'from_object'], $arr);
  }
  public function all() {
    return $this->convert($this->storage->all());
  }
  public function add(Movie $movie): string {
    return $this->storage->insert($movie);
  }
  public function getMoviesByYear(int $year = NULL): array {
    return $this->convert($this->storage->filter(function ($movie) use ($year) {
      return $movie->year === $year;
    }));
  }
  public function changeDirectorName(string $old, string $new) {
    $this->storage->update(
      function ($movie) use ($old) {
        return $movie->director === $old;
      },
      function (&$movie) use ($new) {
        return $movie->director = $new;
      }
    );
  }
}

Megjegyzendő

  • Az adat van a központban
  • Adatszerkezet-vezérelt
  • JsonStorage, Repository osztályok