Tutorial: PHP CRUD – OOP guida passo dopo passo (p.1)

Secondo me il miglior modo di imparare a programmare è interagire su progetti definiti, magari anche semplici, ma con un senso compiuto e che permettano di esercitarsi e magari provare delle varianti. E allora proviamo …

Php crud oop

Secondo me il miglior modo di imparare a programmare è interagire su progetti definiti, magari anche semplici, ma con un senso compiuto e che permettano di esercitarsi e magari provare delle varianti. E allora proviamo a capire la programmazione ad oggetti creando un piccolo sistema PHP CRUD (Create, Read, Update and Delete) per gestire un database MySql.

PHP CRUD – Panoramica

Queste tutorial prende in considerazione la creazione di una semplice applicazione di database e per gestire il problema useremo degli elementi di programmazione ad oggetti creando un sistema CRUD. Si rivelerà anche una conoscenza di base da poter usare in tanti progetti semplici o anche magari allargandosi a problemi più complessi.

Per costruire il frontend HTML useremo il framework Bootstrap in modo che la nostra applicazione abbia un’interfaccia utente decente. Se non hai ancora familiarità con cosa sia Bootstrap e vuoi imparare a usarlo, puoi trovare molti semplici tutorial in rete a breve ne scriverò uno anche su questo blog.

Struttura delle tabelle del database

Il progetto completo si trova sul mio account GitHub a disposizione di chi lo vuole usare ed in fondo al tutorial vi passo il link per scaricarlo; nel progetto sono inclusi anche i files products.sql e categories.sql che contengono in pratica il dump delle tabelle del database MySql.

La prima cosa necessaria è la creazione di un nuovo database MySql, si può fare con PhpMyAdmin o con altri tools per la gestione di MySql. Date al database il nome di php_oop_crud.

Ora dobbiamo creare le tabelle, per prima creeremo la tabella prodotti (products) per la quale sarà sufficiente copiare ed incollare nello spazio di esecuzione SQL di PhpMyAdmin e lanciare “esegui”:

-- Table structure for table `products`
CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `description` text NOT NULL,
  `price` int(11) NOT NULL,
  `category_id` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=38 ;

Poi utilizziamo lo stesso spazio per eseguire l’istruzione SQL che creerà una serie di prodotti popolando la tabella appena crea, bisogna fare la stessa operazione copiando ed incollando questo altro codice:

-- Dumping data for table `products`
INSERT INTO `products` (`id`, `name`, `description`, `price`, `category_id`, `created`, `modified`) VALUES
(1, 'LG P880 4X HD', 'My first awesome phone!', 336, 3, '2014-06-01 01:12:26', '2014-05-31 17:12:26'),
(2, 'Google Nexus 4', 'The most awesome phone of 2013!', 299, 2, '2014-06-01 01:12:26', '2014-05-31 17:12:26'),
(3, 'Samsung Galaxy S4', 'How about no?', 600, 3, '2014-06-01 01:12:26', '2014-05-31 17:12:26'),
(6, 'Bench Shirt', 'The best shirt!', 29, 1, '2014-06-01 01:12:26', '2014-05-31 02:12:21'),
(7, 'Lenovo Laptop', 'My business partner.', 399, 2, '2014-06-01 01:13:45', '2014-05-31 02:13:39'),
(8, 'Samsung Galaxy Tab 10.1', 'Good tablet.', 259, 2, '2014-06-01 01:14:13', '2014-05-31 02:14:08'),
(9, 'Spalding Watch', 'My sports watch.', 199, 1, '2014-06-01 01:18:36', '2014-05-31 02:18:31'),
(10, 'Sony Smart Watch', 'The coolest smart watch!', 300, 2, '2014-06-06 17:10:01', '2014-06-05 18:09:51'),
(11, 'Huawei Y300', 'For testing purposes.', 100, 2, '2014-06-06 17:11:04', '2014-06-05 18:10:54'),
(12, 'Abercrombie Lake Arnold Shirt', 'Perfect as gift!', 60, 1, '2014-06-06 17:12:21', '2014-06-05 18:12:11'),
(13, 'Abercrombie Allen Brook Shirt', 'Cool red shirt!', 70, 1, '2014-06-06 17:12:59', '2014-06-05 18:12:49'),
(25, 'Abercrombie Allen Anew Shirt', 'Awesome new shirt!', 999, 1, '2014-11-22 18:42:13', '2014-11-21 19:42:13'),
(26, 'Another product', 'Awesome product!', 555, 2, '2014-11-22 19:07:34', '2014-11-21 20:07:34'),
(27, 'Bag', 'Awesome bag for you!', 999, 1, '2014-12-04 21:11:36', '2014-12-03 22:11:36'),
(28, 'Wallet', 'You can absolutely use this one!', 799, 1, '2014-12-04 21:12:03', '2014-12-03 22:12:03'),
(30, 'Wal-mart Shirt', '', 555, 2, '2014-12-13 00:52:29', '2014-12-12 01:52:29'),
(31, 'Amanda Waller Shirt', 'New awesome shirt!', 333, 1, '2014-12-13 00:52:54', '2014-12-12 01:52:54'),
(32, 'Washing Machine Model PTRR', 'Some new product.', 999, 1, '2015-01-08 22:44:15', '2015-01-07 23:44:15');

