I would argue its impossible to handle all exceptions the same way. I think you should let it propagate back to the calling code as it ultimately knows how it should handle the *exceptional* situation.

Just to think of a (maybe weird) example. Imagine you have an blog. An each hour you query the database for new articles. But luck isn't with you and your database is temporarily unavailable. Now you could throw an exception and the whole site is down (handle the exception inside the database abstraction layer) or you could log the error, use the cached query results, extend the lifetime of the cache another hour. This way your users would to next to no disruption in your site and may never even notice you had problems before you have fixed them.

So you can, however, implement some general procedures if a query fails, like error logging. This would be preferable as this can be considered boilerplate code that doesn't change much. And having database errors emailed to you (as an example) lets you resolve the issue faster than if you were to discover the issue yourself.

You also get an additional bonus by grouping logging code in one place. You can handle specific logging alongside relevant code and if your generic logging procedure should change you only need to change it once.

Using your `fetchAll()` method the structure could look like:

    public function fetchAll($sql, $bind = array()) {
    
        // Do something awesome before try-catch block.

        try {

            // Execute query...

        }catch(PDOException $exception) {

            $this->logger->log('SQL query failed: ' . $exception->getMessage());

            /*
             * Re-throw the exception to let the calling code
             * handle the situation.
             */
            throw $exception

        }

        // Continue as everything executed as expected.

    } 

Happy coding!