package OX; BEGIN { $OX::AUTHORITY = 'cpan:STEVAN'; } { $OX::VERSION = '0.04'; } use Moose::Exporter; use 5.010; # ABSTRACT: the hardest working two letters in Perl use Bread::Board::Declare 0.11 (); use Carp 'confess'; use Class::Load 0.10 'load_class'; use namespace::autoclean (); use Scalar::Util 'blessed'; my ($import, undef, $init_meta) = Moose::Exporter->build_import_methods( also => ['Moose', 'Bread::Board::Declare'], with_meta => [qw(router route mount wrap)], as_is => [qw(as)], install => [qw(unimport)], class_metaroles => { class => ['OX::Meta::Role::Class'], }, base_class_roles => [ 'OX::Application::Role::Router::Path::Router', 'OX::Application::Role::RouteBuilder', 'OX::Application::Role::Sugar', ], ); sub import { namespace::autoclean->import(-cleanee => scalar(caller)); goto $import; } sub init_meta { my $package = shift; my %options = @_; $options{base_class} = 'OX::Application'; Moose->init_meta(%options); $package->$init_meta(%options); } sub as (&) { $_[0] } sub router { my ($meta, @args) = @_; confess "Only one top level router is allowed" if $meta->has_route_builders; if (ref($args[0]) eq 'ARRAY') { $meta->add_route_builder($_) for @{ $args[0] }; shift @args; } my ($body, %params) = @args; if (!ref($body)) { load_class($body); $meta->add_method(router_class => sub { $body }); $meta->add_method(router_dependencies => sub { \%params }); } elsif (blessed($body)) { $meta->add_method(build_router => sub { $body }); } elsif (ref($body) eq 'CODE') { if (!$meta->has_route_builders) { $meta->add_route_builder('OX::RouteBuilder::ControllerAction'); $meta->add_route_builder('OX::RouteBuilder::HTTPMethod'); $meta->add_route_builder('OX::RouteBuilder::Code'); } $body->(); } else { confess "Unknown argument to 'router': $body"; } } sub route { my ($meta, $path, $action_spec, %params) = @_; my ($class, $route_spec) = $meta->route_builder_for($action_spec); $meta->add_route( path => $path, class => $class, route_spec => $route_spec, params => \%params, ); } sub mount { my ($meta, $path, $mount, %params) = @_; if (!ref($mount)) { $meta->add_mount( path => $path, class => $mount, dependencies => \%params, ); } elsif (blessed($mount)) { confess "Class " . blessed($mount) . " must implement a to_app method" unless $mount->can('to_app'); $meta->add_mount( path => $path, app => $mount->to_app, ); } elsif (ref($mount) eq 'CODE') { $meta->add_mount( path => $path, app => $mount, ) } else { confess "Unknown mount $mount"; } } sub wrap { my ($meta, $middleware, %deps) = @_; $meta->add_middleware( middleware => $middleware, deps => \%deps, ); } 1; __END__ =pod =head1 NAME OX - the hardest working two letters in Perl =head1 VERSION version 0.04 =head1 SYNOPSIS The following describes the outline of how a model-view-controller application might be configured as an OX application. package MyApp; use OX; has model => ( is => 'ro', isa => 'MyApp::Model', lifecycle => 'Singleton', ); has template_root => ( is => 'ro', isa => 'Str', value => 'root', ); has view => ( is => 'ro', isa => 'Template', dependencies => { INCLUDE_PATH => 'template_root' }, ); has root => ( is => 'ro', isa => 'MyApp::Controller', infer => 1, ); router as { route '/' => 'root.index'; route '/inc' => ''; route '/dec' => 'root.dec'; route '/reset' => 'root.reset'; route '/set/:number' => 'root.set' => ( number => { isa => 'Int' }, ); }; =head1 DESCRIPTION OX is a web application framework based on L, L, and L. Bread::Board lets you build your application from a collection of normal L objects, organized together in a "container", which allows components to easily interoperate without any additional configuration. Path::Router maps incoming request paths to method calls on the objects in the Bread::Board container. Finally, at compile time, the framework turns your entire application into a simple PSGI coderef, which can be used directly by any PSGI-supporting web server. The philosophy behind OX is that the building blocks of your web application should just "click" together, without the overhead of an additional plugin system or "glue" layer. The combination of Bread::Board, Path::Router, and the Moose object system provides all that is needed for requests to be mapped to methods and for components to communicate with each other. For example, all configuration information can be provided via roles applied to the application class (affecting application initialization). Similarly, additional runtime features can be added by providing your own request (sub)class. Additionally, OX provides an easy-to-use "sugar" layer (based on L) that makes writing a web application as easy as writing any Moose class. The OX sugar layer supports the full complement of Moose features (attributes, roles, and more), as well as addiitonal sugar methods for mapping request routes to object methods. (See L, L, and L for more detailed information.) You're also free to eschew the sugary syntax and build your application manually -- see L for more information on going that route. =head1 FUNCTIONS =head2 as router as { ... }; Sugar function for declaring coderefs. =head2 router router as { ... }; This function declares the router for your application. By default, it creates a router based on L. Within the C body, you can declare routes, middleware, and mounted applications using the C, C, and C keywords described below. router ['My::Custom::RouteBuilder'] => as { ... }; By default, actions specified with C will be parsed by either L, L, or L, whichever one matches the route. If you want to be able to specify routes in other ways, you can specify a list of L classes as the first argument to C, which will be used in place of the previously mentioned list. router 'My::Custom::Router' => ( foo => 'some_service', ); router My::Custom::Router->new(%router_args); If you have declared a router manually elsewhere, you can pass in either the class name or the built router object to C instead of a block. It will be used directly in that case. If you pass a class name, it can take an optional hash of dependencies, which will be resolved and passed into the class's constructor as arguments. =head2 route $path, $action_spec, %params The C keyword adds a route to the current router. It is only valid in a C block. The first parameter to C is the path for the route to match, the second is an C to be parsed by an L class, and the remaining parameters are a hash of parameters containing either defaults or validations for the router to use when matching. route '/' => 'controller.index'; This declares a simple route using the L route builder. When the application receives a request for C, the application will resolve the C service, and call the C method on it, passing in an L instance for the request. The C method should return either a string, a L response arrayref, or an object that responds to C (probably a L object). route '/view/:id' => 'posts.view', ( id => { isa => 'Int' }, name => 'view', ); This declares a route with parameters. This will resolve the C service and call the C method on it, passing in a request object and the value of C. If C was provided but was not an C, this route will not match at all. Inside the C method, the C method will return a hash of C<< (controller => 'posts', action => 'view', id => $id, name => 'view') >>. Also, other parts of the application can call C with any unique subset of those parameters (such as C<< (name => 'view', id => 1) >>) to get the absolute URL path for this route (for instance, C<"/myapp/view/1"> if this app is mounted at C). route '/method' => 'method_controller'; Since this action spec doesn't contain a C<.>, this will be handled by the L route builder. If a user sends a C request to C, it will resolve the C service, and call the C method on it, passing in the request object. Variable path components and defaults and validations work identically to the description above. route '/get_path' => sub { my $r = shift; return $r->path }; This route will just call the given coderef directly, passing in the request object. Variable path components and defaults and validations work identically to the description above. route '/custom' => $my_custom_thing; In addition, if you specified any custom route builders in the C description, you can pass anything that they can handle into the second argument here as well. =head2 mount The C keyword declares an entirely separate application to be mounted under a given path in your application's namespace. This is different from C, because the targets are full applications, which handle the entire path namespace under the place they are mounted - they aren't just handlers for one specific path. mount '/other_app' => 'My::Other::App', ( template_root => 'template_root', ); If you specify a class name for the target, it will create an app by creating an instance of the class (resolving the parameters as dependencies and passing them into the constructor) and calling C on that instance. mount '/other_app' => My::Other::App->new; If you specify an object as the target, it will create the app by calling C on that object. mount '/other_app' => sub { my $env = shift; return [ 200, [], [$env->{PATH_INFO}] ]; }; You can also specify a coderef directly. Note that in this case, unlike specifying a coderef as the route spec for the C keyword, the coderef is a plain L application, which receives an env hashref and returns a full PSGI response arrayref. =head2 wrap The C keyword declares a middleware to apply to the application. The C statements will be applied in order such that the first C statement corresponds to the outermost middleware (just like L). wrap 'Plack::Middleware::Static' => ( path => dep(value => sub { s{^/static/}{} }), root => 'static_root', ); If you specify a class name as the middleware to apply, it will create an instance of the class (resolving the parameters as dependencies and passing them into the constructor) and call C on that instance, passing in the application coderef so far and using the result as the new application (this is the API provided by L). wrap Plack::Middleware::StackTrace->new(force => 1); If you specify an object as the middleware, it will call C on that object, passing in the application coderef so far and use the result as the new application. wrap sub { my $app = shift; return sub { my $env = shift; return [302, [Location => '/'], []] if $env->{PATH_INFO} eq '/'; return $app->($env); }; }; If you specify a coderef as the middleware, it will call that coderef, passing in the application coderef so far, and use the result as the new application. =head1 BUGS No known bugs. Please report any bugs through RT: email C, or browse to L. =head1 SEE ALSO =head1 SUPPORT The IRC channel for this project is C<#ox> on C. You can find this documentation for this module with the perldoc command. perldoc OX You can also look for information at: =over 4 =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * RT: CPAN's request tracker L =item * Search CPAN L =back =for Pod::Coverage import init_meta =head1 AUTHORS =over 4 =item * Stevan Little =item * Jesse Luehrs =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Infinity Interactive. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut