Commit | Line | Data |
6f28db48 |
1 | package Plack::Session::Store::DBI; |
2 | use strict; |
3 | use warnings; |
4 | |
5 | # XXX Is there a notion of auto-expiry? |
6 | |
7 | our $VERSION = '0.10'; |
8 | our $AUTHORITY = 'cpan:STEVAN'; |
9 | |
6f28db48 |
10 | use MIME::Base64 (); |
11 | use Storable (); |
12 | |
13 | use parent 'Plack::Session::Store'; |
14 | |
15 | use Plack::Util::Accessor qw[ dbh table_name serializer deserializer ]; |
16 | |
17 | sub new { |
18 | my ($class, %params) = @_; |
19 | |
7c2aa126 |
20 | if (! $params{dbh} ) { |
21 | die "DBI instance was not available in the argument list"; |
6f28db48 |
22 | } |
23 | |
24 | $params{table_name} ||= 'sessions'; |
25 | $params{serializer} ||= |
26 | sub { MIME::Base64::encode_base64( Storable::nfreeze( $_[0] ) ) }; |
27 | $params{deserializer} ||= |
28 | sub { Storable::thaw( MIME::Base64::decode_base64( $_[0] ) ) }; |
29 | |
30 | my $self = bless { %params }, $class; |
31 | $self->_prepare_dbh(); |
32 | return $self; |
33 | } |
34 | |
35 | sub _prepare_dbh { |
36 | my $self = shift; |
6f28db48 |
37 | |
7c2aa126 |
38 | my $dbh = $self->{dbh}; |
6f28db48 |
39 | # These are pre-cooked, so we can efficiently execute them upon request |
40 | my $table_name = $self->{table_name}; |
41 | my %sql = ( |
42 | get_session => |
43 | "SELECT session_data FROM $table_name WHERE id = ?", |
44 | |
45 | delete_session => |
46 | "DELETE FROM $table_name WHERE id = ?", |
47 | |
48 | # XXX argument list order matters for insert and update! |
49 | # (they should match, so we can execute them the same way) |
50 | # If you change this, be sure to change store() as well. |
51 | insert_session => |
52 | "INSERT INTO $table_name (session_data, id) VALUES (?, ?)", |
53 | update_session => |
54 | "UPDATE $table_name SET session_data = ? WHERE id = ?", |
55 | |
56 | check_session => |
57 | "SELECT 1 FROM $table_name WHERE id = ?", |
58 | ); |
59 | |
60 | while (my ($name, $sql) = each %sql ) { |
61 | $self->{"_sth_$name"} = $dbh->prepare($sql); |
62 | } |
63 | } |
64 | |
65 | sub fetch { |
66 | my ($self, $session_id) = @_; |
67 | my $sth = $self->{_sth_get_session}; |
68 | $sth->execute( $session_id ); |
69 | my ($data) = $sth->fetchrow_array(); |
70 | $sth->finish; |
71 | return $data ? $self->deserializer->( $data ) : (); |
72 | } |
73 | |
74 | sub store { |
75 | my ($self, $session_id, $session) = @_; |
76 | |
77 | # XXX To be honest, I feel like there should be a transaction |
78 | # call here.... but Catalyst didn't have it, so I'm not so sure |
79 | |
80 | my $sth; |
81 | |
82 | $sth = $self->{_sth_check_session}; |
83 | $sth->execute($session_id); |
84 | |
85 | # need to fetch. on some DBD's execute()'s return status and |
86 | # rows() is not reliable |
87 | my ($exists) = $sth->fetchrow_array(); |
88 | |
89 | $sth->finish; |
90 | |
91 | $sth = ($exists) ? |
92 | $self->{_sth_update_session} : $self->{_sth_insert_session}; |
93 | |
94 | $sth->execute( $self->serializer->($session), $session_id ); |
95 | } |
96 | |
97 | sub remove { |
98 | my ($self, $session_id) = @_; |
99 | my $sth = $self->{_sth_delete_session}; |
100 | $sth->execute( $session_id ); |
101 | $sth->finish; |
102 | } |
103 | |
104 | 1; |
105 | |
106 | __END__ |
107 | |
7c2aa126 |
108 | =head1 NAME |
109 | |
110 | Plack::Session::Store::DBI - DBI-based session store |
111 | |
112 | =head1 SYNOPSIS |
113 | |
114 | use Plack::Builder; |
115 | use Plack::Middleware::Session; |
116 | use Plack::Session::Store::DBI; |
117 | |
118 | my $app = sub { |
119 | return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Hello Foo' ] ]; |
120 | }; |
121 | |
122 | builder { |
123 | enable 'Session', |
124 | store => Plack::Session::Store::DBI->new( |
125 | dbh => DBI->connect( @connect_args ) |
126 | ); |
127 | $app; |
128 | }; |
129 | |
130 | # with custom serializer/deserializer |
131 | |
132 | builder { |
133 | enable 'Session', |
0e9a58b8 |
134 | store => Plack::Session::Store::DBI->new( |
7c2aa126 |
135 | dbh => DBI->connect( @connect_args ) |
136 | # YAML takes it's args the opposite order |
137 | serializer => sub { YAML::DumpFile( reverse @_ ) }, |
138 | deserializer => sub { YAML::LoadFile( @_ ) }, |
139 | ); |
140 | $app; |
141 | }; |
142 | |
143 | =head1 DESCRIPTION |
144 | |
145 | This implements a DBI based storage for session data. By |
146 | default it will use L<Storable> and L<MIME::Base64> to serialize and |
147 | deserialize the data, but this can be configured easily. |
148 | |
149 | This is a subclass of L<Plack::Session::Store> and implements |
150 | it's full interface. |
151 | |
152 | =head1 SESSION TABLE SCHEMA |
153 | |
6f28db48 |
154 | Your session table must have at least the following schema structure: |
155 | |
156 | CREATE TABLE sessions ( |
157 | id CHAR(72) PRIMARY KEY, |
158 | session_data TEXT |
159 | ); |
160 | |
161 | Note that MySQL TEXT fields only store 64KB, so if your session data |
162 | will exceed that size you'll want to move to MEDIUMTEXT, MEDIUMBLOB, |
163 | or larger. |
6f28db48 |
164 | |
165 | =head1 AUTHORS |
166 | |
167 | Many aspects of this module were partially based upon Catalyst::Plugin::Session::Store::DBI |
168 | |
7c2aa126 |
169 | Daisuke Maki |
170 | |
171 | =head1 COPYRIGHT AND LICENSE |
172 | |
173 | Copyright 2009, 2010 Daisuke Maki C<< <daisuke@endeworks.jp> >> |
174 | |
175 | This library is free software; you can redistribute it and/or modify |
176 | it under the same terms as Perl itself. |
177 | =cut |
178 | |