1
\$\begingroup\$

By extending the Model and using this inside my routefiles, I can easily call these functions inside the corresponding HTTP functions. But I feel there might be some improvements and I find it very important this basis is as well written as it can be.

ExtendedModel.php

<?php namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Helpers\QueryStringHelper;
use Illuminate\Http\Request;

class ExtendedModel extends Model {

    public static function get_by_id ($id) {
        $response = array();
        $http_status = 200;
        $error = false;

        try {
            $response['data'] = self::find($id);

            if (!$response['data']) {
                QueryStringHelper::invalid_parameter('object does not exist', 404);
            }
        } catch (Exception $e) {
            QueryStringHelper::invalid_parameter($error = $e->getMessage());
        }

        $response['error'] = $error;
        return response($response, $http_status);
    }

    public static function get_by_query_string_filters ($request) {
        $response = array();
        $http_status = 200;
        $error = false;
        $query_string_array = $request->input();
        $Query = self::query();
        $data = true;
        $count = false;
        $select = false;
        $paginate = false;

        try {
            if ($query_string_array) {
                foreach ($query_string_array as $query_string_array_key => $query_string_array_value) {
                    // If the query argument is not one of the "SQL" keywords below, check for operator
                    if (!in_array($query_string_array_key, ['count', 'data', 'limit', 'offset', 'select', 'orderby', 'paginate'])) {
                        $url_operator = QueryStringHelper::filter_url_operator($query_string_array_value);
                        $operator = QueryStringHelper::eloquent_query_operator($url_operator);
                        $query_string_array_value = QueryStringHelper::remove_operator($url_operator, $query_string_array_value);

                        // Needs 2 arguments
                        if ($url_operator == 'bt') {
                            $between_values = explode(',', $query_string_array_value);

                            if (count($between_values) != 2) {
                                QueryStringHelper::invalid_parameter('"between" needs exaclty 2 values seperated by a "," to filter');
                            }

                            $Query = $Query->whereBetween($query_string_array_key, $between_values);
                        } else {
                            $Query = $Query->where($query_string_array_key, $operator,  $query_string_array_value);
                        }
                    } else {
                        // If count is true in the query argument add a count element to the response
                        if ($query_string_array_key == 'count' && QueryStringHelper::absolute_bool($query_string_array_value)) {
                            $count = true;
                        }

                        // If data is false in the query argument do no add data element to the response
                        if ($query_string_array_key == 'data' && !QueryStringHelper::absolute_bool($query_string_array_value)) {
                            $data = false;
                        }

                        // If limit is set, return a limited result set
                        if ($query_string_array_key == 'limit') {
                            if(!QueryStringHelper::is_int($query_string_array_value)) {
                                QueryStringHelper::invalid_parameter('limit has to be an integer');
                            }
                            $Query = $Query->take(abs(intval($query_string_array_value)));
                        }

                        // If offset is set, return a offset result set
                        if ($query_string_array_key == 'offset') {
                            if(!QueryStringHelper::is_int($query_string_array_value)) {
                                QueryStringHelper::invalid_parameter('offset has to be an integer');
                            }
                            $Query = $Query->offset(abs(intval($query_string_array_value)));
                        }

                        // If select is set, return a limited result set
                        if ($query_string_array_key == 'select') {
                            if (!is_array($query_string_array_value)) {
                                QueryStringHelper::invalid_parameter('select has to be an array');
                            }
                            $select = $query_string_array_value;
                        }

                        // If orderby is set, order by the value
                        if ($query_string_array_key == 'orderby') {
                            $url_operator = QueryStringHelper::filter_url_operator($query_string_array_value);
                            $query_string_array_value = QueryStringHelper::remove_operator($url_operator, $query_string_array_value);

                            if (!in_array($url_operator, ['asc', 'desc'])) {
                                QueryStringHelper::invalid_parameter('orderby has to be "asc" or "desc"');
                            }
                            $Query = $Query->orderBy($query_string_array_value, $url_operator);
                        }

                        if ($query_string_array_key == 'paginate') {
                            if(!QueryStringHelper::is_int($query_string_array_value)) {
                                QueryStringHelper::invalid_parameter('paginate has to be an integer, the amount of records per page to show');
                            }
                            $paginate = abs(intval($query_string_array_value));
                        }
                    }
                }
            }

            if ($paginate) {
                $Query = $Query->paginate($paginate);
            } else {
                $Query = $select ? $Query->get($select) : $Query->get();
            }
        } catch (Exception $e) {
            QueryStringHelper::invalid_parameter($e->getMessage());
        }

        if ($data) {
            $response['data'] = $Query;
        }
        if ($count) {
            $response['count'] = $Query->count();
        }
        if ($error) {
            $response['error'] = $error;
        }

        return response($response, $http_status);
    }

