-4

Suppose I have a product table with attributes id, name, quantity, image, description. and I have another table image with attributes id, productId, imagepath.

and I have a form like this

<form method="post" action=""> 
  < input type="text" name="name" /> 
  < input type="text" name="quantity" /> 
  < input type="file" name="images[]"  multiple/> 
  < input type="text" name="description" /> 
 </form>

I just started learning PHP and I am not very proficient in using php and mysql but I really wish to know how can I add a product with details(name, quantity, description) and two or more images to my database and Later on in another page retrieve the same product with details and the two or more images that belongs to this particular product.

I don't really know if creating a product and an image table (the image table will get the productId and store imagepath for each upload) is the best way to solve it. So I wish to find an easy or reliable way to do it and If you can, for example, illustrate with an example it will be of great help to me. Thanks in advance.

Labelle Doriane
  • 95
  • 1
  • 1
  • 13
  • You are getting downvotes because your question falls out of the scope of where Stackoverflow is designed to help. This sort of question should be resolvable by following some tutorials, reading books etc. Stackoverflow is for where you have have had a go at some code that has beaten you. Submit an attempt and ask for assistance where you are going wrong. People will help you then. read [ask] – Alan Feb 17 '18 at 04:38
  • @Alan I have gone through a lot of tutorials and tried a lot of codes but nothing work so far and I was getting more and more confused, so I decided to seek for a more detailed help with someone point of thinking. – Labelle Doriane Feb 17 '18 at 09:09

1 Answers1

2

Yes, this is how you should do it: a "products" table and a "products_images" table. The later should have a foreign key set: "product_id" column should reference the "id" in "products" table. And both tables must have the "id" columns as primary keys.

I hope you will understand the code:

  • addProduct.php contains the form and adds a product. getProduct.php displays the details of a selected product.
  • A link will appear after successfully adding a product (in addProduct.php), in order to have a way to display its details in getProduct.php.
  • A directory will be automatically created at the path provided in the config.php file. I set the directory path to "uploads". This path will be prepended to an image filename upon upload and saved in the corresponding column "filename" in "products_images".
  • Multiple file uploads.
  • When you submit the form with the provided product details, the user input values will be validated and corresponding error messages will appear over the form.
  • Beside addProduct.php and getProduct.php, there are two auxiliary files: config.php holds some constants regarding upload; connection.php holds the database connection instance and the constants needed for it.
  • The database access operations are performed using PDO/MySQLi and the so called prepared statements.

Just test the code as it is, first, so that you see what it does. Of course, after you create the tables as I did (see the create table syntaxes below).

Good luck.

Create table syntaxes

