PHP Development: Modern, Typed and Secure Web Applications

How we build and rescue PHP applications today: strict types, disciplined Composer use, static analysis, performance work led by profiling, and security by default.

Rough blocks pass a set square, a lens and a padlock box and leave as neat, verified stacks, like a modern PHP pipeline.

Most teams that come to us for PHP development want one of two things: a new web application that will stay easy to change for years, or help with an existing one that has become slow, fragile or hard to staff. Both come down to the same thing, which is whether the codebase is written like modern PHP or like the PHP that gave the language its old reputation.

Modern means typed code that a static analyzer can reason about, and dependencies pinned and audited through Composer. It means tests that run on every push, response times you’ve measured instead of guessed, and security that the framework and the pipeline enforce, so nobody has to remember it. The language has changed a great deal, and the rest of this article is about how we work with it now.

What we build with PHP

PHP is at its best in request-and-response web software backed by a relational database, and that describes a large share of real business applications. Typical projects include:

  • Customer portals and self-service dashboards with role-based access
  • Back-office systems for operations teams, covering bookings, orders, approvals and inventory
  • JSON APIs for mobile apps and single-page front ends
  • Integrations that connect a product to payment providers, ERPs, CRMs and accounting tools
  • Custom modules for existing platforms, e-commerce stores among them
  • Rescue and modernization of older PHP and MySQL applications that still run the business

New products get a framework. Laravel is our default when delivery speed and a broad first-party toolkit matter, and Symfony suits teams that prefer explicit configuration and reusable components. With existing systems we work with the code as we find it, including long-lived CodeIgniter applications and code written without any framework.

How modern PHP development looks in practice

Current PHP is a typed language if you let it be. We enable strict types in every file and use the features that make intent explicit: typed properties, union and nullable types, enums instead of magic strings, readonly properties for value objects, constructor promotion, and match expressions in place of long switch chains. This isn’t about style. With the types in place, tools can catch mistakes before your users run into them.

Composer hygiene

  • The lock file is committed, so every environment installs exactly the same versions.
  • Version constraints follow semantic versioning, and upgrades happen in small, reviewed steps.
  • composer audit runs in CI and fails the build on known vulnerabilities.
  • Production installs skip development dependencies and use an optimized autoloader.
  • We replace abandoned packages before they turn into a security problem.

Static analysis and automated refactoring

PHPStan or Psalm runs on every pull request. On an existing codebase, we start with a baseline that records today’s errors, block new ones right away, and raise the strictness level over time. Rector takes care of the mechanical side of upgrades, like adopting newer syntax or replacing deprecated calls, which leaves reviewers free to look at behavior. A coding standard enforced by PHP-CS-Fixer or PHP_CodeSniffer settles style debates before they reach review.

Architecture and stack choices

Most products are best started as a well-structured monolith: one deployable application with clear internal modules, a relational database and a queue for slow work. We split out services once a boundary has proven itself and a team or scaling need justifies the extra operational work. Not before.

  • Database. MySQL or MariaDB when your team and hosting already know them, PostgreSQL when you need richer data types, stricter constraints or more advanced queries. Either one performs well with good indexes and migrations kept in version control.
  • Caching and queues. Redis covers cache, sessions, locks and queues for most applications. Email, PDF generation, imports and webhook calls move to background jobs.
  • Runtime. Nginx with PHP-FPM is the predictable default. Application servers such as FrankenPHP in worker mode, RoadRunner or Swoole keep the app booted between requests and can cut latency, but any state shared across requests becomes your problem. We adopt them when profiling shows that bootstrap cost matters.
  • Front end. Server-rendered templates for admin-heavy apps, or an API with a JavaScript front end when the interface is highly interactive or shared with mobile.

Performance work that starts with a profiler

Slow PHP applications are rarely slow because of PHP. The time goes into what the code asks the database and the network to do. We measure first, using Blackfire, Tideways, SPX or the Xdebug profiler locally and an APM in production, and then fix whatever the data points to. The usual findings:

A flame graph with one wide amber hot spot under a magnifying lens, beside a row of identical repeated database calls.
  • N+1 queries hidden inside ORM relationships and templates
  • Missing indexes, and queries that load whole tables just to count rows
  • Third-party API calls made synchronously inside a page request
  • File-based sessions that lock, so parallel requests from one user wait in line
  • No cache for data that rarely changes but gets read constantly

Server configuration matters too. OPcache should be on with enough memory, and in production it can skip file timestamp checks as long as each deploy reloads PHP-FPM. Size the number of FPM workers to the available memory instead of leaving the defaults. The JIT compiler helps CPU-heavy code, but a typical web request spends its time waiting on I/O, so that’s rarely where the gains are.

Security that does not depend on memory

The vulnerabilities that hurt PHP applications are rarely exotic. They’re old, well-understood mistakes, and the fix is to make the safe path the default one:

A request path through a maze where every wrong turn is walled off, passing a sieve, a sealed envelope and a lock.
  • Prepared statements or a query builder for every database call, never SQL built from strings
  • Context-aware output escaping, ideally through a template engine that escapes by default
  • CSRF tokens on every state-changing form, and authorization checked per record as well as per page
  • Passwords stored with password_hash(), with legacy MD5 or SHA-1 hashes upgraded at each user’s next login
  • Tokens generated with random_bytes(), never rand() or uniqid()
  • Session IDs regenerated at login, and cookies marked Secure, HttpOnly and SameSite
  • No unserialize() on anything a user can touch; use JSON instead
  • Uploads validated by content, stored outside the web root and served through the application
  • Errors logged and never displayed, and secrets kept in the environment rather than the repository

Running a PHP version that still receives security fixes matters just as much. Every release line has a published support window. Once it closes, no amount of careful application code will patch the runtime underneath.

When PHP is the right choice, and when it is not

PHP is a strong fit when your product is a web application or API with ordinary request patterns, when you want mature libraries and frameworks plus a large hiring pool, and when hosting flexibility matters. Its shared-nothing model, where every request starts clean, is also forgiving. A leak or a corrupted state rarely outlives a single request.

For a few workloads it’s the wrong default. Systems built around many long-lived connections, such as live collaboration or high-volume real-time feeds, sit more naturally in Node.js, Go or Elixir. Data science and machine learning belong in Python. And if your team already works in C# or TypeScript, moving to PHP rarely pays for itself. We raise that during discovery, before anything gets built.

How we work, from first audit to launch

Every engagement starts with discovery. On a new product, our business analysts turn goals into user flows and acceptance criteria. On an existing application, we audit the code, dependencies and infrastructure, and you get a written report that ranks the risks by impact and effort. After discovery, the work moves through these stages:

  1. Design covers the data model, module boundaries, API contracts and the screens that matter most.
  2. The build happens iteration by iteration, each one ending with a working demo, and CI runs tests, static analysis and dependency audits on every change.
  3. Our QA testers write test plans from the acceptance criteria and maintain a regression suite that grows with the product.
  4. Launch means automated, repeatable deploys, database migrations that are safe while the previous version is still serving traffic, and monitoring that’s in place before the first user arrives.
  5. From then on, error tracking and performance data decide what we improve next.

Legacy code gets characterization tests around the riskiest flows before anything changes. Then we upgrade PHP in steps and refactor behind those tests. A full rewrite is rarely the right first move.

Either way, the code lives in your repository, and you get documentation and regular written progress reports. The team fits the work, whether that’s a dedicated product team from idea to launch or a focused engagement on a single application. For more on the server side, see our backend development overview.

Frequently asked questions

Is PHP still a good choice for a new project?

For most web applications and APIs, yes. Modern PHP is typed, fast enough for typical workloads, and backed by mature frameworks and tooling. Pick something else when the workload is dominated by long-lived connections or heavy computation.

Can you take over a PHP application another team built?

Yes, and that’s a large part of our PHP work. We begin with an audit so you know what you have, then stabilize the riskiest areas before adding features.

Should we rewrite our old PHP application or upgrade it?

Usually upgrade. Incremental refactoring behind tests keeps the business running and delivers value along the way. A rewrite makes sense when the data model no longer fits the business, or when the code can’t be tested at all.

Do you only work with MySQL?

No. We work with MySQL, MariaDB and PostgreSQL, and we choose based on your data, your team and your hosting rather than habit.

How do you keep a PHP application secure after launch?

Automated dependency audits, scheduled PHP and framework upgrades, error monitoring, and periodic reviews of the authentication and authorization code. We plan it as ongoing maintenance rather than a one-time task.

For an existing PHP application, the first step is usually the audit described above, and for a new one it’s discovery. In both cases, contact us with a short description of the project and we’ll propose how to start.