# The PrestaShopBackup Class. How to use it in your solutions?

PrestaShop is a robust e-commerce platform designed not only for merchants but also — and perhaps above all — for developers who want to build custom solutions. It offers plenty of built-in features that can be extended and adapted to specific needs, minimizing the necessity of developing functions from scratch. One of the less-known but useful tools is the `PrestaShopBackup` class, which simplifies the implementation of backup mechanisms.

## classes/PrestaShopBackup.php

The `PrestaShopBackup` class exists to make it easier to create database backups. It contains all the necessary logic for gathering database tables, generating a backup file name, and saving the backup data into a file. By default, it is used in the **Advanced → Database → DB Backup** section, but to illustrate a practical use of the `PrestaShopBackup` class in your own solution, let’s consider a scenario where you need a backup mechanism for specific tables related to product and category languages.  
We’ll assume we need these tables because we created a mechanism that generates, for example, SEO-friendly URLs and other product SEO data based on given criteria, but we want to ensure the user always has a backup.

## Don’t reinvent the wheel

Instead of building this from scratch, you can extend the `PrestaShopBackup` class by creating a new class, for example `ProductLangTablesBackup`.  
The method responsible for creating and saving a new backup is `PrestaShopBackupCore::add()`. By default, it creates a backup of all the data in your store (except for some tables, such as statistics, if backups were configured that way). Obviously, we are not interested in a full backup, so in our code we need to add a condition that simply skips all the tables we don’t care about.

## Example

Below you’ll find code I used in one of my projects. It’s based on the class from PrestaShop 1.7.8. If you want to follow this article and build something for your own solution, **make sure to use the [PrestaShop 8.2 codebase](https://github.com/PrestaShop/PrestaShop/blob/8.2.x/classes/PrestaShopBackup.php)**.  
In the code, I’ve highlighted the lines that were modified compared to the original file. We changed:

- the file name  
- reversed the backup condition — instead of ignoring selected tables, we allow only selected tables  

The most important changes are highlighted in the code below.

```php
<?php

class ProductLangTablesBackup extends PrestaShopBackupCore
{
    /**
     * Creates a new backup file.
     *
     * @return bool true on successful backup
     */
    public function add()
    {
        $tablesToBackup = [_DB_PREFIX_ . 'product_lang'];
        // Generate some random number, to make it extra hard to guess backup file names
        $rand = dechex(mt_rand(0, min(0xffffffff, mt_getrandmax())));
        $date = time();
        $backupfile = $this->getRealBackupPath() . $date . '-' . $rand . '-ProductLanguageData.sql';

        // Figure out what compression is available and open the file
        if (function_exists('bzopen')) {
            $backupfile .= '.bz2';
            $fp = @bzopen($backupfile, 'w');
        } elseif (function_exists('gzopen')) {
            $backupfile .= '.gz';
            $fp = @gzopen($backupfile, 'w');
        } else {
            $fp = @fopen($backupfile, 'wb');
        }

        if ($fp === false) {
            echo Context::getContext()->getTranslator()->trans('Unable to create backup file', array(), 'Admin.Advparameters.Notification') . ' "' . addslashes($backupfile) . '"';

            return false;
        }

        $this->id = realpath($backupfile);

        fwrite($fp, '/* Backup for ' . Tools::getHttpHost(false, false) . __PS_BASE_URI__ . "\n *  at " . date($date) . "\n */\n");
        fwrite($fp, "\n" . 'SET NAMES \'utf8\';');
        fwrite($fp, "\n" . 'SET FOREIGN_KEY_CHECKS = 0;');
        fwrite($fp, "\n" . 'SET SESSION sql_mode = \'\';' . "\n\n");

        // Find all tables
        $tables = Db::getInstance()->executeS('SHOW TABLES');
        $found = 0;
        foreach ($tables as $table) {
            $table = current($table);

            // Skip tables which do not start with _DB_PREFIX_
            if (strlen($table) < strlen(_DB_PREFIX_) || strncmp($table, _DB_PREFIX_, strlen(_DB_PREFIX_)) != 0) {
                continue;
            }

            if (!in_array($table, $tablesToBackup)) {
                continue;
            }

            // Export the table schema
            $schema = Db::getInstance()->executeS('SHOW CREATE TABLE `' . $table . '`');

            if (count($schema) != 1 || !isset($schema[0]['Table']) || !isset($schema[0]['Create Table'])) {
                fclose($fp);
                $this->delete();
                echo Context::getContext()->getTranslator()->trans('An error occurred while backing up. Unable to obtain the schema of %s', array($table), 'Admin.Advparameters.Notification');

                return false;
            }

            fwrite($fp, '/* Scheme for table ' . $schema[0]['Table'] . " */\n");

            if ($this->psBackupDropTable) {
                fwrite($fp, 'DROP TABLE IF EXISTS `' . $schema[0]['Table'] . '`;' . "\n");
            }

            fwrite($fp, $schema[0]['Create Table'] . ";\n\n");

            $data = Db::getInstance()->query('SELECT * FROM `' . $schema[0]['Table'] . '`', false);
            $sizeof = Db::getInstance()->numRows();
            $lines = explode("\n", $schema[0]['Create Table']);

            if ($data && $sizeof > 0) {
                // Export the table data
                fwrite($fp, 'INSERT INTO `' . $schema[0]['Table'] . "` VALUES\n");
                $i = 1;
                while ($row = Db::getInstance()->nextRow($data)) {
                    $s = '(';

                    foreach ($row as $field => $value) {
                        $tmp = "'" . pSQL($value, true) . "',";
                        if ($tmp != "'',") {
                            $s .= $tmp;
                        } else {
                            foreach ($lines as $line) {
                                if (strpos($line, '`' . $field . '`') !== false) {
                                    if (preg_match('/(.*NOT NULL.*)/Ui', $line)) {
                                        $s .= "'',";
                                    } else {
                                        $s .= 'NULL,';
                                    }

                                    break;
                                }
                            }
                        }
                    }
                    $s = rtrim($s, ',');

                    if ($i % 200 == 0 && $i < $sizeof) {
                        $s .= ");\nINSERT INTO `" . $schema[0]['Table'] . "` VALUES\n";
                    } elseif ($i < $sizeof) {
                        $s .= "),\n";
                    } else {
                        $s .= ");\n";
                    }

                    fwrite($fp, $s);
                    ++$i;
                }
            }
            ++$found;
        }

        fclose($fp);
        if ($found == 0) {
            $this->delete();
            echo Context::getContext()->getTranslator()->trans('No valid tables were found to backup.', array(), 'Admin.Advparameters.Notification');

            return false;
        }

        return true;
    }
}
```

Executing a backup of only the tables we care about is as simple as running:

```php
// The following code will create a new backup based on your file
$backup = new ProductLangTablesBackup();
$backup->add();
```

This is, of course, a very simple example of how you can make your work easier. If you need something more advanced, there are [ready-made libraries](https://github.com/ramazancetinkaya/mysql-backup) you can use in your modules.
And finally, I’d like to recommend that module developers consider including such solutions in their products. More than once, it can help your users — and as you can see, the implementation is not complicated. 😉