Questo che segue è un altro snippet di codice SQL da utilizzare per creare la tabella delle categorie:

-- Table structure for table `categories`
CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(256) NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;

E poi andiamo a popolarla con dati di esempio con quest’altro snippet di codice:

-- Dumping data for table `categories`
INSERT INTO `categories` (`id`, `name`, `created`, `modified`) VALUES
(1, 'Fashion', '2014-06-01 00:35:07', '2014-05-30 17:34:33'),
(2, 'Electronics', '2014-06-01 00:35:07', '2014-05-30 17:34:33'),
(3, 'Motors', '2014-06-01 00:35:07', '2014-05-30 17:34:54');

Creazione layout frontend

Abbiamo creato il nostro database popolandolo con dei dati di esempio quindi, a questo punto, andiamo a progettare il layout frontend della nostra applicazione CRUD.

Come ho detto in precedenza utilizzeremo il framework Bootstrap per dare un aspetto gradevole al nostro progetto PHP CRUD quindi andiamo a creare le cartelle del nostro progetto e sotto alla cartella principale creiamo le sottocartelle:

  • config
  • css
  • inc

per suddividere i files in modo ordinato, poi creiamo i files dell’intestazione e del piè di pagina nella cartella principale del nostro progetto nominandoli rispettivamente header.php e footer.php in questo modo non dovremo riscriverli più volte, ma li caricheremo ogni volta che servono.

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  
    <title><?php echo $page_title; ?></title>
  
    <!-- Latest compiled and minified Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

   
    <!-- our custom CSS -->
    <link rel="stylesheet" href="./css/custom.css" />
   
</head>
<body>
  
    <!-- container -->
    <div class="container">
  
        <?php
        // show page header
        echo "<div class='page-header'>
                <h1>{$page_title}</h1>
            </div>";
        ?>

Questo è il codice del file header.php che in pratica prende esempio dallo starter template di Bootstrap dove usiamo la versione di Boostrap 5 linkandola via CDN e un nostro file CSS che stileremo più avanti per dare la nostra impostazione grafica al progetto, poi apriamo il <div> “container” che sarà il contenitore principale delle pagine.

Ora defininiamo il file footer.php con il seguente codice:

</div>
    <!-- /container -->
  
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  
<!-- Latest compiled and minified Bootstrap JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>

   
<!-- bootbox library last version -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.5.2/bootbox.min.js" integrity="sha512-RdSPYh1WA6BF0RhpisYJVYkOyTzK4HwofJ3Q7ivt/jkpW6Vc8AurL1R+4AUcvn9IwEKAPm/fk7qFZW3OuiUDeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  
</body>
</html>

