package Catalyst::Plugin::Session::Store::DBIC; use strict; use warnings; use base qw/Catalyst::Plugin::Session::Store::Delegate/; use Catalyst::Exception; use Catalyst::Plugin::Session::Store::DBIC::Delegate; use MIME::Base64 (); use MRO::Compat; use Storable (); our $VERSION = '0.14'; =head1 NAME Catalyst::Plugin::Session::Store::DBIC - Store your sessions via DBIx::Class =head1 SYNOPSIS # Create a table in your database for sessions CREATE TABLE sessions ( id CHAR(72) PRIMARY KEY, session_data TEXT, expires INTEGER ); # Create the corresponding table class package MyApp::Schema::Session; use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/Core/); __PACKAGE__->table('sessions'); __PACKAGE__->add_columns(qw/id session_data expires/); __PACKAGE__->set_primary_key('id'); 1; # In your application use Catalyst qw/Session Session::Store::DBIC Session::State::Cookie/; __PACKAGE__->config( # ... other items ... 'Plugin::Session' => { dbic_class => 'DBIC::Session', # Assuming MyApp::Model::DBIC expires => 3600, }, ); # Later, in a controller action $c->session->{foo} = 'bar'; =head1 DESCRIPTION This L storage module saves session data in your database via L. It's actually just a wrapper around L; if you need complete control over how your sessions are stored, you probably want to use that instead. =head1 METHODS =head2 setup_finished Hook into the configured session class. =cut sub setup_finished { my $c = shift; return $c->next::method unless @_; # Try to determine id_field if it isn't set unless ($c->_session_plugin_config->{id_field}) { my $model = $c->session_store_model; my $rs = ref $model ? $model : $model->can('resultset_instance') ? $model->resultset_instance : $model; my @primary_columns = $rs->result_source->primary_columns; Catalyst::Exception->throw( message => __PACKAGE__ . qq/: Primary key consists of more than one column; please set id_field manually/ ) if @primary_columns > 1; $c->_session_plugin_config->{id_field} = $primary_columns[0]; } $c->next::method(@_); } =head2 session_store_dbic_class Return the L class name to be passed to C<< $c->model >>. Defaults to C. =cut sub session_store_dbic_class { shift->_session_plugin_config->{dbic_class} || 'DBIC::Session'; } =head2 session_store_dbic_id_field Return the configured ID field name. Defaults to C. =cut sub session_store_dbic_id_field { shift->_session_plugin_config->{id_field} || 'id'; } =head2 session_store_dbic_data_field Return the configured data field name. Defaults to C. =cut sub session_store_dbic_data_field { shift->_session_plugin_config->{data_field} || 'session_data'; } =head2 session_store_dbic_expires_field Return the configured expires field name. Defaults to C. =cut sub session_store_dbic_expires_field { shift->_session_plugin_config->{expires_field} || 'expires'; } =head2 session_store_model Return the model used to find a session. =cut sub session_store_model { my ($c, $id) = @_; my $dbic_class = $c->session_store_dbic_class; $c->model($dbic_class, $id) or die "Couldn't find a model named $dbic_class"; } =head2 get_session_store_delegate Load the row corresponding to the specified session ID. If none is found, one is automatically created. =cut sub get_session_store_delegate { my ($c, $id) = @_; Catalyst::Plugin::Session::Store::DBIC::Delegate->new({ model => $c->session_store_model($id), id_field => $c->session_store_dbic_id_field, data_field => $c->session_store_dbic_data_field, }); } =head2 session_store_delegate_key_to_accessor Match the specified key and operation to the session ID and field name. =cut sub session_store_delegate_key_to_accessor { my $c = shift; my $key = $_[0]; my ($field, @args) = $c->next::method(@_); my ($type) = ($key =~ /^(\w+):/); $field = $c->session_store_dbic_id_field if $field eq 'id'; $field = $c->session_store_dbic_expires_field if $field eq 'expires'; $field = $c->session_store_dbic_data_field if $field eq 'session' or $field eq 'flash'; my $accessor = sub { shift->$type($key)->$field(@_) }; if ($field eq $c->session_store_dbic_data_field) { @args = map { MIME::Base64::encode(Storable::nfreeze($_ || '')) } @args; $accessor = sub { my $value = shift->$type($key)->$field(@_); return unless $value; return Storable::thaw(MIME::Base64::decode($value)); }; } return ($accessor, @args); } =head2 delete_session_data Delete the specified session from the backend store. =cut sub delete_session_data { my ($c, $key) = @_; # expires is stored on the session row for compatibility with Store::DBI return if $key =~ /^expires/; $c->session_store_model->search({ $c->session_store_dbic_id_field => $key, })->delete; } =head2 delete_expired_sessions Delete all expired sessions. =cut sub delete_expired_sessions { my $c = shift; $c->session_store_model->search({ $c->session_store_dbic_expires_field => { '<', time() }, })->delete; } =head1 CONFIGURATION The following parameters should be placed in your application configuration under the C key. =head2 dbic_class (Required) The name of the L that represents a session in the database. It is recommended that you provide only the part after C, e.g. C. If you are using L, the following layout is recommended: =over 4 =item * C - your L class =item * C - your session table class =item * C - your L class =back This module will then use C<< $c->model >> to access the appropriate result source from the composed schema matching the C name. For more information, please see L. =head2 expires Number of seconds for which sessions are active. Note that no automatic cleanup is done on your session data. To delete expired sessions, you can use the L method with L. =head2 id_field The name of the field on your sessions table which stores the session ID. Defaults to C. =head2 data_field The name of the field on your sessions table which stores session data. Defaults to C for compatibility with L. =head2 expires_field The name of the field on your sessions table which stores the expiration time of the session. Defaults to C. =head1 SCHEMA Your sessions table should contain the following columns: id CHAR(72) PRIMARY KEY session_data TEXT expires INTEGER The C column should probably be 72 characters. It needs to handle the longest string that can be returned by L, plus another eight characters for internal use. This is less than 72 characters when SHA-1 or MD5 is used, but SHA-256 will need all 72 characters. The C column should be a long text field. Session data is encoded using L before being stored in the database. Note that MySQL C fields only store 64 kB, so if your session data will exceed that size you'll want to use C, C, or larger. If you configure your L to include the size of the column, you will receive warnings for this problem: This session requires 1180 bytes of storage, but your database column 'session_data' can only store 200 bytes. Storing this session may not be reliable; increase the size of your data field See L for more information. The C column stores the future expiration time of the session. This may be null for per-user and flash sessions. Note that you can change the column names using the L, L, and L configuration parameters. However, the column types must match the above. =head1 AUTHOR Daniel Westermann-Clark Edanieltwc@cpan.orgE =head1 ACKNOWLEDGMENTS =over 4 =item * Andy Grundman, for L =item * David Kamholz, for most of the testing code (from L) =item * Yuval Kogman, for assistance in converting to L =item * Jay Hannah, for tests and warning when session size exceeds DBIx::Class storage size. =back =head1 COPYRIGHT Copyright (c) 2006 - 2009 the Catalyst::Plugin::Session::Store::DBIC L as listed above. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1;