CREATE TABLE `products` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `quantity` int(11) DEFAULT NULL,
  `description` varchar(150) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `products_images` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `product_id` int(11) unsigned DEFAULT NULL,
  `filename` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `product_id` (`product_id`),
  CONSTRAINT `products_images_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

config.php

<?php

// Upload configs.
define('UPLOAD_DIR', 'uploads');
define('UPLOAD_MAX_FILE_SIZE', 10485760); // 10MB.
//@changed_2018-02-17_14.28
define('UPLOAD_ALLOWED_MIME_TYPES', 'image/jpeg,image/png,image/gif');

PDO solution

You already have it...

MySQLi solution (object-oriented style)

connection.php

<?php

// Db configs.
define('HOST', 'localhost');
define('PORT', 3306);
define('DATABASE', 'tests');
define('USERNAME', 'root');
define('PASSWORD', 'root');
define('CHARSET', 'utf8');

/*
 * Enable internal report functions. This enables the exception handling, 
 * e.g. mysqli will not throw PHP warnings anymore, but mysqli exceptions 
 * (mysqli_sql_exception).
 * 
 * MYSQLI_REPORT_ERROR: Report errors from mysqli function calls.
 * MYSQLI_REPORT_STRICT: Throw a mysqli_sql_exception for errors instead of warnings. 
 * 
 * @link http://php.net/manual/en/class.mysqli-driver.php
 * @link http://php.net/manual/en/mysqli-driver.report-mode.php
 * @link http://php.net/manual/en/mysqli.constants.php
 */
$mysqliDriver = new mysqli_driver();
$mysqliDriver->report_mode = (MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

/*
 * Create a new db connection.
 * 
 * @see http://php.net/manual/en/mysqli.construct.php
 */
$connection = new mysqli(HOST, USERNAME, PASSWORD, DATABASE, PORT);

addProduct.php

<?php
include 'config.php';
include 'connection.php';

$productSaved = FALSE;

if (isset($_POST['submit'])) {
    /*
     * Read posted values.
     */
    $productName = isset($_POST['name']) ? $_POST['name'] : '';
    $productQuantity = isset($_POST['quantity']) ? $_POST['quantity'] : 0;
    $productDescription = isset($_POST['description']) ? $_POST['description'] : '';

    /*
     * Validate posted values.
     */
    if (empty($productName)) {
        $errors[] = 'Please provide a product name.';
    }

    if ($productQuantity == 0) {
        $errors[] = 'Please provide the quantity.';
    }

    if (empty($productDescription)) {
        $errors[] = 'Please provide a description.';
    }

    /*
     * Create "uploads" directory if it doesn't exist.
     */
    if (!is_dir(UPLOAD_DIR)) {
        mkdir(UPLOAD_DIR, 0777, true);
    }

    /*
     * List of file names to be filled in by the upload script 
     * below and to be saved in the db table "products_images" afterwards.
     */
    $filenamesToSave = [];

    $allowedMimeTypes = explode(',', UPLOAD_ALLOWED_MIME_TYPES);

    /*
     * Upload files.
     */
    if (!empty($_FILES)) {
        if (isset($_FILES['file']['error'])) {
            foreach ($_FILES['file']['error'] as $uploadedFileKey => $uploadedFileError) {
                if ($uploadedFileError === UPLOAD_ERR_NO_FILE) {
                    $errors[] = 'You did not provide any files.';
                } elseif ($uploadedFileError === UPLOAD_ERR_OK) {
                    $uploadedFileName = basename($_FILES['file']['name'][$uploadedFileKey]);

                    if ($_FILES['file']['size'][$uploadedFileKey] <= UPLOAD_MAX_FILE_SIZE) {
                        $uploadedFileType = $_FILES['file']['type'][$uploadedFileKey];
                        $uploadedFileTempName = $_FILES['file']['tmp_name'][$uploadedFileKey];

                        $uploadedFilePath = rtrim(UPLOAD_DIR, '/') . '/' . $uploadedFileName;

                        if (in_array($uploadedFileType, $allowedMimeTypes)) {
                            if (!move_uploaded_file($uploadedFileTempName, $uploadedFilePath)) {
                                $errors[] = 'The file "' . $uploadedFileName . '" could not be uploaded.';
                            } else {
                                $filenamesToSave[] = $uploadedFilePath;
                            }
                        } else {
                            $errors[] = 'The extension of the file "' . $uploadedFileName . '" is not valid. Allowed extensions: JPG, JPEG, PNG, or GIF.';
                        }
                    } else {
                        $errors[] = 'The size of the file "' . $uploadedFileName . '" must be of max. ' . (UPLOAD_MAX_FILE_SIZE / 1024) . ' KB';
                    }
                }
            }
        }
    }

    /*
     * Save product and images.
     */
    if (!isset($errors)) {
        /*
         * The SQL statement to be prepared. Notice the so-called markers, 
         * e.g. the "?" signs. They will be replaced later with the 
         * corresponding values when using mysqli_stmt::bind_param.
         * 
         * @link http://php.net/manual/en/mysqli.prepare.php
         */
        $sql = 'INSERT INTO products (
                    name,
                    quantity,
                    description
                ) VALUES (
                    ?, ?, ?
                )';

        /*
         * Prepare the SQL statement for execution - ONLY ONCE.
         * 
         * @link http://php.net/manual/en/mysqli.prepare.php
         */
        $statement = $connection->prepare($sql);

        /*
         * Bind variables for the parameter markers (?) in the 
         * SQL statement that was passed to prepare(). The first 
         * argument of bind_param() is a string that contains one 
         * or more characters which specify the types for the 
         * corresponding bind variables.
         * 
         * @link http://php.net/manual/en/mysqli-stmt.bind-param.php
         */
        $statement->bind_param('sis', $productName, $productQuantity, $productDescription);

        /*
         * Execute the prepared SQL statement.
         * When executed any parameter markers which exist will 
         * automatically be replaced with the appropriate data.
         * 
         * @link http://php.net/manual/en/mysqli-stmt.execute.php
         */
        $statement->execute();

        // Read the id of the inserted product.
        $lastInsertId = $connection->insert_id;

        /*
         * Close the prepared statement. It also deallocates the statement handle.
         * If the statement has pending or unread results, it cancels them 
         * so that the next query can be executed.
         * 
         * @link http://php.net/manual/en/mysqli-stmt.close.php
         */
        $statement->close();

        /*
         * Save a record for each uploaded file.
         */
        foreach ($filenamesToSave as $filename) {
            $sql = 'INSERT INTO products_images (
                        product_id,
                        filename
                    ) VALUES (
                        ?, ?
                    )';

            $statement = $connection->prepare($sql);

            $statement->bind_param('is', $lastInsertId, $filename);

            $statement->execute();

            $statement->close();
        }

        /*
         * Close the previously opened database connection.
         * 
         * @link http://php.net/manual/en/mysqli.close.php
         */
        $connection->close();

        $productSaved = TRUE;

        /*
         * Reset the posted values, so that the default ones are now showed in the form.
         * See the "value" attribute of each html input.
         */
        $productName = $productQuantity = $productDescription = NULL;
    }
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
        <meta charset="UTF-8" />
        <!-- The above 3 meta tags must come first in the head -->

        <title>Save product details</title>

        <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>
        <style type="text/css">
            body {
                padding: 30px;
            }

            .form-container {
                margin-left: 80px;
            }

            .form-container .messages {
                margin-bottom: 15px;
            }

            .form-container input[type="text"],
            .form-container input[type="number"] {
                display: block;
                margin-bottom: 15px;
                width: 150px;
            }

            .form-container input[type="file"] {
                margin-bottom: 15px;
            }

            .form-container label {
                display: inline-block;
                float: left;
                width: 100px;
            }

            .form-container button {
                display: block;
                padding: 5px 10px;
                background-color: #8daf15;
                color: #fff;
                border: none;
            }

            .form-container .link-to-product-details {
                margin-top: 20px;
                display: inline-block;
            }
        </style>

    </head>
    <body>

        <div class="form-container">
            <h2>Add a product</h2>

            <div class="messages">
                <?php
                if (isset($errors)) {
                    echo implode('<br/>', $errors);
                } elseif ($productSaved) {
                    echo 'The product details were successfully saved.';
                }
                ?>
            </div>

            <form action="addProduct.php" method="post" enctype="multipart/form-data">
                <label for="name">Name</label>
                <input type="text" id="name" name="name" value="<?php echo isset($productName) ? $productName : ''; ?>">

                <label for="quantity">Quantity</label>
                <input type="number" id="quantity" name="quantity" min="0" value="<?php echo isset($productQuantity) ? $productQuantity : '0'; ?>">

                <label for="description">Description</label>
                <input type="text" id="description" name="description" value="<?php echo isset($productDescription) ? $productDescription : ''; ?>">

                <label for="file">Images</label>
                <input type="file" id="file" name="file[]" multiple>

                <button type="submit" id="submit" name="submit" class="button">
                    Submit
                </button>
            </form>

            <?php
            if ($productSaved) {
                ?>
                <a href="getProduct.php?id=<?php echo $lastInsertId; ?>" class="link-to-product-details">
                    Click me to see the saved product details in <b>getProduct.php</b> (product id: <b><?php echo $lastInsertId; ?></b>)
                </a>
                <?php
            }
            ?>
        </div>

    </body>
</html>

getProduct.php

<?php
include 'config.php';
include 'connection.php';

if (!isset($_GET['id']) || empty($_GET['id']) || !is_numeric($_GET['id'])) {
    $errors[] = 'You must select a product in order to see its details!';
} else {
    $productId = $_GET['id'];

    /*
     * Get the product details.
     */
    $sql = 'SELECT * 
            FROM products 
            WHERE id = ? 
            LIMIT 1';

    $statement = $connection->prepare($sql);

    $statement->bind_param('i', $productId);

    $statement->execute();

    /*
     * Get the result set from the prepared statement.
     * 
     * NOTA BENE:
     * Available only with mysqlnd ("MySQL Native Driver")! If this 
     * is not installed, then uncomment "extension=php_mysqli_mysqlnd.dll" in 
     * PHP config file (php.ini) and restart web server (I assume Apache) and 
     * mysql service. Or use the following functions instead:
     * mysqli_stmt::store_result + mysqli_stmt::bind_result + mysqli_stmt::fetch.
     * 
     * @link http://php.net/manual/en/mysqli-stmt.get-result.php
     * @link https://stackoverflow.com/questions/8321096/call-to-undefined-method-mysqli-stmtget-result
     */
    $result = $statement->get_result();

    /*
     * Fetch data (all at once) and save it into an array.
     * 
     * @link http://php.net/manual/en/mysqli-result.fetch-all.php
     */
    $products = $result->fetch_all(MYSQLI_ASSOC);

    /*
     * Free the memory associated with the result. You should 
     * always free your result when it is not needed anymore.
     * 
     * @link http://php.net/manual/en/mysqli-result.free.php
     */
    $result->close();

    $statement->close();

    if (!$products) {
        $errors[] = 'No product found.';
    } else {
        $product = $products[0];

        $productName = $product['name'];
        $productQuantity = $product['quantity'];
        $productDescription = $product['description'];

        /*
         * Get the images list for the provided product.
         */
        $sql = 'SELECT * 
                FROM products_images 
                WHERE product_id = ?';

        $statement = $connection->prepare($sql);

        $statement->bind_param('i', $productId);

        $statement->execute();

        $result = $statement->get_result();

        $images = $result->fetch_all(MYSQLI_ASSOC);

        $result->close();

        $statement->close();

        $connection->close();
    }
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
        <meta charset="UTF-8" />
        <!-- The above 3 meta tags must come first in the head -->

        <title>Product details</title>

        <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>
        <style type="text/css">
            body {
                padding: 30px;
            }

            .product-details tr td {
                padding: 5px;
            }

            .product-details .label {
                font-weight: 700;
            }

            .product-images {
                margin-top: 30px;
            }

            .product-images tr td {
                padding: 10px;
                font-weight: 700;
                background-color: #eee;
            }

            .product-images .label {
                color: #fff;
                font-weight: 700;
                background-color: #8daf15;
            }

            .product-images img {
                max-width: 400px;
                display: inline-block;
                float: left;
            }
        </style>
    </head>
    <body>

        <div class="page-container">
            <h2>Product details</h2>

            <?php
            if (isset($errors)) {
                echo implode('<br/>', $errors);
                exit();
            }
            ?>

            <table class="product-details">
                <tr>
                    <td class="label">Name</td>
                    <td><?php echo $productName; ?></td>
                </tr>
                <tr>
                    <td class="label">Quantity</td>
                    <td><?php echo $productQuantity; ?></td>
                </tr>
                <tr>
                    <td class="label">Description</td>
                    <td><?php echo $productDescription; ?></td>
                </tr>
            </table>

            <table class="product-images">
                <tr>
                    <td class="label">Images</td>
                </tr>
                <?php
                foreach ($images as $image) {
                    $imageId = $image['id'];
                    $imageFilename = $image['filename'];
                    ?>
                    <tr>
                        <td>
                            <img src="<?php echo $imageFilename; ?>" alt="" />
                        </td>
                    </tr>
                    <?php
                }
                ?>
            </table>
        </div>

    </body>
</html>
  • Woke up to this very well explained answer and Can't be thankful enough. But I always get this warning "Warning: Constants may only evaluate to scalar values in C:\xampp\htdocs\sav\config.php on line 11 Warning: in_array() expects parameter 2 to be array, string given in C:\xampp\htdocs\sav\addProduct.php on line 60 " and this error message when I submit the form " The extension of the file "Douglas.jpg" is not valid. Allowed extensions: JPG, JPEG, PNG, or GIF. The extension of the file "Jackson.jpg" is not valid. Allowed extensions: JPG, JPEG, PNG, or GIF.' – Labelle Doriane Feb 17 '18 at 08:51
  • @LabelleDoriane You are welcome. This happens because you have a PHP version smaller then 5.6. Prior to this version you could not define arrays as constant values. I strongly recommend you to update to PHP >= 7.0, because the version 7.0 came with a lot of important good changes. Anyway, your current problem is easy to resolve: I reedited my answer, e.g. page config.php and addProduct.php. Search for the comment `@changed_2018-02-17_14.28` in them. There I made changes. It's about 3 code lines in total: 1 in config.php and 2 in addProduct.php. –  Feb 17 '18 at 13:38
  • @LabelleDoriane P.S: If you ugrade to PHP 7, you don't need to make the changes. –  Feb 17 '18 at 13:42
  • Thank you. I will update that. I will read about PDO and Prepared statement since I was using only MySQLI Object oriented way. In order to change it so it suits the way I was coding – Labelle Doriane Feb 17 '18 at 13:43
  • @LabelleDoriane Then it's good. Because I also suggest you to change from MySQLi to PDO. PDO is similar to MySQLi in many regards, but it's more elegant and easy to use and implement. It is also only oo-style. Here is from where I suggest you to learn it: [PDO Tutorial](https://phpdelusions.net/pdo). –  Feb 17 '18 at 13:58
  • I have changed it so it can adapt Mysqli object-oriented way. I wish to know how I can show you what I did. So you can check – Labelle Doriane Feb 17 '18 at 14:46
  • @LabelleDoriane I have a proposal: I will make it mysqli way for you and reedit my answer with it. So that you can make the difference. –  Feb 17 '18 at 15:30
  • Ohh Really? Thanks that will be much appreciated if you can do it that way using objects. – Labelle Doriane Feb 17 '18 at 15:47
  • @LabelleDoriane I am finished. Please copy the content of my answer somewhere on your computer, so that I can delete the pdo solution and paste the mysqli one. We are allowed to enter only 30000 characters, and both solutions would take > 33000. Tell me when you are done. –  Feb 17 '18 at 15:57
  • Its ok I have it saved. – Labelle Doriane Feb 17 '18 at 16:00
  • Thanks its ok now :) – Labelle Doriane Feb 17 '18 at 16:05
  • @LabelleDoriane There it is. Now you can compare the two libraries. In my code they look very similar. But later, in other types of tasks, you'll find PDO much better. Have a good day and fun on learning :-) –  Feb 17 '18 at 16:05
  • @LabelleDoriane With pleasure. –  Feb 17 '18 at 16:06