Ένα αφηρημένο dataset σε PHP

Από τον καιρό που προγραμμάτιζα σε Delphi, θυμάμαι ένα από τα πιο ενδιαφέροντα χαρακτηριστικά της: Την κλάση TDataset. Ήταν μία αφηρημένη κλάση που χρησιμοποιούνταν για την πρόσβαση σε μία ποικιλία από πηγές δεδομένων, συμπεριλαμβανομένων των βάσεων, αλλά όχι μόνο. Για να προσδιορίσει κάποιος τις λεπτομέρειες πρόσβασης σε μία συγκεκριμένη πηγή, θα έπρεπε να παράγει έναν απόγονο του TDataset, υλοποιώντας τις αφηρημένες μεθόδους του. Η πηγή θα μπορούσε να είναι ένα εξεζητημένο (R)DBMS (όλα τα δημοφιλή ήδη υποστηριζόταν), ένα αρχείο csv ή ακόμα και ένα δυναμικό σύνολο δεδομένων σε πραγματικό χρόνο.

Το να γράφεις λιγότερο κώδικα ήταν προφανής διευκόλυνση, αλλά το καλύτερο απ’ όλα ήταν η συνεργασία του δικού σου dataset με το υπόλοιπο framework. Άλλα αντικείμενα (η Delphi τα ονόμαζε data-aware) δούλευαν ομαλά με κάθε απόγονο του TDataset. Κατά συνέπεια, αρκετοί τρίτοι κατασκευαστές παρήγαγαν μία ποικιλία από αντικείμενα πρόσβασης σε δεδομένα, κάνοντας τη Delphi μία από τις καλύτερες επιλογές για την ανάπτυξη εφαρμογών πρόσβασης. Τελικά το Lazarus υιοθέτησε την ίδια λειτουργικότητα στη δική του υλοποίηση του TDataset.

Αυτού του είδους η λειτουργικότητα μου έλειπε στην PHP από την αρχή. Ασφαλώς έχει μεγάλη ποικιλία σε επεκτάσεις για πρόσβαση σε διάφορες πηγές, αλλά σίγουρα της λείπει η ομοιομορφία και κυρίως η επεκτασιμότητα. Αυτό το άρθρο είναι μία προσπάθεια παρουσίασης της ανάπτυξης μίας πολύ βασικής, αφηρημένης, κλάσης dataset. Αλλά πριν απ’ όλα, ας δώσουμε έναν ορισμό του dataset.

Ένα dataset είναι ένα διατεταγμένο σύνολο πλειάδων (tuples) με προκαθορισμένη οργάνωση που υπαγορεύεται από το σύνολο πεδίων (fieldset, ένα διατεταγμένο σύνολο από ορισμούς πεδίων) του dataset.

Αυτός ο ορισμός μοιάζει να περιγράφει έναν πίνακα με γραμμές και στήλες. Και στις δύο περιπτώσεις, για να προσπελάσουμε τα περιεχόμενα, θα πρέπει να εντοπίσουμε το αντίστοιχο κελί. Ένα κελί ανήκει σε ακριβώς μία πλειάδα και μία στήλη (πεδίο) οπότε οι συντεταγμένες του είναι (1) η σειρά της πλειάδας και (2) το όνομα του πεδίου. Για παράδειγμα, η διεύθυνση της 65ης καταχώρησης σε έναν τηλεφωνικό κατάλογο θα ήταν, σε όρους PHP, $phonebook->cell(65,’address’). Με άλλα λόγια αν το ‘address’ είναι 4ο στο σύνολο πεδίων, το εν λόγω δεδομένο θα ήταν το 4ο μέλος της $phonebook->tuple(65). Τέλος, υποθέτοντας ότι το $t=$phonebook->tuple_assoc(65) δίνει ένα associative array με τα ονόματα των πεδίων σαν κλειδιά, θα έπρεπε να κάνουμε $addr=$t[‘address’].

Τώρα, ένας ορισμός πεδίου είναι κάτι που εξαρτάται, στην πραγματικότητα, από την εφαρμογή. Είναι μία σύμβαση μεταξύ του παραγόμενου dataset και του κώδικα που το χρησιμοποιεί, γι αυτό θα προτιμούσα να το αφήσω αυθαίρετο. Όλοι οι ορισμοί των πεδίων θα πρέπει να επιστρέφονται από μία μέθοδο fields(), υπό την προϋπόθεση ότι η επιστρεφόμενη τιμή είναι ένα associative array με τα ονόματα των πεδίων σαν κλειδιά. Έπειτα από αυτό, τα ονόματα των πεδίων είναι array_keys($this->fields()).