qui nel footer.php andiamo a chiudere il “container” contenitore principale delle pagine e carichiamo le risorse javascript utilizzate da Boostrap, sempre via CDN, JQuery ed infine BootBox JS utilizzato per definire avvisi e finestre di dialogo.

Creazione di un file CSS personalizzato

Questo file verrà utilizzato per modificare lo stile che desideriamo sulla nostra pagina web, ma all’occorrenza lo utilizzeremo anche per sovrascrivere lo stile predefinito fornito da Bootstrap andiamo a crearlo all’interno della cartella /css ed a nominarlo come custom.css.

.left-margin{
    margin:0 .5em 0 0;
}
  
.right-button-margin{
    margin: 0 0 1em 0;
    overflow: hidden;
}
  
/* some changes in bootstrap modal */
.modal-body {
    padding: 20px 20px 0px 20px !important;
    text-align: center !important;
}
  
.modal-footer{
    text-align: center !important;
}

Ora, anche per renderci conto che tutto sia corretto, correggere eventuali errori e poter vedere qualcosa, creiamo la prima bozza del file index.php che lancerà subito il nostro progetto web. Lo scriviamo inserendo una variabile per richiamare il titolo della pagina nel file header.php (utile per cambiare ogni volta il tag <title> delle pagine che carichiamo) ed includendo i due files header.php e footer.php in questo modo:

<?php
// set page header
$page_title = "Read Products";
include_once "header.php";
  
// contents will be here
  
// set page footer
include_once "footer.php";
?>

Se apriamo il nostro progetto con il browser e tutto è corretto dovremmo avere visualizzato una pagina bianca, se abbiamo dubbi nel vedere la semplice pagina bianca possiamo inserire la riga:

echo "Hello, i'm here!";

subito dopo il commento

// contents will be here

in modo da visualizzare una scritta “Hello, i’m here” sullo schermo al posto della pagina bianca e renderci conto che tutto funzioni.

Creazione di record in PHP in modalità OOP

Abbiamo scelto di pensare e scrivere, il più possibile in modo OOP cioè cercando di capire la programmazione ad oggetti, quindi incominciamo a creare i files che ci servono e soprattutto le “classi”, le “proprietà” e le funzionalità (metodi), ovviamente non approfondirò la programmazione ad oggetti in questo tutorial, ma ho previsto un tutorial specifico che potete leggere per capirne le basi.

Restiamo in pratica ed applichiamone i concetti, cominciamo col creare un file create_product.php dove inseriamo le basi di partenza del progetto ed un link <button> per leggere i dati dal database.

<?php
// set page headers
$page_title = "Create Product";
include_once "header.php";
  
echo '<div class="d-grid gap-2 d-md-flex justify-content-md-end"><a href="index.php" class="btn btn-primary">Read Products</a></div>';
  
?>
<!-- 'create product' html form will be here -->
<?php
 
// footer
include_once "footer.php";
?>

Connessione al database

Dobbiamo connettere il database di lavoro e lo facciamo modificando il file create_products.php in modo da caricare i parametri di connessione.

Inserite questo codice prima della riga:

// set page headers

// include database and object files
include_once './config/database.php';
include_once './inc/product.php';
include_once './inc/category.php';
  
// get database connection
$database = new Database();
$db = $database->getConnection();
  
// pass connection to objects
$product = new Product($db);
$category = new Category($db);

Le prime tre righe andranno ad includere tre files: uno contenente i parametri della connessione al database, uno contentente la classe utilizzata per i prodotti ed uno contenente la classe utilizzata per le categorie.

Nelle altre righe iniziamo a trattare la programmazione ad oggetti e instanziamo oggetto Database(), oppure in altri termini, definiamo la nuova istanza di un oggetto che conterrà metodi e proprietà riservate al nostro database prelevandoli dalla classe Database (che creeremo più avanti). Da questo oggetto utilizziamo il metodo getConnection() che ci servirà per connetterci al database con i dati di accesso e l’estensione di PHP PDO acronimo di (PHP Data Object), una estensione che contiene le API di accesso al database introdotta dalla versione 5.x e attualmente utilizzata a questo scopo.

