Frameworks are useful for working in teams, and on long term projects. This is because they provide a set of idioms, patterns and decisions that everyone can learn and know, without having to invent them all yourself. It doesn't have to be the perfect way, it just has to be a way, so that you can focus on your domain specfic problems.
Likewise, when a new developer arrives, they know exactly where to find everything because it has a place that it belongs, and they can reference the docs if they are having trouble.
It's less about the code and more about treating the project as a whole ecosystem.
Those files might work and your project might stay on that small scale - but you'd find it impossible to add automated tests to that. If your project grew to be serious enough to warrant automated testing then you're going to want to seriously refactor it and using a framework's dependency management is just going to make your life easier. You can make things a lot safer with just a little bit of separation and that will help protect you against bugs and security issues.
This is all said by someone who is working right now with a codebase with a checkPermissions function that they added to shift things off of direct $_SESSION access and which has since been almost entirely supplanted by route based authentication. I delight in working to modernize and secure legacy systems which is why I heavily favor Laminas over Laravel due to the add-it-as-you-need-it approach, when I first worked to convert our codebase to a framework we did it page by page and component by component with everything working fine for both legacy and framework fulfilled requests and we've slowly added more components as they've become necessary (and removed ones that are no longer of use).
It might be hard to do, but do you measure any KPIs (performance, codebase size, time needed to write a new feature, security) for before/after converting the platform to a different framework?
I did switch the front-end side from no-framework (jQuery spaghetti) to a framework (React + MUI + TypeScript), but the main reason was that I needed:
1) Component reusability
2) Premade components (UI elements)
3) Data typing (thus TypeScript, to get autocompletion features)
On the back-end side, the default PHP language already supports most of those things, plus the reusability part is very little for an API (it's mostly having some basic functions such as auth/db connection and writing different queries), for which a framework wouldn't necessarily make things easier, just provide a different way of writing those queries (i.e. ORM vs direct DB query). I agree, it's nice that with an ORM you get autocompletion for the fields you want to select, but the databases that I usually use only have a few (intuitively named) columns, so if you take a look at the table it immediately makes sense (but the queries are still prone to typos, so you might get an error the first time you type it).
We don't specifically numerically measure them but we have seen our defect rate go down noticeably. The changes are also quite minimal with very little developer overhead being required - using a service architecture requires writing a factory and class definition once... using a query repository is about the same.
The biggest advantage we've seen though is in onboarding time. The more purpose driven and modular our code gets the quicker it is people pick up on each individual module types structure and new devs can start experimental coding quite quickly.
For me, personally, the ultimate attribute I value over everything else in a codebase is minimized maintenance costs - we have a few corners of our codebase that have extremely brittle automated tests that require hours of labour to execute the simplest of changes in and they're on our fix list. But the stuff we need to change frequently can be changed quickly and safely so our bug fix turn around is relatively minor. I'm actually in the middle of upgrading us from ZF2 to Laminas and this work is probably going to last about three weeks (with a bunch of the most annoying fixes coming out of Doctrine and their fanatical devotion to `final`ing every class under the sun - thanks guys) which honestly seems extremely reasonable to me for bumping two framework versions.
I'm also really not personally a fan of ORMs because the query building tends to make DB performance harder to tune and I loathe the performance implications of ActiveRecord approaches - but they're components I've built into our infrastructure to support other devs and I can comprehend their appeal.
I agree, one big reason to use frameworks is that they at least make it harder to make insecure things, but as you said, if the developer is inexperienced they can still introduce vulnerabilities in any codebase.
I would say, in this regard, that Laravel is better than plain PHP when working with inexperienced devs that are likely to make mistakes or write bad code.
A new dev can start writing code immediately by looking at any existing file, as almost all of them do the same thing: include a shared header, get some input from the user, do one or more MySQL queries, return the result.
The backend is completely separated from the frontend (so the PHP application just exposes an API to work with), so any code written there does exactly what you expect with no side effects on the client-side.
The difference is mostly the user input, MySQL query and DB table and output. This is indeed prone to typos and "brain-farts" when selecting the wrong data.
Does Laravel provide any protection in making sure you don't SELECT from the wrong table?
Generally I use the model builder methods for this anyway, because I'm using the ORM and the models represent tables, so I barely ever build my own select statements at all.
Even with an ORM, isn't there the risk of typing Flight::select('name') instead of Passenger::select('name'), if both values return the same data type (string)?
Is that harder to do than accidentally typing `SELECT name FROM flights` instead of `SELECT name FROM passengers`?
Aren't most of those issues found as soon as you first test the feature?
I don't understand the point you're getting at here. Isn't this kind of typo more or less universally possible? Which code library or generator can protect you from errors like this (and divine your intent?)
You can be protected from SQL injection and you can have various other query operations, and you can gain refactoring support (e.g. by telling the models the new table name), or you can add custom scopes that encapsulate transformations on the query.
Laravel also allows you to express model relationships, so Flight might have:
public function passengers(): BelongsTo {
return $this->hasMany(Passenger::class);
}
> Aren't most of those issues found as soon as you first test the feature?
I don't know.
The thing is, an ORM brings you other stuff (like a query builder integration) that allows you to construct queries without having to glue SQL strings together (and chain and pass query objects).
And Laravel separately also brings you a migration tool that allows you to encapsulate upgrades to your database as code snippets (so you can put code live without manually fiddling with SQL at all).
In my experience even really small apps benefit from this at some level. The discipline of using regular patterns like this is really useful.
(The Lighthouse GraphQL binding is where this stuff gets really interesting)
At any rate, we're unlikely to agree.
[edit: removed a tired-brain word choice mistake!]
There is no doubt that getting in and out of a Laravel project is a bit more difficult than the example you suggest. But the example you suggest absolutely encourages bad practice.
I wrote that example, which is simplified, but here are some security things to keep in mind when dealing with PHP/MySQL:
- Always use PDO prepared statements to avoid SQL injections
- Should probably implement a CSRF token check
- You shouldn't output values directly from the user/database and that can lead to XSS
attacks (either sanitize the stored data, or when outputting it make sure the page is always interpreted as plain text)
- You might forget to check for permissions in a specific route (code enforce this via some linting/IDE rules, by using some automated tests or by checking in an included shared script that permissions were indeed verified)
Thank you. Somehow, I thought the tree structure they described for breaking down program logic was the problem. Obviously I agree with all the other stuff, which is table stakes for a safe web app
Always use PDO prepared statements to avoid SQL injections?
Sanitize your data and use whatever driver you want. Prepare statements are like sending xml instead of json. If you are populating a database with known data why add the overhead?
Bad practice, as in security-wise, performance-wise, readability-wise, or something else?
If you trust the developer to implement things properly (use PDO prepared statements when storing data in DB, properly check for permissions, write efficient queries, etc.), shouldn't that automatically exclude common bad practices?
If the developer is inexperienced, can't they still implement something in a bad way in Laravel (e.g. save a user-submitted file on the disk without checking for permissions)?
> If you trust the developer to implement things properly (use PDO prepared statements when storing data in DB, properly check for permissions, write efficient queries, etc.), shouldn't that automatically exclude common bad practices?
That depends. How tired is the developer? How under pressure? How over deadline? ;-)
I do get your point and I understand where you're coming from.
But this model of PHP (where every URL entry point is its own script) is the thing that most encourages expedient development rather than good development, because you can just hack on that one thing without affecting anything else.
It also (traditionally) encourages somewhat riskier hosting configurations, because any PHP file the web server can route may need to be executable, and because a failure of web server configuration can expose PHP files as readable. Whereas with a single point of entry (index.php router), you can locate your codebase outside the document root, refuse to directly execute anything but your endpoint script, etc.
These are partly old-fashioned concerns, admittedly, but it's surprising how often the approach you're outlining is still the source of malicious PHP exploits where a script file gets buried in a hacked release, or where some server vulnerability can be inveigled into executing PHP code by some image file upload mechanism that didn't do adequate checking.
I agree that writing secure PHP code is hard, but using a framework like Laravel, even if it has a single point of entry, makes it a lot more likely to introduce more security issues. When a framework is popular and open-source, people will spend a lot more resources trying to find and exploit vulnerabilities than in a custom private codebase: https://snyk.io/vuln/composer:laravel%2Fframework
But at the same time, I have never seen a project built along the lines you discussed that has not collapsed under the pressure for expedience, and it's just as vulnerable to issues in libraries (which any project of any complexity will use).
I think the biggest question to ask is how big the project is.
Wikipedia, Facebook, Etsy and other huge platform are written in PHP, but I doubt they use a framework like Laravel and not some in-house built platform.
Very tiny projects probably need no framework.
That leaves medium-sized projects, but again, it's hard to define what "size" means. I think a project is bigger if it implements various functionalities instead of having hundreds of different files that do a similar thing.
I can see Laravel being used more by out-sourcing companies. Custom frameworks or no framework being used more by companies building their own product.
If your project is as simple as this then yes, a framework would be overkill for you.
The people that benefit from Laravel use a lot of its features and aren’t willing to waste time rewriting them for themselves.
In the businesses I have worked at, if we had spent the whole time building the stuff that Laravel already does for us then we’d have gone broke really quickly.
If I am working alone, I sometimes do similar stuff. But if I am working with others, I don't really want to deliberate over every new architectural decision if a project grows. I want to be able to say, "Hey, can you make a new storelocator page" and then they can get it done using the docs. They know where models, controllers, views live, they know how to make migrations for the new tables, they can even google "store locator laravel" and probably get 90% of the way there in a day or less. Once they are done, I already know how this new feature should work before I even read their code.
I took over an enormous system that a third party developer built in Laravel.
Not good code, particularly, and not particularly good database design.
Also no documentation really.
But the implementation was pretty clearly pure Laravel, and the migrations ran without issue. So that was immediately a huge weight off my mind, because I knew I would be able to maintain the live, stage and dev boxes sanely.
The two biggest problems with this app were
1) the number of places they diverged from using the query builder for no really good reason when scopes could have made everything so much more readable
2) a false assumption they made about subquery ordering in MySQL (implicit ordering that worked for them in 4.x but would not work in 5.x, something along those lines anyway)
Because of 1), 2) was incredibly painful to fix. Dozens of bits of arbitrary SQL that could have been handled by the query builder.
And I'm sure the only reason it's like that is because that's the way they started out and they had time pressure that stopped them ever changing course.
Unfortunately, this is how the majority of software is written nowadays. This is one of the reasons I prefer to build things slowly, think about stuff, implement them nicely, find clever solutions and optimize where it's possible. I know I could build more stuff and earn more money if I rushed things more, but I prefer building nicer stuff instead of more stuff. For most outsourcing companies, this doesn't really make economic sense though, but for product-driven companies it is still possible if the leadership decides to do it.
> the implementation was pretty clearly pure Laravel, and the migrations ran without issue
Being able to refactor/change one part of the code without being afraid that something else might break is an excellent reason to use a framework. Once you understand how that framework works, even if a codebase is bad, you can know what and how can break if not implemented properly. It increases the visibility of "code smells".
> Being able to refactor/change one part of the code without being afraid that something else might break is an excellent reason to use a framework.
Yep. It's also more or less essential when something is subcontracted, like this was. In this case I think there were hard-working, competent developers who were burned by the original spec hiding a lot of undiscovered complexity, and what would have been a really good piece of work went inevitably quite wrong. The subcontractors clearly desperately tried to finish it within their parameters, and the result is something that met most of the spec but satisfied nobody.
(The framework decision they made wrong was in the front end, because there they made the disastrous choice to run with Angular 1.x -- which was presumably an in-house skill -- and not push to at least use 2.x. The resulting project is the precise kind of agony that Angular 1.x is famed for.)
Aren't frameworks harder to use in a long-term project if the codebase is not constantly updated to match the new framework standards?
For example, a code-base written in React (pre-hooks) is probably pretty hard to understand for a developer that has only learned React recently and uses hooks for everything.
There’s definitely an overhead in updating your codebase to work with new versions of the framework but the time saved by not having to write all the features yourself makes it worth it.
Can you give example of some features that you have to write that don't exist in a plain PHP application nor an external library (i.e. features that have to be tightly integrated in the core of the product)?
To be honest, I'm sure all the features of Laravel I use could also be done with external libraries, but researching and finding something suitable, hooking it together, configuring, integrating it into my project is something I am unwilling to spend time on.
I write code to solve problems for my company, so building a framework is very low on my priorities. I'd rather just use something that is ready on day one.
Maybe it's not to everyone's taste but I like the fact I can just get on with trying to build my product.
There is often ongoing maintenance for sure, but if that is a concern a framework probably doesn't fit your project for other reasons already.
Frameworks are heavier, but in the same way a company gets heavier as it gets more professional and more complex.
I think your use case doesn't require a framework, so don't think my angle is to convince you that you do. Just that there is definitely a time and place for them.
Likewise, when a new developer arrives, they know exactly where to find everything because it has a place that it belongs, and they can reference the docs if they are having trouble.
It's less about the code and more about treating the project as a whole ecosystem.