If you’ve ever done OOP in PHP you are fully aware of the __constructmethod and the role it plays in setting up class properties, among other tasks. But have you ever given any thought on using the opposite of this function: the __destruct method? Were you aware that such a function existed? Keep reading to find about some use cases I’ve personally implemented in the past.


Save Errors to File

Over the years, I’ve had to create various import scripts. For example, grabbing data from a CSV file and landing it onto a database table. Sometimes what would happen is that my script would stop running because it ran into a row with too many values or a value which couldn’t be inserted properly. These things would hinder my progress because I would then go through a process of elimination to see what the troublesome values were and writing conditions to handle them. It was later that I realized I could come up with a solution whereby the script would continue running and be allowed to finish, while catching and logging any issues that arose along the way. The __destruct method was pivotal in achieving this, see the gist below:

<?php

	namespace App\Classes;

	use Exception;
	use PDO;
	use PDOException;

	abstract class ParentImportScript
	{
	    /**
	     * @var PDO $pdo
	     */
	    protected $pdo;

	    /**
	     * @var array $errorBag
	     */
	    protected $errorBag;

	    /**
	     * @var string $errorFileLocation
	     */
	    protected $errorFileLocation = '/var/www/html/storage/';

	    /**
	     * @var string $startTime
	     */
	    protected $startTime;

	    /**
	     * ChildImportScript constructor.
	     * @param $dsn
	     * @param $username
	     * @param $password
	     * @param $options
	     */
	    public function __construct($dsn, $username, $password, $options = [])
	    {
	        $this->pdo = new PDO($dsn, $username, $password, $options);
	        /*
	         * This tells PDO to emit a PDOException should there be
	         * any issues with the query statement it executes.
	         */
	        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

	        /*
	         * Making sure to initialize the variable
	         * which holds all the errors.
	         */
	        $this->errorBag = [];

	        $this->recordStartTime();

	        /*
	         * The responsibility of actually running the import is handled
	         * by the parent. The child just specifies what data should
	         * be imported. The possibility to alter in what specific
	         * manner the import should be performed is present
	         * however.
	         */

	        $this->handleImport($this->getImportData());

	        /*
	         * Lazily simulating the import taking some arbitrary time.
	         */
	        sleep(10);
	    }

	    /**
	     * @return string
	     */
	    abstract protected function getTable(): string;

	    /**
	     * @return array
	     */
	    abstract protected function getImportData();

	    /**
	     * @param array $data
	     * @return void
	     */
	    protected function handleImport(array $data)
	    {
	        foreach ($data as $datum) {
	            $columnNames = array_keys($datum);

	            $preparedColumnNames = implode(',', $columnNames);

	            $preparedValuePlaceholders = implode(',', array_map(function ($column) {
	                return ":$column";
	            }, $columnNames));

	            $sql = "INSERT INTO {$this->getTable()} ($preparedColumnNames) VALUES ($preparedValuePlaceholders)";

	            try {
	                $this->pdo->prepare($sql)->execute($datum);
	            } catch (PDOException $e) {
	                $this->addExceptionToErrorBag($e);
	            }
	        }
	    }

	    /**
	     * @param Exception $e
	     */
	    protected function addExceptionToErrorBag(Exception $e)
	    {
	        /*
	         * The actual data that is recorded can be
	         * changed to fit your needs. Either
	         * here or in the child class.
	         */
	        $this->errorBag[] = [
	            'Exception' => get_class($e),
	            'Message' => "{$e->getMessage()} {$e->getLine()}",
	            'Trace' => $e->getTraceAsString(),
	        ];
	    }

	    private function getRunningScriptName()
	    {
	        $classNamespacePath = explode('\\', get_class($this));

	        return array_pop($classNamespacePath);
	    }

	    private function recordStartTime()
	    {
	        $this->startTime = date('Y-m-d H:i:s');

	        $sql = "INSERT INTO scripts_table (script_name, started_at) 
	                VALUES ('{$this->getRunningScriptName()}', '{$this->startTime}')";

	        $this->pdo->prepare($sql)->execute();
	    }

	    public function __destruct()
	    {
	        if (count($this->errorBag) > 0) {
	            $className = $this->getRunningScriptName();
	            $timestamp = date('Ymd_His');

	            $filename = "{$className}_errors_$timestamp.json";

	            file_put_contents("$this->errorFileLocation/$filename", json_encode($this->errorBag));
	        }

	        try {
	            $sql = "UPDATE scripts_table SET completed_at = CURRENT_TIMESTAMP 
	                    WHERE script_name = '{$this->getRunningScriptName()}'
	                    AND started_at = '{$this->startTime}';";

	            $this->pdo->prepare($sql)->execute();
	        } catch (PDOException $e) {
	            $this->addExceptionToErrorBag($e);
	        }
	    }
	}

#php #oop #laravel #symfony #php-developers

Use Cases for The __destruct Method in Vanilla PHP
1.45 GEEK