Poi instanziamo i due nuovi oggetti Product e Category contenenti metodi e proprietà riutilizzabili per i nostri prodotti e le nostre categorie.

Entriamo di conseguenza nel vivo della programmazione ad oggetti creando la nostra classe Database nel file /config/database.php :

<?php
class Database{
   
    // specify your own database credentials
    private $host = "localhost";
    private $db_name = ""; // insert your database name
    private $username = ""; // insert your database user 
    private $password = ""; // insert your database password
    public $conn;
   
    // get the database connection
    public function getConnection(){
   
        $this->conn = null;
   
        try{
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
        }catch(PDOException $exception){
            echo "Connection error: " . $exception->getMessage();
        }
   
        return $this->conn;
    }
}
?>

qui definiamo le proprietà contenenti i dati di accesso al nostro database ed il metodo getConnection() con l’Exception Handling, ovvero la gestione delle eccezioni try (prova) catch (cattura) necessaria per utilizzare PDO e l’accesso al database.

In pratica viene tentato l’accesso al database e se ci sono dei problemi viene restituito un codice di errore che ci da informazioni sul tipo di errore il quale può essere anche manipolato e personalizzato per avere informazioni maggiormente esaustive o subito velocemente interpretabili per la eventuale correzione.

Dobbiamo creare il form per l’inserimento dei dati quindi inseriamo il codice seguente al posto della riga

<!-- 'create product' html form will be here -->

nel file create_products.php :

<!-- PHP post code will be here -->
  
<!-- HTML form for creating a product -->
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>" method="post">
  
    <div class="mb-3">
        
            <label class="form-label">Name</label>
            <input type='text' name='name' class='form-control' />
                
            <label class="form-label">Price</label>
            <input type='text' name='price' class='form-control' />
                  
            <label class="form-label">Description</label>
            <textarea name='description' class='form-control'></textarea>
        
            <label class="form-label">Category</label>
            
			<?php
			// read the product categories from the database
			$stmt = $category->read();
			  
			// put them in a select drop-down
			echo '<select class="form-select" name="category_id">';
			    echo "<option>Select category...</option>";
			  
			    while ($row_category = $stmt->fetch(PDO::FETCH_ASSOC)){
			        extract($row_category);
			        echo "<option value='{$id}'>{$name}</option>";
			    }
			  
			echo "</select>";
			?>

	</div>		
            
            <button type="submit" class="btn btn-primary">Create</button>
  
    
</form>

Si tratta di un semplice form di inserimento dati dove il “name” dei campi input fa riferimento ai campi della tabella da inserire, tranne che alla voce delle categorie dove andiamo ad usare il metodo read() della classe Category per prelevare dal database le voci da assegnare al campo select del form.

Creazione della classe di oggetti per le categorie

Per far funzionare appunto il selettore del form e per lavorare sulle categorie andiamo quindi a creare la classe dell’oggetto Category inserendo questo codice nel file /inc/category.php :

<?php
class Category{
  
    // database connection and table name
    private $conn;
    private $table_name = "categories";
  
    // object properties
    public $id;
    public $name;
  
    public function __construct($db){
        $this->conn = $db;
    }
  
    // used by select drop-down list
    function read(){
        //select all data
        $query = "SELECT
                    id, name
                FROM
                    " . $this->table_name . "
                ORDER BY
                    name";  
  
        $stmt = $this->conn->prepare( $query );
        $stmt->execute();
  
        return $stmt;
    }
    // used to read category name by its ID
    function readName(){
          
        $query = "SELECT name FROM " . $this->table_name . " WHERE id = ? limit 0,1";
      
        $stmt = $this->conn->prepare( $query );
        $stmt->bindParam(1, $this->id);
        $stmt->execute();
      
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
          
        $this->name = $row['name'];
    }
  
}
?>

