When I started programming a few years back, I was told that PHP is legacy tech and a terrible language I should avoid like a plague. After years of battling with Node.js on the backend, I took a closer look at modern PHP and the Laravel framework and I was amazed by how good the developer experience was. The language itself feels like a lite version of Java. I believe everyone starting on web development should know PHP. It's hugely deployed, it has a very mature and rich ecosystem and it's a great language to build your side project/business without spending your time on meaningless tasks. Don't be driven by FOMO like I was, there's no such thing as a "perfect programming language". Every language has it's quirks and that's fine. PHP might be old and boring but it helps you get things done faster than using the new coolest language.
> there's no such thing as a "perfect programming language". Every language has it's quirks and that's fine. PHP might be old and boring but it helps you get things done faster than using the new coolest language.
I disagree with you on this point. I worked with PHP for two years on some legacy systems, as well as some Laravel-based systems. While Laravel yields results that are worlds away from PHP build in the 90's, I still wasted dozens of hours because of poor constructs that the language is unfortunately coupled to. I documented each instance where I wasted a significant amount of time due to issues like scoping issues, closure problems, arrays-that-are-arrays-sometimes-but-maps-other-times, etc.
PHP has gotten better. But there are so many good choices that provide much better tooling, guarantees, etc. Examples: Ruby, Go, and Elixir. Heck, even Perl is more sane about its data types in some ways by enforcing consistent comparisons with `==` vs. `eq` and much more robust data structures.
I agree that every language has it's quirks. But I think PHP has so many that it seriously gets in the way often. I don't think it offers any significant velocity gains over using, say, Ruby.
EDIT to be fair, i see the rationale – having the index of the filtered element is useful sometimes, and requires some contortions with the usual impl of filter. it's quite neat, because you get both the index and the elements! but it's just... surprising as the default, and PHP's conflation of maps and lists obscures it – all the docs say is "array keys are preserved", which makes sense in retrospect, but doesn't really jump out for something with this much impact
This is exactly the sort of thing that bites. It totally breaks your functional pipelines: now you have to thread everything through `array_values`. Ew ew ew. No thank you.
On the other hand, having learned PHP inside-out as my first language, this is perfectly natural to me, and I miss the flexibility of PHP arrays in other languages :)
I don’t believe there are maps in PHP - just plain arrays which to be honest are pretty simple to use. Unlike say JavaScript where arrays are always numerically indexed, PHP can use any value as an index.
I’m pretty sure this is wrong. JS objects and arrays are different. The former will only yield elements from the array itself when iterated on, and has a consistent iteration order, while the later does not.
Arrays are objects of class Array which have special literals, override the toString() method, and update the non-enumerable "length" property on certain operations.
Try calling Object.keys([1, 2, 3]), or even better try Object.getOwnPropertyNames([1, 2, 3])
With the exception that unlike (I think?) PHP, JS also uses maps (w hich can only have string keys [1]) to implement objects and classes. Hence their being called objects, not maps.
[1] So how do arrays have numerical keys? Well... actually they don't. I was trying to make JS look better than it is in my original comment. Actually, array keys are stringified versions of numbers, and values are automatically cast to string when you put them in indexing braces.
Object.keys([1, 2])
--> ["0", "1"]
As you can imagine, this leads to gotchas when you assume that a JS object can have, say, a date as a key
b = {}; b[new Date()] = 10;
Object.keys(b)
--> [
"Sat Nov 09 2019 23:15:58 GMT-0800 (Pacific Standard Time)"
]
I believe it is just a naming issue. Not even sure that PHP devs have agreed on how to name the type. Internally, PHP calls them both arrays and hash tables if I understand correctly.
That being said AFAICT, HashTable and zend_array are used interchangeably throughout the source. I am not a C programmer, but I did write a couple of PHP extensions and that was my general understanding. Perhaps it is a compatibility issue or just used to abstract types differently in various areas of the C API.
Check out [2] for a deeper understanding of how arrays are handled internally in PHP.
PHP has come a long way, and nowadays it's in the same category as Python. Worse in many ways, but better by other, like the type declarations and their (runtime) enforcing.
Yet there are still many ugly sides to PHP, and Laravel illustrates most of them. There is so much magic that IDE can't follow: some classes have a `__call()` magic function that redirect methods calls to other instances. Some functions return values of varying types, with no common interface.
I've worked with several PHP frameworks, and Laravel is by far the worst. Its awful documentation plays a big role in this (no real reference doc, just a tutorial ; no links to classes or function in the doc ; the API doc is a joke ; the acclaimed "laracasts" are useless for serious work). The fact that this framework is dominant in the PHP community is worrying.
Laravel is popular because it allows you to setup your project very quickly.
And then the trouble starts. Remember what properties your models had? No? Well so doesn't your IDE.
There is just too much magic going on to keep things maintainable.
If you like a framework like Laravel you should go with Symfony instead. But don't use annotations. Keeps things separated so you and your colleagues can find routing and database information at logical places instead of all over the place in classes.
For model properties and a few other IDE related issues with laravel you can use ide helper which generates annotations on your models by inspecting the schema in the database. The ide then uses the annotations and gives you property auto complete among other things.
I would recommend you to give Yii[0] a try. It is a solid, consistent and well thought framework with a vey good documentation. Unfortunately, It has always been eclipsed by new kids on the block frameworks like Laravel but really deserve more credits (provided you accept to let some of the laravel magic go).
I work for a web development company and We have been successfully using Yii for years now, including for medium size projects (in terms of LOC and exposure).
Regarding model properties - there’s a standard documentation syntax that can be used to document that and give you autocomplete in your IDE (Laravel IDE helper can also auto generate these for you).
Not ideal - since it’s not enforced in anyway (the same as local scope type doc declarations). But can be useful.
I agree in regards to Larvel. It was a breath of fresh air in the early days but they have baked so much magic into it and break backwards compatibility constantly that I have long given up even considering it. I will second the recommendation for symfony [1]. It lets you compose your app of just what components you need instead of the everything and the kitchen sink approach. However, composable systems are my preference but may not be for everyone.
How long ago was your experience? I started using Laravel early last year, took a hiatus and have been actively developing in it for the past 7 months. This is coming from years with Perl Dancer / Dancer2. I have not run into these issues. But I have to admit my own bias in preferring the “show me how” instructional method over “tell me everything, no matter how esoteric”. I’ve also found Laravel superior to Dancer in every way. But I did have to get into a different mindset.
> But I have to admit my own bias in preferring the “show me how” instructional method over “tell me everything, no matter how esoteric”.
I don't think it's about preferring one approach or the other.
A good library/framework should have both instructional documentation and reference documentation. They have different use-cases and are not interchangeable.
> Its awful documentation plays a big role in this
Laravel is lauded by many for its fantastic documentation. It's not comprehensive, but it's still pretty extensive, easy to read, and the gaps are only for niche situations.
As far as the API docs, what is your complaint about them?
I know absolutely nothing about Laravel and haven't touched PHP in nearly 20 years - but within about 10 seconds on Google I found the following, which at first glance looks pretty comprehensive:
That is an old version, the current being https://laravel.com/api/6.x/ but I have to agree, I have found them to be great. There is no reference to the API docs from the main website though, which I have always found odd.
php is definitely better these days (i used to use it early in my career), but unless you're already knee-deep in php, i can't think of a web project where php/laravel would generally be a better choice than ruby/rails. alas, ruby has become passé too, in favor of javascript, a decidedly unrefined language.
javascript has too many hidden ways to shoot yourself in the foot, and just hasn't evolved for developer happiness the way php and ruby have.
honestly i haven't looked at laravel recently enough to remember the specifics (although others here seem to have), but 2 general stengths are (1) ruby is more consistent, intuitive and fun vs. php, and (2) rails has reasonable defaults, easy configurability on top of that, and a nice ecosystem of both built-in and community-built widgets. it's so fast to get up and running with rails.
but i'd pick php over js any day! (probably even over python/django, unless data mining is a core concern)
Which part do you see that is not performance in that benchmark which shown the average time? I have tried benchmark on my own and PHP took longer than RubyJIT.
Command being timed: "/usr/local/bin/ruby --jit -W0 nbody.rb 50000000"
User time (seconds): 249.30
System time (seconds): 0.58
Percent of CPU this job got: 100%
Elapsed (wall clock) time (h:mm:ss or m:ss): 4:08.97
---
PHP 7.3.11 (cli) (built: Oct 24 2019 11:29:52) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.11, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.11, Copyright (c) 1999-2018, by Zend Technologies
gtime -v php -n nbody.php 50000000
-0.169075164
-0.169059907
Command being timed: "php -n nbody.php 50000000"
User time (seconds): 248.02
System time (seconds): 0.49
Percent of CPU this job got: 99%
Elapsed (wall clock) time (h:mm:ss or m:ss): 4:09.82
Polish and refinement. Ruby's expressiveness and clean design make possible a level of elegance in Rails that I haven't seen in any other web framework. Ruby lends itself to DSLs much more than most other languages, Clojure excepted, and its meta-programming makes possible a number of shortcuts which help you to get a project up and running in record time. Add to that the magnitude of commits to the Rails project over its 14-year history and you have an ideal tool for SME web projects.
Considering PHP's pseudo-Java verbosity I wouldn't exactly classify it as a fast-to-develop language. Doc-block comments are an abomination if you come from Ruby,Python or Javascript and if you follow the numerous PSRs to the letter you're lucky if you can fit a couple of lines of real code on a screen surrounded as they will be by space-wasting blank lines, doc-block annotations and KR-style braces.
I guess you have not read the post you are commenting. One of the rationale behind union types is to reduce the doc-block comments. It is also, I believe, one of the idea of introducing a stronger type system in PHP.
From the original post:
[quote]
Supporting union types in the language allows us to move more type information from phpdoc into function signatures, ...
[/quote]
Whether you love it or hate it, it's cool that the PHP project has come so far. I know a lot of people still use PHP every day, so it's good to see the language continues to improve. I imagine a significant percentage of web services are still PHP based to this day.
It's nice to start out typeless during problem domain discovery and proof of concept phase then quickly introduce types when you better understand the problem. This change will help that phase transition with enforcement right in the runtime as opposed to simple doc block annotations.
Yes. We want to spend as little time as possible on code so we can quickly find the data structure that best solves the problem we have yet to understand.
// I know nothing. Not sure what types would work best...
public function method($data)
// I know more
public function method($data array)
// More
public function method($data array|false)
// Even more
public function method($data array|false) : int
// Ah. Okay. I understand the problem domain and have proper data structures to solve it
public function method($data array|false) : int|float
Notice the type system never got in our way. We organically grew into the type system as our understanding of the data evolved.
The Hack HHVM JIT is typically more performant than standard PHP 7. However, that is more of an apples to oranges comparison of JIT vs Non-JIT than Lang to Lang comparison.
That said, PHP 7 has squeezed most of the performance from the language and the low hanging fruit is gone.
However, PHP 8 looks to bring a standard JIT to PHP [1]. I'd guess that once that happens Hack / HHVM may no longer have any advantage.
This is one bad feature among many good decisions, but bad nonetheless. The language will have yet-another-way to do type-erasure/autoboxing that will have to be tracked down, in the interest of removing boilerplate when multiple types are desired to be coerced. Type safety is recognized as valuable by the PHP core voters on one hand, and waved away by the other.
Do any relational databases have them? I’ve never understood why not. They go through the trouble of providing a mechanism to formally model your data schema, but if your data involves things that can be one thing or another, they totally punt and ask you to find a way to hack it on top via ORMs or similar. Why not model everything in Postgres such that users can issue requests against their actual data model and Postgres can make it fast, instead of building a shim layer between the data model surface and Postgres (shim layer = ORM, in case it wasn’t obvious) that can only generate queries that are likely more difficult for Postgres to optimize for lack of missing type information.
I don't understand this comment from the standpoint that DBs including PostgreSQL, my fav, are not duck typed like higher level languages. They have to store the data in binary that must conform to something that can both be validated and indexed. That's to say how do you index something such as an int against a float. And how do you validate a constraint the the value must be between 1 and 10. This is why DBs can be hard and you have things like collations even among the same encoding for strings. Let DBs be good at data.
I don't see the need for abstraction either especially with Postgres which has a very good type casting system. I can insert a float into an integer column without any trouble and can return the same. Postgres even has a great syntactical sugar of :: for casting such that value::int or value::numeric just works.
Finally if this is a true requirement Postgres fully supports domains which are custom data types. They are not difficult to deal with and could provide for a syntax which would handle that. You might need a little work but I can have a domain which would include the int type and float type as a single data type. This still requires some plumbing to create a true union however it's not that far off. The downside however is again the DB has to store in binary so a domain like that would have two values stored for a single value which would be less than optimal solution.
The true value for PHP programmers here is that we can get closer to type validation in a duck typed system. This provides multiple type validation of scalars as parameter to a function. Less interesting to Java, Python, Ruby developers where everything is an object except in a few cases. Scalars are still widely used in PHP and this feature allows for not sending "banana" as a parameter to a function, that in the past would just cast this to 1. If you think banana == 1 then you are going to have logic bugs.
I think that's because the proper way to accomplish something like that in SQL is via specification. I do agree with you, though, postgres added enums (another feature that it's not strictly needed in SQL, but nice-to-have) but not something like that.
It's possible to emulate union types in SQL with triggers (i.e. check that the field A and B cannot be NULL at the same time, but it does not work if NULL is be a possible value.)
An aside: I wish some relation database adopted sum types: I didn't thought about the implications, but doing 'create table foo ( bar Maybe integer );' and then 'select Some bar ...' would be cool (and maybe a cleaner way to work with NULL.)
> An aside: I wish some relation database adopted sum types: I didn't thought about the implications, but doing 'create table foo ( bar Maybe integer );' and then 'select Some bar ...' would be cool (and maybe a cleaner way to work with NULL.)
That's not an aside, that's my whole argument--RDBMSes should support sum types for all of the same reasons that we should use RDBMSes in the first place: developers describe a data model and work against that while the database storage and retreival. Postgres enums and NULL are just special cases of sum types.
>Do any relational databases have them? I’ve never understood why not
For one, because they need a fixed binary representation for the type, to persist it on disk. In a programming language yoi do things in memory, so you don't have that issue...
Still, you could have had union types, or even coerce everything as string, as SQLite does, but it would be bad for performance as they'd need an alternate representation.
> For one, because they need a fixed binary representation for the type, to persist it on disk. In a programming language yoi do things in memory, so you don't have that issue...
Memory and consequently programming languages also requires a fixed binary representation. How you represent data is orthogonal to its storage medium--you can write application memory to disk and read it back in, no problem (e.g., swap).
> Still, you could have had union types, or even coerce everything as string, as SQLite does, but it would be bad for performance as they'd need an alternate representation.
The problem doesn't go away by moving support out of the database and into the application; it only makes it worse insofar as the application is limited in its optimizations. At the end of the day, the real world has sum types, applications use them, and they are encoded into databases--they simply aren't encoded _well_ and the database isn't giving you any correctness guarantees as it does for product types (i.e., structs, records, etc).
>Memory and consequently programming languages also requires a fixed binary representation
Already covered that.
Programming languages can save their data as unions or structs, and take the minimal hit to switch on the type.
DB's persisting data on disk can do the same but will take a much bigger hit.
>How you represent data is orthogonal to its storage medium--you can write application memory to disk and read it back in, no problem (e.g., swap).
The costs are not orthogonal to the storage medium however.
>The problem doesn't go away by moving support out of the database and into the application
On the DBs side, it does go away. The DB only has to guarantee what it says it supports (only store one specific type in a column). So for the DB implementors, that's a great invariant for their implementation ease and performance.
> Programming languages can save their data as unions or structs, and take the minimal hit to switch on the type. DB's persisting data on disk can do the same but will take a much bigger hit.
Yeah, of course. Disk is more expensive across the board. Same applies for storing ints, but databases don’t punt on that. And anyway, the sum types still exist in the schema, they are just implicit, as hoc spectacles built on the fly by the user. So you’re still dealing with performance issues, but they’re worse.
> On the DBs side, it does go away. The DB only has to guarantee what it says it supports (only store one specific type in a column). So for the DB implementors, that's a great invariant for their implementation ease and performance.
This applies to every feature for every tool. You don’t have to solve the problem if you just put it on your users.
Of course, tools have charters, and the relational axiom is that users shouldn’t have to manage their own storage and retrieval layer, but rather they should declare a data model and interface with it and the RDBMS would make search and retrieval fast and correct. Sum types are necessary in data modeling, so it fits clearly and neatly into the charter.
"For one, because they need a fixed binary representation for the type, to persist it on disk. In a programming language you do things in memory, so you don't have that issue..."
The crucial difference I point is "persist on disk" vs "do it in memory", not in the "binary representation". Both running programs and DBs have one, but one absolutely needs to be persisted on disk, whereas live program memory doesn't.
>This applies to every feature for every tool. You don’t have to solve the problem if you just put it on your users.
There's also the fact that it might not be a problem just an easy cop-out from the user.
In which case it's better to force your users into the more formal and rigid structure, and have them rethink their model, than turn the DB into an "anything goes anywhere" store.
> The crucial difference I point is "persist on disk" vs "do it in memory", not in the "binary representation". Both running programs and DBs have one, but one absolutely needs to be persisted on disk, whereas live program memory doesn't.
Right, I agree, and my point was it doesn’t matter. Disk vs memory is a red herring. The same principles apply to both and the fact that disk is slower applies as much to product types as it does to sum types. In fact, sum types are represented as product types, but the system enforces invariants about the structure.
> There's also the fact that it might not be a problem just an easy cop-out from the user.
That’s a nope from me. Tools exist to solve problems. If a tool purports to solve a problem but only does it halfway, it warrants criticism or observation.
> In which case it's better to force your users into the more formal and rigid structure, and have them rethink their model, than turn the DB into an "anything goes anywhere" store.
RDBMSs are literally forcing their users into a less formal structure. You can’t rethink your model and make them go away (they are fundamental data modeling primitives), you can only find ways to hack product types to represent them, but you have to do all the work to make them fast and you probably just have to give up on verifiable safety altogether.
And how do you get from “sum types” to “anything goes data store”? Are you sure you understand the debate?
>The DB only has to guarantee what it says it supports (only store one specific type in a column).
If you mean primitive types specifically, this already is far from the case, and it's great. One "type" is great, but types can be composed. Allowing types beyond primitive types has already been a blessing for me. Getting columns of type ARRAY[some other type] or MAP[type, type] is incredibly convenient. I don't feel so strongly about the JSON types that are entering into every db, but they're certainly supported and widely used.
Why would the binary representation matter distinctly for storage on disk versus storage in RAM? If you need a fixed width representation, why not do unions like C does?
For classes it's enforced since PHP 5. For scalars you get to chose between enforcement or type coercion for callers on a per-file basis.
However, as with other dynamically typed and interpreted languages, all of this is happening at run-time, so one of the biggest benefits of strong typing (type checking at compile time) doesn't apply.
Sorry to interrupt here, but this is wrong. it is not enforced to use types for anything (arguments, return values or properties) if you don't want to. Using it is completely optional.
Edit: Ah I believe there is a misunerstanding between posters here. The usage of types is not enforced, but when you use them their correctness is enforced at runtime.
Unfortunately, PHP doesn't ship a tool for this (and it’s hard due to the way autoloading works). So PHP's typing is slightly less useful than e.g. TypeScript.
They do static analyses that go beyond type checking. I have not tried phan and psalm, but the last time I used phpstan its type analysis threw false positives because it was using incorrect assumptions. So my impression is that phpstan lacks the rigor of a true compiler‘s type checker phase.
In my opinion, the PHP distribution shipping an official type checker which does nothing other than verify type correctness based on the information in the code would be much more useful. Kind of like `php -l`.
Tooling is not the issue here, nor is autoloading.
ISO C and C++ doesn't provide tooling, nor should any language. Tooling is better handled by third parties and implementers. Language should be focused on application and execution of the language. Unfortunately for PHP the language and implementation are tightly coupled as is Java and Swift.
A well constructed modern PHP project using composer has the tooling needed to statically validate. Personally I use PHPStorm but I also use IntelliJ for Java and Android. There are others out there as well.
Autoloading used correctly is no more than Java using a package line or namespace.
I find no problem with TypeScript and others. I however have a 10+ million line code base of PHP that predates TypeScript and others and needs to get a viable transition path. This gets things closer with real type errors at runtime. That is way better than my mainframe COBOL counterparts who have no path forward at the level of modern code.
Tooling inside IDEs is somwhat useful. But being able to just run a compiler-like CLI tool to tell you if there are type errors in your program is much more useful still, since you can run it in pre-commit hooks and on the CI. As far as I know, a tool which can do this _without_ requiring extensive configuration and without throwing false positives does not exist yet for PHP.
As for the "language shouldn't provide tooling" argument: You picked C / C++ as a positive example for this. Those are standardized languages which evolve at a glacial pace. For most of their use cases, this is a good thing. But I'd say PHP's faster evolution over the last decade was the right thing for that language. Other modern language projects seem to follow a strategy of a single standard implementation with extensive tooling pretty successfully (e.g. Go, Rust, Swift, ..).
There are phan, php-stan or psalm tools from third parties which add this functionality to PHP. All three of them are very good and actively developed.
Before the usual avalanche of posts criticising some PHP 4 features, the union types look like a nice way to formalise something that is commonly used and very useful.