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.
His point was that when you decide to compose roles (also know as traits) instead of or in addition to more traditional inheritance, override simply doesn’t work: only a method modifier will do. (And as Graham Knop and Karen Etheridge later remarked on IRC, override isn’t even an option if you’re using Moo as an alternative to Moose.)
Modifying a role’s method with around might look like this:
#!/usr/bin/env perl
use v5.12; # for strict and say use warnings;
package Local::Role::Hungry; use Moose::Role; requires 'name';
sub wants_food { my $self = shift; say $self->name, ' is hungry!'; return; }
package Local::GuineaPig; use Moose; has name => (is => 'ro'); with 'Local::Role::Hungry';
around wants_food => sub { my ($orig, $self, @args) = @_; say $self->name, ' runs to the front of the cage!'; $self->$orig(@args); say 'Wheek!'; return; };
package Local::Dog; use Moose; has name => (is => 'ro'); with 'Local::Role::Hungry';
around wants_food => sub { my ($orig, $self, @args) = @_; say $self->name, ' runs to the kitchen!'; $self->$orig(@args); say 'Woof!'; return; };
before wants_food => sub { my $self = shift; say $self->name, ' is jumping!'; };
package main; my $dog = Local::Dog->new(name => 'Seamus'); my @pigs = map { Local::GuineaPig->new(name => $_) } qw<Cocoa Ginger Pepper>;
for my $animal ($dog, @pigs) { $animal->wants_food(); }
Running the above yields:
Seamus runs to the kitchen! Seamus is hungry! Woof! Cocoa runs to the front of the cage! Cocoa is hungry! Wheek! Ginger runs to the front of the cage! Ginger is hungry! Wheek! Pepper runs to the front of the cage! Pepper is hungry! Wheek!
It’s a little more involved than overriding a sub, since method modifiers are passed both the consumed role’s original method ($orig above) and the instance ($self above) as parameters. It has the same effect, though, and you can call the original method by saying $self->$orig(parameters).
If all you want to do is have something happen either before or after the original method, just use before or after:
before wants_food => sub { my $self = shift; say $self->name, ' is jumping!'; };
Note that there’s no return value in a before or after modifier, as those are handled by the original method.
Modifiers aren’t limited to consuming classes; they can be in roles and modify their consumers’ methods. They also have a couple of other tricks:
You can pass an array reference to modify multiple methods at once.
You can use the contents of a variable to specify the modified method name, and use that same variable in the modifier itself.
Putting these together gives you constructs like these:
after qw<foo bar baz> => sub { say 'Something got called'; };
for my $method_name (qw<foo bar baz>) { before $method_name => sub { say "Calling $method_name..."; }; }
before qr/^request_/ => sub { my ($self, @args) = @_; $self->is_valid(@args) or die 'Invalid arguments'; };
Moose comes with great introductory manuals for method modifiers and roles, so be sure to check those out. There’s a lot more to them and a blog can only cover so much.
Last week found me exploring Object::Pad as an alternative to the Moo object-oriented framework for Perl since the former is prototyping the syntax and concepts for a proposed built-in OO framework named Corinna. I had to put that particular project on hold as dbcritic’s current design is a bit too role-happy and Object::Pad currently lacks method modifiers as in Moo. (Corinna is explicitly skipping them for its current minimum viable product.) Thankfully, development continues at a rapid pace. For instance, author Paul Evans has already addressed a problem I ran into when attempting to examine slot values in the debugger.
But I wanted to highlight a point I made in one of the comments last week: Object::Pad’s slots (a.k.a. fields, attributes, whatever) are private by default, completely unexposed to other class instances unless they monkey with the meta-object protocol. Unless you explicitly define or generate some kind of accessor method, these slots act like lexical (a.k.a. my) variables and are only available to methods within the class.
Here’s an example:
use v5.14; # for say and package blocks
use Object::Pad 0.50;
use Feature::Compat::Try;
class Local::MyClass {
has $arg :param = 'hello';
has $readable_slot :reader = 'world';
has $private_slot = 'shh';
method show_slots {
say "You passed me $arg in the constructor.";
say "I can see $readable_slot and you can use it as a reader.";
say "Here's me using the reader too: ", $self->readable_slot;
say "But only I can see $private_slot.";
return;
}
}
package main {
my $obj = Local::MyClass->new(arg => 'foo');
$obj->show_slots();
say $obj->readable_slot;
# Nope: Not a HASH reference
try { say $obj->{private_slot} } catch ($e) { say "Nope: $e" }
# Nope: Can't locate object method "private_slot" via package "Local::MyClass"
try { say $obj->private_slot } catch ($e) { say "Nope: $e" }
}
This stands in stark contrast to Perl’s more low-tech hashref-based objects, where all attributes are available simply through dereferencing the instance, e.g., $object->{foo}. Although discouraged, OO purists sometimes ding Perl for this kind of unenforced encapsulation, and I myself have seen codebases that violate it despite the convention of preceding private method and attribute names with an underscore (_).
Unfortunately, there is not yet any way to declare an Object::Pad method private. You could use lexical subroutines, but then you lose the convenience of a pre-made $self variable and accessibility through the MOP. The Corinna proposal lists several different types of methods including private ones, so maybe this is an area for future Object::Pad development.
Another open question from the comments: “How is [Object::Pad] on memory and speed compared to Moo and blessed objects?” Luckily the prolific perlancar has already added Object::Pad to his Bencher::Scenarios::Accessors distribution, and from that, it appears that between it and Moo, Object::Pad is faster on startup, neck-and-neck on object construction and accessor generation, and slower on reads and writes. (Note that Object::Pad is a fast-moving target so these figures may not track with the latest version’s changes.) It’s no surprise that plain blessed objects fared better than both in most scenarios except for reads, where Moo was faster than hash-based objects but slower than array-based.
I expect that should Corinna be built into Perl it would narrow that gap with blessed objects, but in my mind, the advantages of using an object system outweigh the performance hit95% of the time. As far as benchmarking memory goes, I still need to test that on a Linux box (maybe my new VPS?) once I get more familiar with the Bencher framework.
This can take a lot of work to use effectively. To help address that, several systems have been developed over the years to reduce boilerplate and provide modern (or “postmodern”) OO features that developers from other languages expect. My favorite for a while has been Moo: it’s got the features I need 90% of the time like built-in constructors, roles (an alternative to composition through inheritance), attributes, type validation, and method modifiers for enhanced polymorphism. And if I need to dig around in the guts of classes, attributes, and the like I can always upgrade to Moo’s big brother Moose and its meta-object protocol with minimal effort.
Corinna, Object::Pad, and porting dbcritic
But there’s a new kid on the block. Curtis “Ovid” Poe has been spearheading Corinna, an effort “to bring effective OO to the Perl core and leapfrog [emphasis his] the capabilities of many OO languages today.” No CPAN modules, no chain of dependencies; just solid OO features and syntax built-in. And while Corinna is a ways off from shipping, Paul “LeoNerd” Evans(maybe I should get a cool nickname too?) has been implementing some of these ideas as new Perl keyword syntax in his Object::Pad module.
Both Ovid and LeoNerd have been asking developers to try out Object::Pad, not just as a new toy, but to get feedback on what works and what needs to be added. So I thought I’d try porting an older small Moo-based project named dbcritic to this new reality. In the process, I learned some of the advantages and disadvantages of working with Object::Pad. Hopefully, this can inform both it and Corinna’s evolution as well as other curious developers’ evaluations. You can follow my coding efforts in this GitHub branch.
First, the marquee result: the code for App::DBCritic (the class I started with) is cleaner and shorter, with 33 lines shaved off so far. Mainly this is due to Object::Pad’s more concise attribute syntax (called “slots” in its documentation) and lack of explicit support for Moo’s attribute coercion. I only used the latter for one attribute in the Moo version and I’m not sure it worked particularly well, so it wasn’t hard to jettison. But if your code supports coercions extensively, you’ll have to look into Object::Pad’s BUILD or ADJUST phase blocks for now.
After, an Object::Pad slot. No coercion and builder code is handled in a later ADJUST block:
has $schema :reader :param = undef;
Speaking of ADJUST blocks, it took a little bit of insight from the #perl IRC channel to realize that they were the appropriate place for setting slot defaults that are computed from other slots. Previously I was using a maze of dependencies mixing Moo lazy attributes and builder methods. Clarifying the main set of optional constructor arguments into a single ADJUST block helped untangle things, so this might be an indication that lazy attributes are an antipattern when trying to write clean code. It’s also worth noting that Object::Pad ADJUST blocks run on object construction, whereas Moo lazy attributes are only built when needed. This tends to matter for database access.
The ADJUST block for the $schema slot:
ADJUST {
my @connect_info = ( $dsn, $username, $password );
if ($class_name and eval "require $class_name") {
$schema = $class_name->connect(@connect_info);
}
elsif ( not ( blessed($schema) and $schema->isa('DBIx::Class::Schema') ) ) {
local $SIG{__WARN__} = sub {
if ( $_[0] !~ / has no primary key at /ms ) {
print {*STDERR} $_[0];
}
};
$schema = App::DBCritic::Loader->connect(@connect_info);
}
croak 'No schema defined' if not $schema;
}
Object::Pad’s slots have one great advantage over Moo and Moose attributes: they directly support Perl array and hash data structures, while the latter only supports scalars and references contained in scalars. This means methods in your class can eliminate a dereferencing step, again leading to cleaner code. I used this specifically in the @violations array and %elements hash slots and was very pleased with the results.
The @violations and %elements slots and their ADJUST blocks:
I did have some development lifecycle issues with Object::Pad, but they’re mainly a result of its future-facing syntax. I had to give up using perltidy and perlcritic in my build and test phases, respectively: perltidy doesn’t understand slot attributes like :reader and :param and will emit an error file (but code still compiles), and several of the perlcritic policies I use report problems because its PPI parser doesn’t recognize the new syntax. I could add exceptions in the perlcriticrc file and litter my code with more ## no critic annotations than it already had, but at this point, it was easier to just disable it entirely.
Overall I’m satisfied with Object::Pad and by extension some of the syntax that Corinna will introduce. I’m going to try porting the rest of dbcritic and see if I can work around the issues I listed above without giving up the kwalitee improvement tools I’m used to. I’ll post my findings if I feel it merits another blog.
What do you think? Is this the future of object-oriented Perl? Let me know in the comments below.
I think this is the real point about Perl code readability: it gives you enough flexibility to do things however you like, and as a result many programmers are faced with a mirror that reflects their own bad practices back at them.
The other conformance tool in a Perl developer’s belt is perltidy, and it too has a Conway-compatible configuration as well as its default Perl Style Guide settings. I’ve found that more than anything else, perltidy helps settle arguments both between developers and between their code in helping to avoid excessive merge conflicts.
But apart from extra tools, Perl the language itself can be bent and even broken to suit just about anyone’s agenda. Those used to more bondage-and-discipline languages(hi, Java!) might feel revulsion at the lengths to which this has sometimes been taken, but per the quote above this is less an indictment of the language and more of its less methodical programmers.
Some of this behavior can be rehabilitated with perlcritic and perltidy, but what about other sins attributed to Perl? Here are a few perennial “favorites”:
The good news is that since 2007 we’ve had a sophisticated metaobject-protocol-based layer on top of them called Moose, since 2010 a lightweight but forward-compatible system called Moo, and a couple of even tinier options as described in the Perl OO Tutorial. Waiting in the wings is Corinna, an effort to bring next-generation object capabilities into the Perl core itself, and Object::Pad, a testbed for some of the ideas in Corinna that you can use today in current code. (Really, please try it—the author needs feedback!)
All this is to say that 99% of the time you never need trouble yourself with bless, constructors, or writing accessors for class or object attributes. Smarter people than me have done the work for you, and you might even find a concept or three that you wish other languages had.
There are two major ones: list and scalar. Another way to think of it is “plural” vs. “singular” in English, which is hopefully a thing you’re familiar with as you’re reading this blog.
Some functions in Perl act differently depending on whether the expected return value is a list or a scalar, and a function will provide a list or scalar context to its arguments. Mostly these act just as you would expect or would like them to, and you can find out how a function behaves by reading its documentation. Your own functions can behave like this too, but there’s usually no need as “both scalars and lists are automatically interpreted into lists.” Again, Perl’s DWIMmery at work.
Subroutine and Method Arguments
I’ve already written about this. Twice. And presented about it. Twice. The short version: Perl has signatures, but they’ve been considered experimental for a while. In the meantime, there are alternatives on CPAN. You can even have type constraints if you want.
I’ll leave you with this: Over the past month, Neil Bowers of the Perl Steering Council has been collecting quirks like these from Perl developers. The PSC is reviewing this collection for potential documentation fixes, bug fixes, further discussion, etc. I wouldn’t expect to see any fundamental changes to the language out of this effort, but it’s a good sign that potentially confusing features are being addressed.
{"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}