From 997a90bcf52fc0c01f589fa1b78851f34b043ddc Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 31 Oct 2011 22:15:47 -0500 Subject: [PATCH] major session refactoring. --- application/{libraries => classes}/.gitignore | 0 application/config/application.php | 2 +- application/models/.gitignore | 0 laravel/autoloader.php | 98 ++++++ laravel/bootstrap/constants.php | 23 +- laravel/bootstrap/core.php | 59 +--- laravel/config/container.php | 68 +---- laravel/container.php | 10 + laravel/facades.php | 63 ++++ laravel/form.php | 2 +- laravel/input.php | 4 +- laravel/laravel.php | 22 +- laravel/redirect.php | 2 +- laravel/session/manager.php | 289 ------------------ laravel/session/payload.php | 279 +++++++++++++++++ laravel/session/transporters/cookie.php | 39 --- laravel/session/transporters/transporter.php | 26 -- storage/sessions/.gitignore | 1 + 18 files changed, 484 insertions(+), 503 deletions(-) rename application/{libraries => classes}/.gitignore (100%) delete mode 100644 application/models/.gitignore create mode 100644 laravel/autoloader.php create mode 100644 laravel/facades.php delete mode 100644 laravel/session/manager.php create mode 100644 laravel/session/payload.php delete mode 100644 laravel/session/transporters/cookie.php delete mode 100644 laravel/session/transporters/transporter.php diff --git a/application/libraries/.gitignore b/application/classes/.gitignore similarity index 100% rename from application/libraries/.gitignore rename to application/classes/.gitignore diff --git a/application/config/application.php b/application/config/application.php index f02bc76a..175b1904 100644 --- a/application/config/application.php +++ b/application/config/application.php @@ -154,7 +154,7 @@ 'Redis' => 'Laravel\\Redis', 'Request' => 'Laravel\\Request', 'Response' => 'Laravel\\Response', - 'Session' => 'Laravel\\Session\\Manager', + 'Session' => 'Laravel\\Facades\\Session', 'Str' => 'Laravel\\Str', 'Validator' => 'Laravel\\Validation\\Validator', 'View' => 'Laravel\\View', diff --git a/application/models/.gitignore b/application/models/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/laravel/autoloader.php b/laravel/autoloader.php new file mode 100644 index 00000000..43f64c5c --- /dev/null +++ b/laravel/autoloader.php @@ -0,0 +1,98 @@ +aliases = $aliases; + } + + /** + * Load the file corresponding to a given class. + * + * @param string $class + * @return void + */ + public function load($class) + { + // Most of the core classes are aliases for convenient access in spite + // of the namespace. If an alias is defined for the class, we will load + // the alias and bail out of the auto-load method. + if (array_key_exists($class, $this->aliases)) + { + return class_alias($this->aliases[$class], $class); + } + + $file = str_replace('\\', '/', $class); + + $namespace = substr($class, 0, strpos($class, '\\')); + + // If the class namespace exists in the libraries array, it means that + // the library is PSR-0 compliant, and we will load it following those + // standards. This allows us to add many third-party libraries to an + // application and be able to auto-load them automatically. + if (array_key_exists($namespace, $this->libraries)) + { + require CLASS_PATH.$this->psr($file); + } + + foreach ($this->paths as $path) + { + if (file_exists($path = $path.strtolower($file).EXT)) + { + require $path; + + return; + } + } + + // If the namespace exists in the classes directory, we will assume the + // library is PSR-0 compliant, and will add the namespace to the array + // of libraries and load the class accordingly. + if (is_dir(CLASS_PATH.$namespace)) + { + $this->libraries[] = $namespace; + + require CLASS_PATH.$this->psr($file); + } + } + + /** + * Format a path for PSR-0 compliant auto-loading. + * + * @param string $file + * @return string + */ + protected function psr($file) + { + return str_replace('_', '/', $file); + } + +} \ No newline at end of file diff --git a/laravel/bootstrap/constants.php b/laravel/bootstrap/constants.php index 6b7bdefc..02db99e8 100644 --- a/laravel/bootstrap/constants.php +++ b/laravel/bootstrap/constants.php @@ -4,11 +4,6 @@ define('CRLF', chr(13).chr(10)); define('EXT', '.php'); -/** - * Define a function that registers an array of constants if they haven't - * haven't already been registered. This allows the constants to - * be changed from their default values when unit testing. - */ function constants($constants) { foreach ($constants as $key => $value) @@ -17,11 +12,6 @@ function constants($constants) } } -/** - * Register the core framework paths. All other paths are built on - * top of these core paths. All of these paths are changable by - * the developer in the front controller. - */ $constants = array( 'APP_PATH' => realpath($application).'/', 'BASE_PATH' => realpath("$laravel/..").'/', @@ -34,19 +24,13 @@ function constants($constants) unset($application, $public, $storage, $laravel); -/** - * Register all of the other framework paths. All of these paths - * are built on top of the core paths above. We still allow the - * developer to override these for easy unit testing. - */ $constants = array( 'CACHE_PATH' => STORAGE_PATH.'cache/', + 'CLASS_PATH' => APP_PATH.'classes/', 'CONFIG_PATH' => APP_PATH.'config/', 'CONTROLLER_PATH' => APP_PATH.'controllers/', 'DATABASE_PATH' => STORAGE_PATH.'database/', 'LANG_PATH' => APP_PATH.'language/', - 'LIBRARY_PATH' => APP_PATH.'libraries/', - 'MODEL_PATH' => APP_PATH.'models/', 'ROUTE_PATH' => APP_PATH.'routes/', 'SESSION_PATH' => STORAGE_PATH.'sessions/', 'SYS_CONFIG_PATH' => SYS_PATH.'config/', @@ -57,11 +41,6 @@ function constants($constants) constants($constants); -/** - * Set the Laravel environment configuration path constant. - * The environment is controlled by setting an environment - * variable on the server running Laravel. - */ $environment = (isset($_SERVER['LARAVEL_ENV'])) ? CONFIG_PATH.$_SERVER['LARAVEL_ENV'].'/' : ''; constants(array('ENV_CONFIG_PATH' => $environment)); diff --git a/laravel/bootstrap/core.php b/laravel/bootstrap/core.php index 03b3b690..9553a2a7 100644 --- a/laravel/bootstrap/core.php +++ b/laravel/bootstrap/core.php @@ -2,69 +2,20 @@ require 'constants.php'; -/** - * Load the classes that can't be resolved through the auto-loader. - * These are typically classes that are used by the auto-loader or - * configuration classes, and therefore cannot be auto-loaded. - */ require SYS_PATH.'arr'.EXT; require SYS_PATH.'config'.EXT; +require SYS_PATH.'facades'.EXT; +require SYS_PATH.'container'.EXT; +require SYS_PATH.'autoloader'.EXT; -/** - * Load some core configuration files by default so we don't have to - * let them fall through the Config parser. This will allow us to - * load these files faster for each request. - */ Config::load('application'); Config::load('container'); Config::load('session'); -/** - * Bootstrap the application inversion of control (IoC) container. - * The container provides the convenient resolution of objects and - * their dependencies, allowing for flexibility and testability - * within the framework and application. - */ -require SYS_PATH.'container'.EXT; +IoC::bootstrap(); -$container = new Container(Config::$items['container']); +spl_autoload_register(array(IoC::container()->core('autoloader'), 'load')); -IoC::$container = $container; - -unset($container); - -/** - * Register the application auto-loader. The auto-loader closure - * is responsible for the lazy-loading of all of the Laravel core - * classes, as well as the developer created libraries and models. - */ -$aliases = Config::$items['application']['aliases']; - -spl_autoload_register(function($class) use ($aliases) -{ - if (array_key_exists($class, $aliases)) - { - return class_alias($aliases[$class], $class); - } - - $file = strtolower(str_replace('\\', '/', $class)); - - foreach (array(BASE_PATH, MODEL_PATH, LIBRARY_PATH) as $path) - { - if (file_exists($path = $path.$file.EXT)) - { - require_once $path; - - return; - } - } -}); - -unset($aliases); - -/** - * Define a few convenient global functions. - */ function e($value) { return HTML::entities($value); diff --git a/laravel/config/container.php b/laravel/config/container.php index a0284e0e..f1d405d3 100644 --- a/laravel/config/container.php +++ b/laravel/config/container.php @@ -2,25 +2,11 @@ return array( - /* - |-------------------------------------------------------------------------- - | Laravel Routing Components - |-------------------------------------------------------------------------- - | - | The following components are used by the Laravel routing system. - | - | The router is used to map a given method and URI to a route intance. - | - | The route loader is responsible for loading the appropriates routes file - | for a given request URI, as well as loading all routes when the framework - | needs to find a named route wtihin the application. - | - | The route caller is responsible for receiving a route and taking the - | appropriate action to execute that route. Some routes delegate execution - | to a controller, so this class will also resolve controllers out of the - | container and call the appropriate methods on those controllers. - | - */ + 'laravel.autoloader' => array('singleton' => true, 'resolver' => function($c) + { + return new Autoloader(Config::$items['application']['aliases']); + }), + 'laravel.routing.router' => array('singleton' => true, 'resolver' => function($c) { @@ -39,17 +25,6 @@ return new Routing\Caller($c, require APP_PATH.'filters'.EXT, CONTROLLER_PATH); }), - /* - |-------------------------------------------------------------------------- - | Laravel Database Connectors - |-------------------------------------------------------------------------- - | - | The following components are used to establish PDO database connections - | to the various database systems supported by Laravel. By resolving these - | connectors out of the IoC container, new database systems may be added - | by simply registering a connector in the container. - | - */ 'laravel.database.connectors.sqlite' => array('resolver' => function($c) { @@ -66,16 +41,6 @@ return new Database\Connectors\Postgres; }), - /* - |-------------------------------------------------------------------------- - | Laravel Caching Components - |-------------------------------------------------------------------------- - | - | The following components are used by the wonderfully simple Laravel cache - | system. Each driver is resolved through the container, so new drivers may - | be added by simply registering them in the container. - | - */ 'laravel.cache.apc' => array('resolver' => function($c) { @@ -118,29 +83,6 @@ return $memcache; }), - /* - |-------------------------------------------------------------------------- - | Laravel Session Components - |-------------------------------------------------------------------------- - | - | The following components are used by the Laravel session system. - | - | The framework allows the session ID to be transported via a variety - | of different mechanisms by resolve the ID itself and the session - | transporter instance out of the container. This allows sessions - | to be used by clients who cannot receive cookies. - | - | The session manager is responsible for loading the session payload - | from the session driver, as well as examining the payload validitiy - | and things like the CSRF token. - | - */ - - 'laravel.session.transporter' => array('resolver' => function($c) - { - return new Session\Transporters\Cookie; - }), - 'laravel.session.apc' => array('resolver' => function($c) { diff --git a/laravel/container.php b/laravel/container.php index 32f076d8..64bd9526 100644 --- a/laravel/container.php +++ b/laravel/container.php @@ -9,6 +9,16 @@ class IoC { */ public static $container; + /** + * Bootstrap the global IoC instance. + * + * @return void + */ + public static function bootstrap() + { + static::$container = new Container(Config::$items['container']); + } + /** * Get the active container instance. * diff --git a/laravel/facades.php b/laravel/facades.php new file mode 100644 index 00000000..7d761db9 --- /dev/null +++ b/laravel/facades.php @@ -0,0 +1,63 @@ +resolve(static::$resolve); + + $count = count($parameters); + + if ($count > 5) + { + return call_user_func_array(array($class, $method), $parameters); + } + elseif ($count == 1) + { + return $class->$method($parameters[0]); + } + elseif ($count == 2) + { + return $class->$method($parameters[0], $parameters[1]); + } + elseif ($count == 3) + { + return $class->$method($parameters[0], $parameters[1], $parameters[2]); + } + elseif ($count == 4) + { + return $class->$method($parameters[0], $parameters[1], $parameters[2], $parameters[3]); + } + elseif ($count == 5) + { + return $class->$method($parameters[0], $parameters[1], $parameters[2], $parameters[3], $parameters[4]); + } + } + +} + +class Autoloader extends Facade { public static $resolve = 'laravel.autoloader'; } +class Session extends Facade { public static $resolve = 'laravel.session'; } \ No newline at end of file diff --git a/laravel/form.php b/laravel/form.php index 5f2b810b..b02ae842 100644 --- a/laravel/form.php +++ b/laravel/form.php @@ -159,7 +159,7 @@ public static function raw_token() throw new \Exception("A session driver must be specified before using CSRF tokens."); } - return Session\Manager::get('csrf_token'); + return IoC::container()->core('session')->get('csrf_token'); } /** diff --git a/laravel/input.php b/laravel/input.php index ad888782..435eb89a 100644 --- a/laravel/input.php +++ b/laravel/input.php @@ -96,7 +96,9 @@ public static function old($key = null, $default = null) throw new \Exception('A session driver must be specified in order to access old input.'); } - return Arr::get(Session\Manager::get(Input::old_input, array()), $key, $default); + $session = IoC::container()->core('session'); + + return Arr::get($session->get(Input::old_input, array()), $key, $default); } /** diff --git a/laravel/laravel.php b/laravel/laravel.php index a961fd5d..9a919d20 100644 --- a/laravel/laravel.php +++ b/laravel/laravel.php @@ -26,11 +26,21 @@ */ if (Config::$items['session']['driver'] !== '') { + require SYS_PATH.'cookie'.EXT; + require SYS_PATH.'session/payload'.EXT; + $driver = IoC::container()->core('session.'.Config::$items['session']['driver']); - $transporter = IoC::container()->core('session.transporter'); + if ( ! is_null($id = Cookie::get(Session\Payload::cookie))) + { + $payload = new Session\Payload($driver->load($id)); + } + else + { + $payload = new Session\Payload; + } - Session\Manager::start($driver, $transporter); + IoC::container()->instance('laravel.session', $payload); } /** @@ -113,13 +123,13 @@ /** * Close the session and write the active payload to persistent - * storage. The input for the current request is also flashed - * to the session so it will be available for the next request - * via the Input::old method. + * storage. The session cookie will also be written and if the + * driver is a sweeper, session garbage collection might be + * performed depending on the "sweepage" probability. */ if (Config::$items['session']['driver'] !== '') { - Session\Manager::close(); + IoC::container()->core('session')->save($driver); } /** diff --git a/laravel/redirect.php b/laravel/redirect.php index 625ecf26..92f76432 100644 --- a/laravel/redirect.php +++ b/laravel/redirect.php @@ -61,7 +61,7 @@ public function with($key, $value) throw new \Exception('A session driver must be set before setting flash data.'); } - Session\Manager::flash($key, $value); + IoC::container()->core('session')->flash($key, $value); return $this; } diff --git a/laravel/session/manager.php b/laravel/session/manager.php deleted file mode 100644 index d9678dc3..00000000 --- a/laravel/session/manager.php +++ /dev/null @@ -1,289 +0,0 @@ -load($transporter->get($config)); - - // If the session is expired, a new session will be generated and all of - // the data from the previous session will be lost. The new session will - // be assigned a random, long string ID to uniquely identify it among - // the application's current users. - if (is_null($session) or (time() - $session['last_activity']) > ($config['lifetime'] * 60)) - { - static::$exists = false; - - $session = array('id' => Str::random(40), 'data' => array()); - } - - // Now that we should have a valid session, we can set the static session - // property and check for session data such as the CSRF token. We will - // also set the static driver and transporter properties, since they - // will be used to close the session at the end of the request. - static::$session = $session; - - if ( ! static::has('csrf_token')) static::put('csrf_token', Str::random(16)); - - list(static::$driver, static::$transporter) = array($driver, $transporter); - } - - /** - * Determine if the session or flash data contains an item. - * - * @param string $key - * @return bool - */ - public static function has($key) - { - return ( ! is_null(static::get($key))); - } - - /** - * Get an item from the session. - * - * - * // Get an item from the session - * $name = Session::get('name'); - * - * // Return a default value if the item doesn't exist - * $name = Session::get('name', 'Taylor'); - * - * - * @param string $key - * @param mixed $default - * @return mixed - */ - public static function get($key, $default = null) - { - foreach (array($key, ':old:'.$key, ':new:'.$key) as $possibility) - { - if (array_key_exists($possibility, static::$session['data'])) - { - return static::$session['data'][$possibility]; - } - } - - return ($default instanceof Closure) ? call_user_func($default) : $default; - } - - /** - * Write an item to the session. - * - * - * // Write an item to the session - * Session::put('name', 'Taylor'); - * - * - * @param string $key - * @param mixed $value - * @return void - */ - public static function put($key, $value) - { - static::$session['data'][$key] = $value; - } - - /** - * Write an item to the session flash data. - * - * Flash data only exists for the next request. After that, it will be - * removed from the session. Flash data is useful for temporary status - * or welcome messages. - * - * - * // Flash an item to the session - * Session::flash('name', 'Taylor'); - * - * - * @param string $key - * @param mixed $value - * @return void - */ - public static function flash($key, $value) - { - static::put(':new:'.$key, $value); - } - - /** - * Keep all of the session flash data from expiring at the end of the request. - * - * @return void - */ - public static function reflash() - { - static::replace(':old:', ':new:', array_keys(static::$session['data'])); - } - - /** - * Keep a session flash item from expiring at the end of the request. - * - * If a string is passed to the method, only that item will be kept. - * An array may also be passed to the method, in which case all - * items in the array will be kept. - * - * - * // Keep a session flash item from expiring - * Session::keep('name'); - * - * - * @param string|array $key - * @return void - */ - public static function keep($key) - { - if (is_array($key)) - { - return array_map(array('Laravel\\Session\\Manager', 'keep'), $key); - } - - static::flash($key, static::get($key)); - - static::forget(':old:'.$key); - } - - /** - * Remove an item from the session. - * - * @param string $key - * @return Driver - */ - public static function forget($key) - { - unset(static::$session['data'][$key]); - } - - /** - * Remove all items from the session. - * - * @return void - */ - public static function flush() - { - static::$session['data'] = array(); - } - - /** - * Regenerate the session ID. - * - * @return void - */ - public static function regenerate() - { - static::$session['id'] = Str::random(40); - - static::$regenerated = true; - - static::$exists = false; - } - - /** - * Age the session payload, preparing it for storage after a request. - * - * @return array - */ - public static function age() - { - static::$session['last_activity'] = time(); - - foreach (static::$session['data'] as $key => $value) - { - if (strpos($key, ':old:') === 0) static::forget($key); - } - - static::replace(':new:', ':old:', array_keys(static::$session['data'])); - - return static::$session; - } - - /** - * Readdress the session data by performing a string replacement on the keys. - * - * @param string $search - * @param string $replace - * @param array $keys - * @return void - */ - protected static function replace($search, $replace, $keys) - { - $keys = str_replace($search, $replace, $keys); - - static::$session['data'] = array_combine($keys, array_values(static::$session['data'])); - } - - /** - * Close the session handling for the request. - * - * @return void - */ - public static function close() - { - $config = Config::$items['session']; - - static::$driver->save(static::age(), $config, static::$exists); - - static::$transporter->put(static::$session['id'], $config); - - // Some session drivers clean-up expired sessions manually; however, since - // this can be an expensive process, it is only performed once in a while. - // The probability of session garbage collection occuring for any given - // request is controlled by the "sweepage" configuration option. - $sweep = (mt_rand(1, $config['sweepage'][1]) <= $config['sweepage'][0]); - - if ($sweep and static::$driver instanceof Drivers\Sweeper) - { - static::$driver->sweep(time() - ($config['lifetime'] * 60)); - } - } - -} \ No newline at end of file diff --git a/laravel/session/payload.php b/laravel/session/payload.php new file mode 100644 index 00000000..f9516a87 --- /dev/null +++ b/laravel/session/payload.php @@ -0,0 +1,279 @@ +session = $session; + + if ($this->invalid()) + { + $this->exists = false; + + // A CSRF token is stored in every session. The token is used by the + // Form class and the "csrf" filter to protect the application from + // cross-site request forgery attacks. The token is simply a long, + // random string which should be posted with each request. + $token = Str::random(40); + + $this->session = array('id' => Str::random(40), 'data' => compact('token')); + } + } + + /** + * Deteremine if the session payload instance is valid. + * + * The session is considered valid if it exists and has not expired. + * + * @return bool + */ + protected function invalid() + { + $lifetime = Config::$items['session']['lifetime']; + + return is_null($this->session) or (time() - $this->last_activity > ($lifetime * 60)); + } + + /** + * Determine if the session or flash data contains an item. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return ( ! is_null($this->get($key))); + } + + /** + * Get an item from the session. + * + * The session flash data will also be checked for the requested item. + * + * + * // Get an item from the session + * $name = Session::get('name'); + * + * // Return a default value if the item doesn't exist + * $name = Session::get('name', 'Taylor'); + * + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + foreach (array($key, ':old:'.$key, ':new:'.$key) as $possibility) + { + if (array_key_exists($possibility, $this->session['data'])) + { + return $this->session['data'][$possibility]; + } + } + + return ($default instanceof Closure) ? call_user_func($default) : $default; + } + + /** + * Write an item to the session. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function put($key, $value) + { + $this->session['data'][$key] = $value; + } + + /** + * Write an item to the session flash data. + * + * Flash data only exists for the next request to the application. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function flash($key, $value) + { + $this->put(':new:'.$key, $value); + } + + /** + * Keep all of the session flash data from expiring at the end of the request. + * + * @return void + */ + public function reflash() + { + $this->keep(array_keys($this->session['data'])); + } + + /** + * Keep a session flash item from expiring at the end of the request. + * + * @param string|array $key + * @return void + */ + public function keep($keys) + { + foreach ((array) $keys as $key) $this->flash($key, $this->get($key)); + } + + /** + * Remove an item from the session data. + * + * @param string $key + * @return Driver + */ + public function forget($key) + { + unset($this->session['data'][$key]); + } + + /** + * Remove all of the items from the session. + * + * @return void + */ + public function flush() + { + $this->session['data'] = array(); + } + + /** + * Assign a new, random ID to the session. + * + * @return void + */ + public function regenerate() + { + $this->session['id'] = Str::random(40); + + $this->exists = false; + } + + /** + * Store the session payload in storage. + * + * @param Driver $driver + * @return void + */ + public function save(Driver $driver) + { + $this->session['last_activity'] = time(); + + $this->age(); + + $config = Config::$items['session']; + + // To keep the session persistence code clean, session drivers are + // responsible for the storage of the session array to the various + // available persistent storage mechanisms. + $driver->save($this->session, $config, $this->exists); + + $this->cookie(); + + // Some session drivers implement the Sweeper interface, meaning that they + // must clean up expired sessions manually. If the driver is a sweeper, we + // need to determine if garbage collection should be run for the request. + // Since garbage collection can be expensive, the probability of it + // occuring is controlled by the "sweepage" configuration option. + if ($driver instanceof Sweeper and (mt_rand(1, $config['sweepage'][1]) <= $config['sweepage'][0])) + { + $driver->sweep(time() - ($config['lifetime'] * 60)); + } + } + + /** + * Age the session flash data. + * + * Session flash data is only available during the request in which it + * was flashed, and the request after that. To "age" the data, we will + * remove all of the :old: items and re-address the new items. + * + * @return void + */ + protected function age() + { + foreach ($this->session['data'] as $key => $value) + { + if (strpos($key, ':old:') === 0) $this->forget($key); + } + + $this->replace(':new:', ':old:', array_keys($this->session['data'])); + } + + /** + * Re-address the session data by performing a string replacement on the keys. + * + * @param string $search + * @param string $replace + * @param array $keys + * @return void + */ + protected function replace($search, $replace, $keys) + { + $keys = str_replace($search, $replace, $keys); + + $this->session['data'] = array_combine($keys, array_values($this->session['data'])); + } + + /** + * Send the session ID cookie to the browser. + * + * @return void + */ + protected function cookie() + { + $config = Config::$items['session']; + + $minutes = ( ! $config['expire_on_close']) ? $config['lifetime'] : 0; + + Cookie::put(Payload::cookie, $this->id, $minutes, $config['path'], $config['domain'], $config['secure']); + } + + /** + * Dynamically retrieve items from the session array. + */ + public function __get($key) + { + return (isset($this->session[$key])) ? $this->session[$key] : $this->get($key); + } + +} \ No newline at end of file diff --git a/laravel/session/transporters/cookie.php b/laravel/session/transporters/cookie.php deleted file mode 100644 index fc10ada0..00000000 --- a/laravel/session/transporters/cookie.php +++ /dev/null @@ -1,39 +0,0 @@ -