Detecting Bugs in PHP Code with PHPStan: A Static Analysis Tool

Learn how to detect bugs early in your PHP codebase using PHPStan, a powerful static analyzer. Improve code quality with practical tips and examples.

Writing clean, bug-free code is a goal for every developer — but manual testing and reviews aren’t always enough. That’s where static analysis tools like PHPStan come in. PHPStan helps you catch bugs in your PHP code without executing it. By analyzing your source files, it detects potential issues before they ever become runtime errors.

In this article, we’ll explore what PHPStan is, how it works, and how you can integrate it into your PHP projects to improve code quality.

What is PHPStan?

PHPStan is a static analysis tool specifically designed for PHP. It scans your codebase and finds problems such as:

  • Type mismatches
  • Undefined variables or methods
  • Dead or unreachable code
  • Incorrect method calls

Unlike traditional debuggers, PHPStan doesn’t execute your code — it reads and analyzes your PHP files directly, looking for structural or logical inconsistencies based on static rules.

Why Use PHPStan?

Here are a few reasons to integrate PHPStan into your development workflow:

  • Early bug detection: Spot bugs before running the code.
  • Improved code quality: Enforce strict typing and coding standards.
  • Reduced runtime errors: Catch potential issues during development.
  • Better refactoring: Safely restructure code with confidence.

PHPStan becomes especially powerful when used alongside modern PHP features like type hints, union types, and generics.

Getting Started with PHPStan

Installing PHPStan is very easy. You can install it in your project through composer:

$ composer require --dev phpstan/phpstan

This installs PHPStan as a development dependency.

Running PHPStan

We can now run PHPStan from the base directory of our project. To analyze your code, use the following command:

$ vendor/bin/phpstan analyze src

Replace src with the directory containing your PHP files.

A breakdown of this command:

  • vendor/bin/phpstan is the executable
  • analyze tells PHPStan to analyze all files in the given directories
  • src is the directory we want to analyse

Try running this in your own project and see what kind of potential errors are living in your codebase.

By default, PHPStan analyzes the code with a conservative level of strictness. However, you can make it more strict using levels from 0 (lenient) to 8 (strictest):

vendor/bin/phpstan analyse src --level=5

Configuration with phpstan.neon

For more control, you can create a phpstan.neon configuration file in your project root:

parameters:
    level: 5
    paths:
        - src
        - app
    ignoreErrors:
        - '#Call to an undefined method.*#'

This allows you to customize which folders to analyze, define rules to ignore, and adjust the strictness level.

Common Issues Caught by PHPStan

PHPStan can detect a wide range of issues:

  • Calling methods or accessing properties that don’t exist.
  • Passing the wrong type of arguments to a function or method.
  • Returning values of the wrong type.
  • Forgetting to return a value in non-void functions.
  • Using undefined variables.

These issues often lead to hard-to-find bugs in production. Catching them early saves time and effort.

Integrating PHPStan into CI

To make PHPStan even more effective, integrate it into your CI/CD pipeline. This ensures code is analyzed automatically on every push or pull request, helping maintain a high standard across your team.

For most of my personal projects, I use TravisCI. Since we’ve included PHPStan as a dev-dependency in our composer.json file we just have to add the PHPStan executable to the scripts that the CI-software needs to run.

For TravisCI, this means just changing the default script in a .travis.yml like this:

language: php
php:
  - '8.0'
install: composer install

# Simply add these lines
script:
    - vendor/bin/phpunit
    - vendor/bin/phpstan analyse src tests --level=5

The default script that TravisCI runs for PHP projects is phpunit. Now we’ve added PHPStan to it. If PHPStan finds any errors within your project, the build will fail.

Similarly, in GitHub Actions:

- name: Run PHPStan
  run: vendor/bin/phpstan analyse --level=5

Conclusion

PHPStan is a must-have tool for modern PHP development. It helps you catch bugs early, write cleaner code, and reduce technical debt over time. Whether you’re maintaining a legacy project or starting a new one, integrating PHPStan into your workflow is a smart move.

Advanced Error Handling in PHP (Part 2)

Explore advanced techniques for error handling in PHP including exceptions, custom error handlers, and best practices in Part 2 of this series.

In this second part of our series, we’ll dive deeper into error handling in PHP. We’ll cover how to read, process, and present your error logs so you can monitor and debug more effectively. If you haven’t gone through Part 1 (logging errors), it’s a good idea to start there first.

Reading Error Logs Programmatically

Once you have error logs being generated, the next step is to read them in a usable way. The goal is to convert the log file into a structured format, so you can display recent errors first, filter entries, etc to make error handling easy.

Here is a sample PHP method that reads an error log file:

public function errorLogs($filePath = 'error.log') {

        $fileContent = file($filePath);

        $errorsArray = array();
        if(sizeof($fileContent) == 0) {
            return false;
        }

        foreach($fileContent as $row) {
            $errors = explode(":  ", $row);

            if(empty($errors[1])) continue;
            $errorsArray[] = $errors;
        }

        return array_reverse($errorsArray, true);
}

Explanation:

$fileContent = file($filePath);

This line of code will read the file line by line from the provided file path.

if(sizeof($fileContent) == 0) {
    return false;
}

Checks whether the file is empty; if yes, returns false to indicate there’s nothing to process.

foreach($fileContent as $row) {
      $errors = explode(":  ", $row);

      if(empty($errors[1])) continue;
      $errorsArray[] = $errors;
}

This part of the function will loop through the log contents row by row.

explode(": ", $row)

Splits each line at the pattern ": " — usually separating a timestamp from the error message.

if (empty($errors[1])) continue;

Skips any lines that don’t have an error message portion after splitting.

$errorsArray[] = $errors;

Adds the parsed pieces to an array.

return array_reverse($errorsArray, true);

Reverses the order, so the newest log entries appear first in whatever display you build.

Why You’d Do This

Prioritize recent errors: By reversing the array, newer errors show up first so you don’t have to scroll through older logs.

Filter out noise: Skipping lines with missing data helps prevent malformed entries from causing trouble.

Make things display-friendly: Once you have structured data (e.g. timestamp + message), you can feed this into UI tables or dashboards.

Beginner’s Guide to Error Handling in PHP – Part 1

Learn the basics of error handling in PHP. Understand production safety and developer insights with logging errors using error_reporting.

Effective error handling in PHP is essential for developers—it helps identify issues while keeping your production environment secure and user-friendly.

Balancing Error Display vs. Security

On a production server, showing PHP errors directly on the screen poses security risks, as they may reveal sensitive file paths or internal logic. Hence, many developers suppress error display using:

ini_set('error_reporting', 0);
error_reporting(0);

ini_set('display_errors', FALSE);

However, completely hiding errors without logging them makes debugging nearly impossible.

Capturing and Logging Errors Safely

A more practical approach is to keep errors hidden from users but log them for developers to review, like below:

ini_set('error_reporting', E_ALL);
error_reporting(E_ALL);

ini_set('log_errors', TRUE);
ini_set('html_errors', FALSE);
ini_set('error_log', LOG_PATH.'error.log');

ini_set('display_errors', FALSE);

Explanation:

  • Enable reporting for all errors (E_ALL), ensuring nothing is missed.
  • Direct errors to logs, turning off HTML formatting (html_errors = FALSE) for cleaner log formatting.
  • Specify a custom log file path (via LOG_PATH . 'error.log'), enabling modular logging for different parts of your application.
  • Disable display of errors to protect end users from seeing raw error output.

Benefits of This Approach

  • Production safety: Users can’t see system internals or error details.
  • Developer insight: All error information will be logged in centralized log files.
  • Modular logging: You can segregate logs per module for quicker diagnostics.

Would you like to learn advanced error handling techniques in PHP like exceptions, custom handlers, or reading log files? Just let me know—happy to continue!

Get Last Executed Query in CodeIgniter PHP

Learn how to retrieve the last executed query in CodeIgniter’s built-in query methods developed in PHP. Helpful for debugging and query optimization.

When developing applications with CodeIgniter, retrieving the last executed SQL query becomes the most useful features for debugging and performance tuning. Whether you’re trying to diagnose a bug, optimize performance, or log queries for later review, CodeIgniter makes it easy to access the most recent database query.

In this article, we’ll explore how to get the last executed query in both CodeIgniter 3 and CodeIgniter 4, with examples.

Why Retrieve the Last Executed Query?

Here are a few scenarios where getting the last executed query is helpful:

  • Debugging incorrect or unexpected results.
  • Profiling SQL performance issues.
  • Logging queries for auditing purposes.
  • Building custom query logs for admin or developer panels.

CodeIgniter 3: Getting the Last Query

CodeIgniter 3 provides a simple method from the database class:

$this->db->last_query();

For Example:

public function getUser($id)
{
    $query = $this->db->get_where('users', ['id' => $id]);
    echo $this->db->last_query(); // Outputs the SQL query
    return $query->row();
}

Output:

SELECT * FROM `users` WHERE `id` = '1'

You can also store it in a variable to use it for logging:

$last_query = $this->db->last_query();
log_message('debug', 'Last Query: ' . $last_query);

CodeIgniter 4: Getting the Last Query

In CodeIgniter 4, the approach is slightly different. You can use the getLastQuery() method from the Query Builder object.

Example:

$db = \Config\Database::connect();
$builder = $db->table('users');

$query = $builder->where('id', 1)->get();
echo $db->getLastQuery(); // Outputs the last SQL query