Questo è un oggetto un po’ più complesso di quello del database. Qui definiamo alcune proprietà: la connessione al database, il nome della tabella, un “id” e un “name” per la categoria.

Poi con il “metodo magico” __construct(), non si tratta di magia nera……tranquilli, ma solo di un metodo che viene istanziato automaticamente quando si istanzia l’oggetto, andiamo a definire subito che in questo oggetto deve essere conosciuta la connessione al database database dalla classe Database().

Nella stessa classe andiamo a creare due altri metodi read() e readName(). Il metodo read() definisce una selezione dei dati del database prelevando una lista di dati che contiene sia “id” che “name” ed il metodo readName() dove invece andiamo a selezionare il nome della categoria con precisione attraverso il suo “id”.

Invio del modulo

Cosa succede quando il modulo viene inviato tramite il “Create” <button>? Dovrebbe accadere che qualcuno inserirà valori nel modulo HTML e quando cliccherà il pulsante “Create” i valori verranno inviati tramite una richiesta POST, ma bisogna scrivere del codice per salvarlo nel database.

Allora apriamo di nuovo il file create_products.php ed a andiamo a sostituire la riga di commento

<!-- PHP post code will be here -->

con il seguente codice:

<?php 
// if the form was submitted save database 
if($_POST){
  
    // set product property values
    $product->name = $_POST['name'];
    $product->price = $_POST['price'];
    $product->description = $_POST['description'];
    $product->category_id = $_POST['category_id'];
  
    // create the product
    if($product->create()){
        echo "<div class='alert alert-success'>Product was created.</div>";
    }
  
    // if unable to create the product, tell the user
    else{
        echo "<div class='alert alert-danger'>Unable to create product.</div>";
    }
}
?>

dove vediamo che in base a dati inviati viene invocato il metodo create() della classe Product che si occuperà della creazione di un nuovo record nella tabella prodotti del database.

Creazione della classe di oggetti per i prodotti

A questo punto dobbiamo creare la classe Product e lo faremo nel file /inc/product.php copiando ed incollando il seguente codice:

<?php
class Product{
  
    // database connection and table name
    private $conn;
    private $table_name = "products";
  
    // object properties
    public $id;
    public $name;
    public $price;
    public $description;
    public $category_id;
    public $timestamp;
  
    public function __construct($db){
        $this->conn = $db;
    }
  
    // create product
    function create(){
  
        //write query
        $query = "INSERT INTO
                    " . $this->table_name . "
                SET
                    name=:name, price=:price, description=:description, category_id=:category_id, created=:created";
  
        $stmt = $this->conn->prepare($query);
  
        // posted values
        $this->name=htmlspecialchars(strip_tags($this->name));
        $this->price=htmlspecialchars(strip_tags($this->price));
        $this->description=htmlspecialchars(strip_tags($this->description));
        $this->category_id=htmlspecialchars(strip_tags($this->category_id));
  
        // to get time-stamp for 'created' field
        $this->timestamp = date('Y-m-d H:i:s');
  
        // bind values 
        $stmt->bindParam(":name", $this->name);
        $stmt->bindParam(":price", $this->price);
        $stmt->bindParam(":description", $this->description);
        $stmt->bindParam(":category_id", $this->category_id);
        $stmt->bindParam(":created", $this->timestamp);
  
        if($stmt->execute()){
            return true;
        }else{
            return false;
        }
  
    }
}
?>

Somiglia molto al funzionamento del codice della classe Category nell’assegnazione delle proprietà e nel metodo magico __construct() che in sostanza fanno le stesse cose, poi qui abbiamo solo un metodo create() che ovviamente viene usato per l’inserimento di un nuovo record nella tabella “products” assegnata nelle proprietà, preoccupandosi di rilevare i dati POST del form ed assegnarli ai campi della tabella “products” del database.

SEGUE PARTE 2

Lascia un commento