    public static function create_model_object ($request) {
        $object = self::create($request::all());
        $response = array();
        //$response['data'] = $object;
        $response['primaryKey'] = $object->getKey();
        $response['message'] = 'success';
        return response($response, 200);
    }

    public static function update_model_object ($id, $request) {
        return self::find($id)->update($request::all());
    }

    public static function delete_model_object ($id) {
        return self::find($id)->delete();
    }
}

QueryStringHelper.php

<?php

namespace App\Helpers;

class QueryStringHelper {

    private static $query_string_operators = [
        'eq' => '=',
        'ne' => '!=',
        'lt' => '<',
        'le' => '<=',
        'ge' => '>=',
        'gt' => '>',
        'lk' => 'like',
        'bt' => 'between'
    ];

   /**
    * [eloquent_query_operator description]
    * @method  eloquent_query_operator          Translate a Query String operator in 2 character syntax to the Eloquent equivalent
    * @param  string  $query_string_operator    The Query String operator to be translated
    * @return  string                           Eloquent equivalent operator
    */
    public static function eloquent_query_operator ($query_string_operator) {
        if (!$query_string_operator) {
            self::invalid_parameter('missing operator in query string');
        }
        if (!array_key_exists($query_string_operator, self::$query_string_operators)) {
            self::invalid_parameter('undefined operator in query string');
        }

        return self::$query_string_operators[$query_string_operator];
    }

    /**
     * [remove_operator description]
     * @method  remove_operator             Remove the url operator part of the Query String value
     * @param  string  $url_operator        The to be removed url operator
     * @param  string  $full_query_value    The Query String Parameter
     * @return  string                      The value to be used stripped from it's operator
     */
    public static function remove_operator ($url_operator, $full_query_value) {
         return str_replace('[' . $url_operator . ']' , '' , $full_query_value);
    }

    /**
     * [filter_url_operator description]
     * @method  filter_url_operator         Get the operator between the brackets
     * @param  string  $full_query_value    The value part of a Query String parameter
     * @return  string or bool              The operator between the brackets or false if no match is found
     */
    public static function filter_url_operator ($full_query_value) {
        // Use a regular expression to filter the operator
        preg_match_all('/\[([a-z]+)\]/', $full_query_value, $matches);
        // If no match is found just return false
        return $matches[1][0] ?? false;
    }

    /**
     * [absolute_bool description]
     * @method  absolute_bool  Convert a string to a boolean based on content. E.g. "false" is false and "true" is true
     * @param  string  $value  The value part of a Query String parameter
     * @return  bool  The boolean interpretation of said string
     */
    public static function absolute_bool ($value) {
        return filter_var($value, FILTER_VALIDATE_BOOLEAN);
    }
    /**
     * [is_int description]
     * @method is_int Checks if the $var is a positive integer (PHP's is_int will see this value as a string)
     * @param  integer  $var To be checked
     * @return boolean  Result of being a positive integer
     */
    public static function is_int ($var) {
        preg_match_all('/[0-9]+/', $var, $matches);
        // If not a number return false
        return $matches[0][0] == $var;
    }

    /**
     * [invalid_parameter description]
     * @method invalid_parameter return an error message if the user input is invalid
     * @param  string  $error_message    [description]
     * @param  integer           $http_status_code [description]
     * @return [type]                              [description]
     */
    public static function invalid_parameter ($error_message, $http_status_code = 400) {
        $response = array();
        $response['error'] = $error_message;
        return response($response, $http_status_code);
    }
}

Example implementation for routes

<?php

Route::get('/examples/{id}', function ($id) {
    return Example::get_by_id($id);
});

Route::get('/examples', function (Request $request) {
    return Example::get_by_query_string_filters($request);
});

Route::post('/examples', function (Request $request) {
    return Example::create_model_object($request);
});

Route::put('/examples/{id}', function ($id, Request $request) {
    return Example::update_model_object($id, $request);
});

Route::delete('/examples/{id}', function($id) {
    return Example::delete_model_object($id);
});
\$\endgroup\$
  • \$\begingroup\$ I think that you are trying to provide way to much functionality. You should give user more entry points to the API to which he can make calls for specific data. orderby - its on user to format data, not API. If your insist on having such functionality I can help you make this code cleaner. \$\endgroup\$ – xReprisal Nov 4 '16 at 15:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.