Ένα χαρακτηριστικό που θα ήθελα, είναι να μπορώ να κάνω foreach($phonebook as $num=>$entry) και μέσα να φωλιάζω ένα foreach($entry as $field=>$data). Για να επιτευχθεί το πρώτο, η κλάση μας θα πρέπει να υλοποιεί το Iterator interface. Αυτό σημαίνει ότι πρέπει να διατηρούμε έναν εσωτερικό δείκτη δεδομένων και να υλοποιήσουμε πέντε μεθόδους, τις rewind(), current(), key(), next() και valid(). Για το δεύτερο, δεδομένου ότι το $this->position είναι ο δείκτης δεδομένων, είναι αρκετό να κάνουμε την current() να επιστρέφει $this->tuple_assoc($this->position). Για την υπόλοιπη λειτουργικότητα του Iterator, η rewind() θα πρέπει να μηδενίζει το δείκτη, η key() να τον επιστρέφει, η next() να τον αυξάνει και η valid() να ελέγχει αν είναι μικρότερος από $this->num_rows(), η οποία προφανώς επιστρέφει το συνολικό αριθμό πλειάδων.

Τώρα, ας τα συγκεντρώσουμε όλα σε μία κλάση της PHP:

<?php
abstract class Dataset 
    implements Iterator
	{
	private $position=0;
	abstract function num_rows();
	abstract function cell($row,$field);
    // fields
	abstract function fields();
	final function field_names() { return array_keys($this->fields()); }
	// tuples
	final function tuple($row)
		{
		$r=array();
		foreach($this->field_names() as $f) $r[]=$this->cell($row,$f);
		return $r;
		}
	final function tuple_assoc($row)
		{
		return array_combine($this->field_names(),$this->tuple($row));
		}
	// iterator stuff
	final function rewind() { $this->position=0; }
	final function current() { return $this->tuple_assoc($this->position); }
	final function key() { return $this->position; }
	final function next() { ++$this->position; } 
	final function valid() { return $this->position<$this->num_rows(); }
	}

Οπότε, ένας απόγονος του Dataset θα πρέπει να προσδιορίσει τρία πράγματα: (1) πόσες πλειάδες υπάρχουν (num_rows()), (2) τους ορισμούς των πεδίων (fields()) και (3) πως ανακτάται το περιεχόμενο ενός κελιού (cell()). Σημειώστε ότι δεν προβλέπεται λειτουργικότητα για εισαγωγή/ενημέρωση/διαγραφή, διότι δεν την χρειάζονται απαραίτητα όλων των ειδών τα datasets.

Παραδείγματα

Ένα null dataset δεν έχει ούτε πλειάδες ούτε πεδία, οπότε:

<?php
class NullDataset extends Dataset
    {
	function fields() { return array(); }
	function num_rows() { return 0; }
	function cell() { return NULL; }
	}

Αν θέλαμε να δούμε ένα array σαν dataset, θα το κάναμε ως εξής:

<?php
class SuitsDataset extends Dataset
    {
    private $arr=array(
    	array('♠','Spades','black'),
		array('♥','Hearts','red'),
		array('♦','Diamonds','red'),
		array('♣','Clubs','black'),
		);
	function fields() { return array('suit'=>array(),'suit_name'=>array(),'color'=>array()); }	
	function num_rows() { return count($this->arr); }
	function cell($n,$field) 
        { 
        $k=array_search($field,$this->field_names());
        return $this->arr[$n][$k]; 
        }
	}

Η μεταβλητή $_SESSION είναι ένα associative array που περιέχει δεδομένα για τη συνεδρία. Παρακάτω είναι ένα dataset μίας-πλειάδας, που περιέχει τα δεδομένα της $_SESSION:

<?php
class SessionDataset extends Dataset
    {
    function fields() 
        { 
        $r=array();
        foreach($_SESSION as $key=>$value) $r[$key]=array();
        return $r; 
        }
	function num_rows() { return 1; }
	function cell($n,$field) { return $_SESSION[$field]; }
	}

Τέλος, το παρακάτω κομμάτι κώδικα σχηματίζει ένα dataset μέσα σε ένα <table>:

<?php
echo "<table><tr>";
$dataset=new SuitsDataset();
foreach($dataset->field_names() as $f) echo "<th>$f</th>";
echo "</tr>";
foreach($dataset as $tuple)
    {
    echo "<tr>";
    foreach($row as $v) echo "<td>$v</td>";
    echo "</tr>";
    }
echo "</table>";

Αυτά προς ώρας. Τα λέμε την επόμενη φορά!

Αφήστε μια απάντηση

Η ηλ. διεύθυνση σας δεν δημοσιεύεται. Τα υποχρεωτικά πεδία σημειώνονται με *