function bindFunction(path, fn, verb, options) {
  var sails = this.sails;

  // Make sure (optional) options is a valid plain object ({})
  // TODO -- replace _.isPlainObject with _.isObject && !_.isArray && !_.isFunction ?
  // TODO -- if we're doing _.cloneDeep here, do we need it in all the places we do it in blueprints?
  options = _.isPlainObject(options) ? _.cloneDeep(options) : {};

  // Warn about no-longer-used blueprint request options.
  if (_.intersection(_.keys(options), ['populate', 'skip', 'limit', 'sort', 'where']).length > 0) {
    sails.log.debug('In route `' + verb + ' ' + path + ':');
    sails.log.debug('The `populate`, `skip`, `limit`, `sort` and `where` route options are no longer supported in Sails 1.0.');
    sails.log.debug('Instead, you can use a `parseBlueprintOptions` function to fully customize blueprint behavior for a route.');
    sails.log.debug('See http://sailsjs.com/docs/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions.');
    sails.log.debug();
  }

  // Get the _middlewareType of the function.
  var _middlewareType =
    // If it was set on the function itself, use that.
    fn._middlewareType ||
    // Otherwise if options._middlewareType is set (probably because the function was defined inline
    // in a call to `.bind()`), use that.
    options._middlewareType ||
    // Otherwise if the function has a name, use that.
    ('FUNCTION: ' + (fn.name || '<anonymous>'));

  // Set the middleware type on the function.  This can be useful for debugging if the same function
  // was bound in different contexts (like different actions).
  fn._middlewareType = _middlewareType;

  // Remove any _middlewareType property from the options.  It's done its job, and we don't need
  // it to get merged into req.options.
  // delete options._middlewareType;

  // Log info about the bound route in SILLY mode.
  sails.log.silly('Binding route :: ', verb || '', path, _middlewareType);


  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // FUTURE: simplify away the unnecessary function declarations below and inline the logic instead.
  // (will make it much clearer what's going on; and any minimal performance impact will be in the form of gains)
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  /**
   * `router:route`
   *
   * Create a closure that emits the `router:route` event each time the route is hit
   * before actually triggering the target function.
   *
   * NOTE: Modifications to route path parameters (i.e. `req.params`) or to `req.options`
   * must be made here, since their values can change not only on a per-request, but
   * also a per-route basis.
   */
  var enhancedFn = function routeTargetFnWrapper(req, res, next) {

    // Set req.options, using `options` to supply default values.
    req.options = _.merge({}, options || {}, req.options || {});


    // This event can be tapped into to take control of
    // (synchronous) logic that should be run before each bound
    // route handler function runs.
    sails.emit('router:route', {
      req: req,
      res: res,
      next: next,
      options: options,
      fn: fn
    });


    // Trigger original route handler function.
    //
    // > Note that, if it is an async function, then we also attach a handler to `.catch()`
    // > its return value (which will be a promise) in order to handle rejections in the same
    // > way we handle exceptions that are thrown synchronously (mainly for the purpose of being able to use async/await)
    // > (https://trello.com/c/UdK9ooJ3/108-es7-async-await-in-core-sniff-request-handler-function-to-see-if-it-s-an-async-function-if-so-then-grab-the-return-value-from-th)
    try {
      if (fn.constructor.name === 'AsyncFunction') {
        // FUTURE: benchmark this and, if tangible enough, allow configuration to be used to hard-code functions
        // one way or the other.  (Frankly, seems like we could just forcefully swap all request handling functions
        // over to be async functions -- but that'd be kind of a big change and I'd rather wait for a later release
        // unless we can prove that that'd definitely be a 100% backwards compatible change)
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        var promise = fn(req, res, next);

        promise.catch(function(e) {
          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          // Note that async+await+bluebird+Node 8 errors are not necessarily "true" Error instances,
          // as per _.isError() anyway (see https://github.com/node-machine/machine/commits/6b9d9590794e33307df1f7ba91e328dd236446a9).
          // So if we want improve the stack trace here, we'd have to be a bit more relaxed and tolerate
          // these sorts of "errors" directly as well (by tweezing out the `cause`, which is where the
          // original Error lives.)
          //
          // Note: This is now taken care of automatically by flaverr.parseError()
          // (The implementation of this "tweezing" is in the default serverError
          // response handler though.)
          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          next(e);
          // (Note that we don't do `return next(e)` here.  That's on purpose--
          // to avoid sending the wrong idea to you, dear reader)
        });
      }
      else {
        fn(req, res, next);
      }
    } catch (e) { return next(e); }
  };

  /**
   * Wrap a regex route in a helper function that pulls out regex params
   *
   * Example: for route: 'r|/\\d+/(.*)/(.*)$|foo,bar', the two parenthesized
   * groups would be pulled out as req.params[0] and req.params[1] by Express;
   * the regexRouteWrapper would then map them to req.params['foo'] and req.params['bar']
   *
   * @param  {array} params List of params to apply to the req.params object
   * @return {Function} A middleware function
   */
  var regexRouteWrapper = function(params) {

    return function(req, res, next) {
      // Apply the regex route params
      params.forEach(function(param, index) {
        req.params[param] = req.params[index];
      });
      // Call enhancedFn (which is just defined above)
      enhancedFn(req, res, next);
    };
  };

  /**
   * Wrap a route in a helper function that first checks whether the URL matches
   * any of a set of regexes, and if so, skips the defined handler.
   *
   * @param  {array}   regexes Array of regexes to match the URL against
   * @param  {Function} fn      Middleware function to run if URL does NOT match regexes
   * @return {Function} A middleware function
   */
  var skipRegexesWrapper = function(regexes, fn) {

    // Remove anything that's not a regex
    regexes = _.compact(regexes.map(function(regex) {
      if (regex instanceof RegExp) {
        return regex;
      }
      sails.log.warn('Invalid regex "' + regex + '" supplied to skipRegexesWrapper; ignoring.');
      return undefined;
    }));


    return function(req, res, next) {

      // Check for matches
      for (var i = 0; i < regexes.length; i++) {
        if (req.url.match(regexes[i])) {
          // If we find one, bail out
          return next();
        }
      }

      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      // TODO: Need to double-check on this, but shouldn't this call `enhancedFn`, instead of just `fn`?
      // If so, then we can just make that change.  Otherwise, we need to do more here.
      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      // Otherwise continue with the handler
      return fn(req, res, next);

    };

  };

  // If verb is not specified, default to CRUD methods.
  // You can still explicitly route to "all /path" if you want ALLLLlllll the things.
  var targetVerb = verb || ['get', 'put', 'post', 'delete', 'patch'];

  // Ensure targetVerb is an array of lowercased verbs.
  if (!Array.isArray(targetVerb)) {targetVerb = [targetVerb.toLowerCase()];}
  else {
    targetVerb = _.map(targetVerb, function(verb) { return verb.toLowerCase(); });
  }

  // Function to actually bind
  var targetFn;

  // Regex to check if the route is...a regex.
  var regExRoute = /^r\|(.*)\|(.*)$/;

  // Perform the check
  var matches = path.match(regExRoute);

  // If it *is* a regex, create a RegExp object that Express can bind,
  // pull out the params, and wrap the handler in regexRouteWrapper
  if (matches) {
    path = new RegExp(matches[1]);
    var params = matches[2].split(',');
    targetFn = regexRouteWrapper(params);
  }

  // Otherwise just bind enhancedFn
  else {
    targetFn = enhancedFn;
  }

  // If options.skipRegex is specified, make sure it's an array
  if (options.skipRegex) {
    if (!Array.isArray(options.skipRegex)) {
      options.skipRegex = [options.skipRegex];
    }
  }
  // Otherwise just make it an empty array
  else {
    options.skipRegex = [];
  }

  // For GET routes ending in pattern vars, default `skipAssets` to true.
  if (_.isString(path) && path.match(/\:[^\/]+\/?$/) && _.isUndefined(options.skipAssets) && _.contains(targetVerb, 'get')) {
    options.skipAssets = true;
  }

  // If "skipAssets" option is true, add the skipAssets regex
  // to the options.skipRegex array
  if (options.skipAssets) {
    options.skipRegex.push(sails.LOOKS_LIKE_ASSET_RX);
  }

  // If we have anything in the options.skipRegex array, wrap
  // the target function again.
  if (options.skipRegex.length) {
    targetFn = skipRegexesWrapper(options.skipRegex, targetFn);
  }

  // Loop through the verbs we want to bind
  targetVerb.forEach(function(verb) {

    // Bind the function to the private router
    sails.router._privateRouter[verb](path, targetFn);

    // Emit an event to make hooks aware that a route was bound
    // This allows hooks to handle routes directly if they want to-
    // e.g. with Express, the handler for this event looks like:
    // sails.hooks.http.app[verb || 'all'](path, target);
    sails.emit('router:bind', {
      path: path,
      target: targetFn,
      verb: verb,
      options: options,
      originalFn: fn
    });

  });


}
Attach middleware function to route.

@api private