Originally coined in 1998 during the “dot-com” bubble, I had thought that the term “LAMP” had faded with developers in the intervening decades with the rise of language-specific web frameworks for:
Granted, PHPstill relies on web server-specific modules, APIs, or variations of the FastCGI protocol for interfacing with a web server. And Python web applications typically make use of its WSGI protocol either as a web server extension or, like the Perl examples above, as a proxiedstandalone server. But all of these are deployment details and do little to describe how developers implement and extend a web application’s structure.
Note how the various four-letter JavaScript stacks (e.g., MERN, MEVN, MEAN, PERN) differentiate themselves mostly by frontend framework (e.g., Angular, React, Vue.js) and maybe by the (relational or NoSQL) database (e.g., MongoDB, MySQL, PostgreSQL). All however seem standardized on the Node.js runtime and Express backend web framework, which could, in theory, be replaced with non-JavaScript options like the more mature LAMP-associated languages and frameworks. (Or if you prefer languages that don’t start with “P”, there’s C#, Go, Java, Ruby, etc.)
My point is that “LAMP” as the name of a web development stack has outlived its usefulness. It’s at once too specific (about operating system and web server details that are often abstracted away for developers) and too broad (covering three separate programming languages and not the frameworks they favor). It also leaves out other non-JavaScript back-end languages and their associated frameworks.
The question is: what can replace it? I’d propose “NoJS” as reminiscent of “NoSQL,” but that inaccurately excludes JavaScript from its necessary role in the front-end. “NJSB” doesn’t exactly roll off the tongue, either, and still has the same ambiguity problem as “LAMP.”
How about pithy sort-of-acronyms patterned like database-frontend-backend? Here are some Perl examples:
MRDancer: MySQL, React, and Dancer (I use this at work. Yes, the M could also stand for MongoDB. Naming things is hard.)
MRMojo: MongoDB, React, and Mojolicious
PACat: PostgreSQL, Angular, and Catalyst
etc.
Ultimately it comes down to community and industry adoption. If you’re involved with back-end web development, please let me know in the comments if you agree or disagree that “LAMP” is still a useful term, and if not, what should replace it.
This blog has devoted a fair amount of attention to the popular and multifaceted object-oriented system Moose and its lightweight subset Moo. I’ve also covered Object::Pad, the testbed of concepts and syntax for Corinna, the proposed next-generation Perl core OO system. But what if your project is too memory‑, performance‑, or dependency-constrained for these options?
It turns out that CPAN has a rich history of lighter-weight OO modules to meet many different needs. If you can live with their trade-offs, they’re worth investigating instead of rolling your own layer over Perl’s OO. Here are a few.
Class::Struct’s main claim to fame is its inclusion in the standard Perl distribution, so there’s no need to install dependencies from CPAN. It provides a syntax for defining classes as C‑style structs at either compile time or runtime. (There’s no speed advantage to the former; it just means that your class will be built as if you had written the accessors yourself as subs.) Here’s an example:
#!/usr/bin/env perl
use v5.24; # for strict, say, and postfix dereferencing
use warnings;
package Local::MyClass;
use Class::Struct (
foo => '$',
bar => '@',
baz => '%',
);
package main;
my $obj = Local::MyClass->new(
foo => 'hello',
bar => [1, 2, 3],
baz => { name => 'Mark'},
);
say $obj->foo, ' ', $obj->baz('name');
say join ',', $obj->bar->@*;
# replace the name element of baz
$obj->baz(name => 'Sharon');
# replace the second element of bar
$obj->bar(1, 'replaced');
say $obj->foo, ' ', $obj->baz('name');
say join ',', $obj->bar->@*;
And here’s the output:
hello Mark
1,2,3
hello Sharon
1,replaced,3
Note that Class::Struct supports accessors for scalar, array, and hash types, as well as other classes (not demonstrated). Consult the module’s documentation for the different ways to define and retrieve them.
Class::Accessor does one thing: it makes accessors and mutators (also known as getters and setters) for fields in your class. Okay, it actually does another thing: it provides your class with a new method to initialize those fields. Those accessors can be read-write, read-only, or write-only. (Why would you want write-only accessors?) You can define any of them using either its historical class methods or a Moose-like attribute syntax.
If you’re trying to squeeze every bit of performance out of your code and can sacrifice a little flexibility in altering accessor behavior, you can opt for Class::Accessor::Fast or Class::Accessor::Faster. The former still uses hash references under the hood to represent objects and the latter uses array references. The main Class::Accessor documentation contains an efficiency comparison of the three for your edification.
Here’s an example script using Class::Accessor::Faster and the Moose-like syntax:
#!/usr/bin/env perl
use v5.12; # for strict and say
use warnings;
package Local::MyClass;
use Class::Accessor::Faster 'moose-like';
has readwrite => (is => 'rw');
has readonly => (is => 'ro');
package main;
my $obj = Local::MyClass->new( { # must be a hash reference
readwrite => 'hello',
readonly => 'world',
} );
say $obj->readwrite, ' ', $obj->readonly;
$obj->readwrite('greetings');
say $obj->readwrite, ' ', $obj->readonly;
# throws an error
$obj->readonly('Cleveland');
And here is its output:
hello world
greetings world
'main' cannot alter the value of 'readonly' on objects of class 'Local::MyClass' at ./caf.pl line 24.
Class::Tiny both does less and more than Class::Accessor. All of its generated accessors are read-write, but you can also give their attributes lazy defaults. Its generated constructor takes arguments via either a Class::Accessor-style hash reference or a plain list of key/value pairs, so that’s a little more convenient. It also supports Moose-style BUILDARGS, BUILD, and DEMOLISH methods for argument adjustment, validation, and object cleanup, respectively.
It’s a toss-up as to which of the previous two is “better.” You’ll have to examine their respective features and determine which ones map to your needs.
Here’s an example script that shows a few of Class::Tiny’s unique features:
#!/usr/bin/env perl
use v5.12; # for strict and say
use warnings;
package Local::MyClass;
use Class::Tiny qw<foo bar>,
{
baz => 'default baz',
timestamp => sub { time },
};
package main;
my $obj = Local::MyClass->new( # plain key-values OK
foo => 'hello',
bar => 'world',
);
say $obj->foo, ' ', $obj->bar;
say 'Object built on ', scalar localtime $obj->timestamp;
$obj->foo('greetings');
$obj->bar('Cleveland');
say $obj->foo, ' ', $obj->bar;
say $obj->baz;
And its output:
hello world
Object built on Tue Sep 7 09:00:00 2021
greetings Cleveland
default baz
For an even more minimalist approach, consider Object::Tiny. Its accessors are read-only, it gives you a simple constructor, and that’s it. Its documentation lists a number of reasons why it can be superior to Class::Accessor, including lower memory usage and less typing. There’s also a fork called Object::Tiny::RW that adds read-write support to its accessors.
Class::Tiny’s documentation contains a feature table comparison of it, Object::Tiny, and Class::Accessor. This may help you decide which to use.
Here’s an example script:
#!/usr/bin/env perl
use v5.12; # for strict and say
use warnings;
package Local::MyClass;
use Object::Tiny qw<foo bar>;
package main;
my $obj = Local::MyClass->new(
foo => 'hello',
bar => 'world',
);
say $obj->foo, ' ', $obj->bar;
# has no effect unless you use Object::Tiny::RW
$obj->foo('greetings');
say $obj->foo, ' ', $obj->bar;
And its output:
hello world
hello world
Add some speed with XS
If the above options are still too slow and you don’t mind requiring a C compiler to install them, there are variants that use Perl’s XS interface instead of pure Perl code:
If you’re eyeing Moose and Moo’s support for roles (also known as traits) as an alternative to inheritance but still want to keep things light with one of the above modules, you’re in luck. The Role::Tiny module lets you compose methods into consuming classes with Moo-like syntax and will pull in Common Lisp Object System-style method modifier support from Class::Method::Modifiers if you need it. It does mean another couple of CPAN dependencies, so if that’s a problem in your situation you’ll just have to live without roles.
Here’s an example script with a role and a consuming class that uses Class::Tiny. The role requires that its consumers implement a required_method, provides a foo method that uses it, and a method modifier for bar.
#!/usr/bin/env perl
use v5.12; # for strict and say
use warnings;
package Local::MyRole;
use Role::Tiny;
requires 'required_method';
sub foo {
my $self = shift;
say $self->required_method();
}
before bar => sub {
warn 'About to call bar...';
};
package Local::MyClass;
use Class::Tiny {name => ''};
use Role::Tiny::With;
with 'Local::MyRole';
sub bar {
my ($self, $greeting) = @_;
say "$greeting ", $self->name;
}
sub required_method {
my $self = shift;
return 'Required by Local::MyRole';
}
package main;
my $obj = Local::MyClass->new(name => 'Mark');
$obj->bar('hello');
$obj->name('Sharon');
$obj->bar('salutations');
$obj->foo();
And its output:
About to call bar... at ./rt.pl line 17.
hello Mark
About to call bar... at ./rt.pl line 17.
salutations Sharon
Required by Local::MyRole
What’s your favorite?
There will always be those who insist on writing everything longhand, but modules like these can save a lot of time and typing as well as reduce errors. Do you have a favorite, maybe something I missed? Let me know in the comments.
This upcoming blog series by Perl core contributor Paul Evans promises to be very interesting, as it details what goes into developing and committing a new feature into Perl itself. Evans recently added the isa operator to Perl 5.32, and will be describing how to add a similar (but fictional) feature across 10 or more articles. I’m looking forward to following along.
{"id":"11","mode":"button","open_style":"in_modal","currency_code":"USD","currency_symbol":"$","currency_type":"decimal","blank_flag_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":500,"top_media_type":"featured_image","featured_image_url":"https:\/\/phoenixtrap.com\/wp-content\/uploads\/2021\/02\/image-200x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Leave a tip!","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"The Phoenix Trap","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"The Phoenix Trap","form_subtitle":"Do you like what you see? Leave a one-time or recurring tip!","currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for being a supporter!","payment_confirmation_title":"The Phoenix Trap","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"What is the name on your credit card?"},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"What is your email address?"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}