Upgrading an Old PHP Application



I recently had the occasional opportunity to work with several old PHP applications. I noticed a few common anti-patterns that had to be fixed. This article is not about how to rewrite an old PHP application to <insert the name of the wonderful framework here>, but how to make it more maintainable and less hassle to work with.



Antipattern # 1: credentials in code



This is the most common of the worst patterns I've come across. In many projects, versioned code is hardcoded with important information, such as names and passwords to access the database. Obviously, this is bad practice, because it does not allow creating local environments, because the code is tied to a specific environment. In addition, anyone with access to the code can see the credentials usually appropriate for the production environment.



To fix this, I prefer a method that works for any application: install the phpdotenv package , which allows you to create an environment file and access variables using environment super variables.



Let's create two files: .env.exampleone that will be versioned and serve as a template for the file.env, which will contain the credentials. The file is .envnot versioned, so add it to .gitignore. This is well explained in the official documentation .



Your file .env.examplewill list the credentials:



DB_HOST=
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=


And the data itself will be in the file .env:



DB_HOST=localhost
DB_DATABASE=mydb
DB_USERNAME=root
DB_PASSWORD=root


In a regular file, load .env:



$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();


Can then apply to the accounting data using, say $_ENV['DB_HOST'].



It is not recommended to use the package in operation "as is", for this it is better:



  • Inject environment variables into the runtime of your container if you have a Docker-based deployment, or into the server-side HTTP configuration if possible.
  • Cache environment variables to avoid the overhead of reading .env on every request. This is how Laravel does it .


Credential files can be removed from the Git history .



Antipattern # 2: don't use Composer



It used to be very popular to have a lib folder with large libraries like PHPMailer. This should be avoided in every possible way when it comes to versioning, so these dependencies should be managed with Composer . Then it will be very easy for you to see which version of the package is in use and update it if necessary.



So install Composer and use it to manage.



Antipattern # 3: no local environment



Most of the applications I worked with only had one environment: production.





But by getting rid of anti-pattern # 1, you can easily customize your local environment. You may have had some of your configuration hardcoded, such as boot paths, but now you can move that to .env.



I use Docker to create local environments. It works especially well for older projects because they often use older versions of PHP that you don't want or can't install.



You can use a service like PHPDocker , or use a small file docker-compose.yml.



Antipattern # 4: don't use the Public folder



It turned out that most of these old projects are accessible from their root folders. That is, any file in the root will be available for public reading. This is especially bad when attackers (such as a kiddie script) try to access the included files directly, because you might not be able to determine the output if the script accesses all your included files directly.



Obviously this situation is incompatible with using .envor Composer, because opening the vendor folder is a bad idea . Yes, there are some tricks to doing this; but if possible, move all PHP files open to clients into a folder Publicand change the server configuration so that this folder becomes the root folder for your application.



I usually do this:



  • Create a folder dockerfor Docker related files (Nginx config, PHP Dockerfile, etc.).
  • I create a folder appin which I store business logic (services, classes, etc.).
  • I create a folder publicin which I store PHP scripts and resources (JS / CSS) open to clients. This is the root folder of the application from the clients' point of view.
  • I create files .envand .env.example.


Antipattern # 5: egregious security problems



PHP applications, especially older ones that don't use frameworks, often suffer from egregious security issues:



  • Due to the lack of escaping of parameters in the query, there is a danger of SQL injection. To prevent them, use PDO!
  • There is a danger of XSS injection due to the display of non-escaped user data. Use htmlspecialchars to prevent them.
  • . , , , .
  • - CSRF-. Anti-CSRF, .
  • Poor password encryption. I've seen many projects still use SHA-1 and even MD5 for password hashing. PHP 5.5 out of the box has good support for BCrypt, it's a shame not to use it. To comfortably transfer passwords, I prefer to update the hashes in the database as users log in. The main thing is to make sure that the column passwordis long enough to accommodate BCrypt passwords, VARCHAR (255) is fine. Here is pseudocode to make it clearer:



    <?php
    //    :    $
    //  :     
    if (strpos($oldPasswordHash, '$') !== 0 &&
        hash_equals($oldPasswordHash, sha1($clearPasswordInput))) {
        $newPasswordHash = password_hash($clearPasswordInput, PASSWORD_DEFAULT);
    
        //   password
    
        //  :    
    }
    
    //   
    if (password_verify($clearPasswordInput, $currentPasswordHash)) {
        //  :    
    }
    
    //   :    
    


Antipattern # 6: no tests



This is very common in older applications. It is hardly possible to start writing unit tests for the entire application, so you can write functional tests.





These are high-level tests to help you ensure that subsequent refactoring of your application doesn't break it. Tests can be simple, for example, we launch the browser and enter the application, then wait for the HTTP code on the success of the operation and / or the corresponding message on the final page. For tests, you can use PHPUnit, or Cypress , or codeception .



Antipattern # 7: Poor Error Handling



If (or most likely when) something breaks, you need to find out quickly. But many older applications don't handle errors well, relying on PHP's leniency.



You need to be able to catch and log as many errors as possible in order to fix them. There are good articles on this topic .



Also, it will be easier for you to find places where errors occur if the system throws specific exceptions.



Antipattern # 8: global variables



I thought I would never see them again until I started working with old projects. Global variables make the reading and understanding of the behavior of the code unpredictable. In short, it is evil .



It's better to use dependency injection instead , because it allows you to control which instances are used and where. For example, the Pimple package has performed well .



What else to improve?



Depending on the fate of the application or budget, there are several more steps you can take to improve your project.



First, if the application is running on an older version of PHP (below 7), try to update it. Most often, this does not cause big problems, and most of all it will take most of the time to get rid of the calls mysql_ calls, if any. To quickly fix this, you can use a similar library , but it is better to rewrite all requests for PDO so that all parameters are escaped at the same time.



If the application does not use the MVC pattern, that is, business logic and templates are separated, then it's time to add a template library (I know that PHP is a templating language, but modern libraries are much more convenient), for example, Smarty, Twig or Blade.



Finally, in the long run, it is best to rewrite your application in a modern PHP framework like Laravel or Symfony. You will have all the tools you need for safe and smart PHP development. If the application is large, then I recommend using the strangler pattern to avoid the big bang rewriting , which may (and probably will) end badly. Therefore, you can migrate to the new system those parts of the code that you are currently working on, keeping the old working parts intact until it comes up to them.



This is an effective approach that allows you to create a modern PHP environment for your day-to-day work without freezing features for weeks or months depending on the project.



All Articles