Output:

SELECT * FROM `users` WHERE `id` = 1

getLastQuery() returns a CodeIgniter\Database\Query object, so you can also format it if needed:

echo $db->getLastQuery()->getQuery(); // returns query string

Pro Tips

  • Use this feature only in development mode or behind admin-only views.
  • Avoid exposing raw SQL queries in production environments for security reasons.
  • Combine it with CodeIgniter\Debug\Toolbar for enhanced SQL visibility in CI4.

Logging All Queries in CodeIgniter

You can also log all database queries automatically:

CodeIgniter 3:

In application/config/database.php, set:

$db['default']['save_queries'] = TRUE;

Then access them:

print_r($this->db->queries); // array of all executed queries

CodeIgniter 4:

Use the Debug Toolbar, or manually:

$db = \Config\Database::connect();
$queries = $db->getQueries(); //returns an array of all queries

Conclusion

Accessing the last executed SQL query is a powerful feature that can significantly speed up debugging and development. Whether you’re using CodeIgniter 3 or 4, the framework provides convenient tools to track your database interactions.

Make sure to leverage this feature wisely, especially when you’re optimizing queries or tracking down elusive bugs.

Do you use query logging in your CodeIgniter project? Share your tips or challenges in the comments below!

What Are ORM Frameworks? A Beginner’s Guide

Understand what ORM (Object-Relational Mapping) frameworks are, how they work, and why they are essential in modern web development.

ORM is a short form of Object Relational Mapping. ORM framework is written specifically in OOP (object-oriented programming) language (like PHP, C#, Java, etc…). It is like a wrapper around a relational database (like MySQL, PostgreSQL, Oracle, etc…). So, ORM is basically mapping objects to relational tables.

What does an ORM framework do?

The ORM framework generates objects (as in OOP) that virtually map the tables in a database. So, any programmer could use these objects to interact with the database without writing an optimized SQL code.

For example:

We have 2 tables in a database:

  • Products
  • Orders

The ORM framework would create 2 objects corresponding to the above tables (like products_object and orders_object) with little configuration, which will handle all the database interactions. So, if you want to add a new product to the products table, you would have to use the products_object and save() method like below,

product = new products_object("Refrigerator","Electronics");
product.save();

You can see, how much easier an ORM framework can make things. No need to write any SQL syntax. And the application code would be very clean.

Some other advantages of using ORM frameworks

1. Syncing between OOP language and the relational database data types is always creating a problem. Sometimes variable data types have to be converted properly to insert into the database. A good ORM framework will take care of these conversions.

2. Using an ORM will create a consistent code base for your application, because it is not using any SQL statements in the code. This makes it easier to write and debug any application, especially if more programmers are using same code base.

3. ORM frameworks will shield your application from SQL injection attacks since the framework will be filtering the data before any operation in the database.

4. Database Abstraction; Switching databases for the application is easier as, ORM will take care of writing all the SQL code, data type conversions etc …

When to use an ORM framework?

An ORM framework becomes more useful as the size and complexity of the project increases. An ORM framework may be overkilling an application on a simple database with 5 tables and 5-6 queries to be used for the application.

Consider the use of ORM when:

  • 3 or more programmers are working on an application.
  • Application database consists of 10+ tables.
  • The application is using 10+ queries.

About 80-90% of application queries can be handled by the ORM generated objects. It is inevitable that at some point straight SQL query is required, which can’t be handled by ORM generated objects.

In fact, ORM frameworks often have their own *QL query language that looks a lot like SQL. Doctrine, a popular PHP based ORM framework has DQL (Doctrine Query Language) and the very popular Hibernate (used in the Java and .Net world) has HQL. Going even further, Hibernate allows writing straight SQL if need be.

ORM Frameworks for PHP programmers

  • CakePHP, ORM, and framework for PHP 5
  • CodeIgniter, a framework that includes an ActiveRecord implementation
  • Doctrine, open source ORM for PHP 5.3.X
  • FuelPHP, ORM, and framework for PHP 5.3. Based on the ActiveRecord pattern.
  • Laravel, a framework that contains an ORM called “Eloquent” an ActiveRecord implementation.
  • Maghead, a database framework designed for PHP7 includes ORM, Sharding, DBAL, SQL Builder tools etc.
  • Propel, ORM and query-toolkit for PHP 5, inspired by Apache Torque
  • Qcodo, ORM, and framework for PHP 5
  • QCubed, A community-driven fork of Qcodo
  • Redbean, ORM layer for PHP 5, creates and maintains tables on the fly
  • Yii, ORM, and framework for PHP 5. Based on the ActiveRecord pattern.
  • Zend Framework, a framework that includes a table data gateway and row data gateway implementations. ZendDb