Laravel is a special web framework. A modern, full toolbox packed with goodies like user account management, web sockets, concurrency, an ORM, and a variety of frontends, including a bespoke tempting language and JavaScript framework for PHP.
"Isn’t PHP notoriously 90s?", I hear you ask. Though it’s true PHP originally rocked with Cold Fusion and ASP, the language has continued to innovate and modernize with each release. Especially in its design department. Early PHP was a mess of global functions with no clear naming conventions. In the past decade, PHP has adopted numerous standards that have streamlined how the language looks and performs. Laravel has built on this work and PHP’s exceptional runtime reflection and customizability to rework basic language functionality, such as dependency injection, to create a much cleaner dependency system than vanilla PHP.
Here’s an example of using a Composer dependency from within a nested file. It could be a route, or just some backend code, but the point is that it’s unclean, and required the developer to be aware of the file’s position relative to the vendor directory. This is a huge pain on large projects, especially if you’re using vanilla PHP and file-based routing.
require_once __DIR__ . "/../../vendor/autoload.php";
use SomeNamespace\CoolClass;
$c = new CoolClass();
Compare this to Laravel, which, under the hood, has supplied it’s own dependency injection container, and that file becomes:
use SomeNamespace\CoolClass;
$c = new CoolClass();
It’s all based on namespaces and doesn’t require relative knowledge of file positions. The only requirement is that you have to follow PSR naming conventions for your files and classes. I don't know enough about how dependency injection works in PHP to comment on this further, but frankly it solves one of my biggest gripes with the language.
My favorite aspect of PHP is how customizable it is. You can really make it do anything. From how it loads dependencies, to how protect/private methods work within a class, all because of its incredible reflection capabilities. Reflection, in this context, is a programming language's ability to analyze itself. It's what enables Go to serialize structs into JSON or other formats from struct tags.
type Example struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
}
How it works is dependent on the language, but in my experience building Fan and now Osprey, two stack-based dynamic languages, I'd implement it by having the virtual machine work directly with values already on the call stack. Osprey, pictured below, is currently in very early alpha. All of the variables are based around a union type called Value
that stores them as either their natural type bool
, f64
, and null
, or as an Object responsible for it's own allocated space that can be cast to store more complex types like strings
.
var breakfast = "beignets";
var beverage = "cafe au lait";
breakfast = "beignets with " + beverage;
print breakfast;
I plan to change the keywords and statements in a later iteration of Osprey, but the context remains. That little script to print a new string results in the following call stack.
$ zig build -Dtrace run -- run .\lib\var.test.fan
== code ==
0 0 1 Constant 1 'beignets'
2 2 | DefineGlobal 0 'breakfast'
4 4 2 Constant 3 'cafe au lait'
6 6 | DefineGlobal 2 'beverage'
8 8 3 Constant 5 'beignets with '
10 10 | GetGlobal 6 'beverage'
12 12 | Add
13 13 | SetGlobal 4 'breakfast'
15 15 | Pop
16 16 5 GetGlobal 7 'breakfast'
18 18 | Print
19 19 | Return
0 1 Constant 1 'beignets'
[ beignets ]
2 | DefineGlobal 0 'breakfast'
4 2 Constant 3 'cafe au lait'
[ cafe au lait ]
6 | DefineGlobal 2 'beverage'
8 3 Constant 5 'beignets with '
[ beignets with ]
10 | GetGlobal 6 'beverage'
[ beignets with ][ cafe au lait ]
12 | Add
[ beignets with cafe au lait ]
13 | SetGlobal 4 'breakfast'
[ beignets with cafe au lait ]
15 | Pop
16 5 GetGlobal 7 'breakfast'
[ beignets with cafe au lait ]
18 | Print
beignets with cafe au lait
19 | Return
The fourth column contains Osprey's operating codes. These are bytes that represent actions or requests for the VM. Even defining something as simple as a global variable requires multiple codes. You first have to add the actual string to your constants table, then link it to your global variable. That's two separate systems working together to create a simple string. I've never implemented reflection, but I'd imagine this is where the magic happens. The next step for Osprey is a byte-code compiler. Implementing that may change my opinion on this whole matter.
Programming languages are incredibly cool. To create a piece of software that can do anything just by reading text is like creating a whole universe that works to your beat. I have dreams for Osprey, or whatever language I end up writing, where writing something portable, performant, and powerful is as simple as knowing how to code.