Optional arguments kwargs in PHP

Goal

Create a function that reads a JSON file and returns its content as an array or object.

There are many optional arguments in json_decode() and file_get_contents() native PHP functions. These args are all lined up within the argument parentheses. I don't want to line them up in my new function, that's insane.

I want to create keyword arguments for a PHP function, just like I could do in Ruby or Python.


Ruby and Python kwargs

Ruby and Python have kwargs: named arguments passed in a function/method grouped as a hash or dictionary.

Ruby kwargs

In Ruby, the named arguments are hash items and result in a hash.

def print_kwargs(**kwargs)
    puts kwargs.inspect
end

print_kwargs(make: "Honda", model: "Civic", year: "1991")
print_kwargs(:make=>"Honda", :model=>"Civic", :year=>"1991")
# Curly brackets are optional
print_kwargs({make: "Honda", model: "Civic", year: "1991"})
kwargs = {:make=>"Honda", :model=>"Civic", :year=>"1991"}
print_kwargs(kwargs)

Result:

{:make=>"Honda", :model=>"Civic", :year=>"1991"}

Python kwargs

In Python, the named arguments result in a dictionary.

def print_kwargs(**kwargs):
    print(kwargs)

print_kwargs(make="Honda", model="Civic", year="1991")

Result:

{'make': 'Honda', 'model': 'Civic', 'year': '1991'}

PHP kwargs

In PHP, I can feed a function some associative array to act as kwargs.

function print_kwargs(array $kwargs = []) {
    var_export($kwargs);
}
print_kwargs(['make'=>"Honda", 'model'=>"Civic", 'year'=>"1991"]);

Result:

array (
  'make' => 'Honda',
  'model' => 'Civic',
  'year' => '1991',
)

Create a PHP function with kwargs

  1. Create function with kwargs.
  2. Override default options with provided kwargs.
  3. Use kwargs keys as local variables for clarity.

Needed native PHP functions

Look at the optional arguments of native PHP functions I want to use.

file_get_contents()

https://www.php.net/manual/en/function.file-get-contents.php

file_get_contents ( string $filename [, bool $use_include_path = FALSE [, resource $context [, int $offset = 0 [, int $maxlen ]]]] ) : string
json_decode()

https://www.php.net/manual/en/function.json-decode.php

json_decode ( string $json [, bool $assoc = FALSE [, int $depth = 512 [, int $options = 0 ]]] ) : mixed

My function

/**
 * Read a json file and returns its content as an array or object.
 *
 * @params string $filename
 * @params array $kwargs
 * @return mixed
 */
function json_read($filename, array $kwargs = []) {
    extract($kwargs + [
        'assoc' => TRUE, // TRUE= associative array, FALSE= stdClass object
        'depth' => 512,
        'options' => 0,
        'use_include_path' => FALSE,
    ]);
    $json = @file_get_contents($filename, $use_include_path);
    return json_decode($json, $assoc, $depth, $options);
}

Explanation

Using native PHP function argument variable names as kwargs

I usually use the same function argument variable names when I call a native PHP function.

file_get_contents()

For file_get_contents(), I don't need all the optional arguments listed in its definition.

file_get_contents($filename, $use_include_path, $context, $offset, $maxlen);

I just need file_get_contents($filename, $use_include_path).

json_decode()

For json_decode(), I need all the optional arguments listed in its definition.

json_decode($json, $assoc, $depth, $options);

In fact, I want to return the result of the json_decode() call.

json_read() is like json_decode() but reading a file as a string to feed json_decode().

Declaring default options

I use native PHP function argument variable names ('assoc', 'depth', 'options', 'use_include_path') as kwargs keys and values.

I am modifying the default option $assoc = FALSE to TRUE in my json_decode() call.

extract($kwargs + [
    'assoc' => TRUE, // TRUE= associative array, FALSE= stdClass object
    'depth' => 512,
    'options' => 0,
    'use_include_path' => FALSE,
]);

Overriding default options

I am using the Union of two arrays to override the default options.

"The + operator returns the right-hand array appended to the left-hand array; for keys that exist in both arrays, the elements from the left-hand array will be used, and the matching elements from the right-hand array will be ignored."

So:

['options'=>JSON_BIGINT_AS_STRING] + ['options'=>0] // => ['options'=>2] because JSON_BIGINT_AS_STRING = (integer) 2

So in this function call:

json_read($filename, ['options'=>JSON_BIGINT_AS_STRING]);

I merge $kwargs and defaults with the Union of the provided $kwargs plus the default $kwargs that contains default options.

$kwargs + ['options'=>0] // => ['options'=>2]

So the default option ['options'=>0] gets overridden by the provided kwarg ['options'=>JSON_BIGINT_AS_STRING].

Extracting to local variables

Instead of this:

return json_decode($json, $kwargs['assoc'], $kwargs['depth'], $kwargs['options']);

I prefer this:

return json_decode($json, $assoc, $depth, $options);

So I use extract() https://www.php.net/manual/en/function.extract.php to wrap the union above.

Reading the file

I put the file contents in the $json variable which is json_decode()'s first argument.

$json = @file_get_contents($filename, $use_include_path);

I suppress errors (@) that would trigger file_get_contents() if there was an error reading a file. I know that a json_decode(NULL) call will result in NULL without errors. I don't need Exception handling in json_read(); I can let it return NULL.

Decoding JSON

return json_decode($json, $assoc, $depth, $options);

Tests in console

$filename = 'https://openlibrary.org/api/books?bibkeys=ISBN:0385472579,LCCN:62019420&format=json';
$json = json_read($filename);
var_dump($json);

Result:

array(2) {
  'ISBN:0385472579' =>
  array(5) {
    'bib_key' =>
    string(15) "ISBN:0385472579"
    'preview' =>
    string(6) "noview"
    'thumbnail_url' =>
    string(48) "https://covers.openlibrary.org/b/id/240726-S.jpg"
    'preview_url' =>
    string(51) "https://openlibrary.org/books/OL1397864M/Zen_speaks"
    'info_url' =>
    string(51) "https://openlibrary.org/books/OL1397864M/Zen_speaks"
  }
  'LCCN:62019420' =>
  array(4) {
    'bib_key' =>
    string(13) "LCCN:62019420"
    'preview' =>
    string(6) "noview"
    'preview_url' =>
    string(69) "https://openlibrary.org/books/OL5857365M/The_adventures_of_Tom_Sawyer"
    'info_url' =>
    string(69) "https://openlibrary.org/books/OL5857365M/The_adventures_of_Tom_Sawyer"
  }
}
$filename = 'https://openlibrary.org/api/books?bibkeys=ISBN:0385472579,LCCN:62019420&format=json';
$json = json_read($filename, ['assoc'=>FALSE]);
var_dump($json);

Result:

class stdClass#2 (2) {
  public $ISBN:0385472579 =>
  class stdClass#1 (5) {
    public $bib_key =>
    string(15) "ISBN:0385472579"
    public $preview =>
    string(6) "noview"
    public $thumbnail_url =>
    string(48) "https://covers.openlibrary.org/b/id/240726-S.jpg"
    public $preview_url =>
    string(51) "https://openlibrary.org/books/OL1397864M/Zen_speaks"
    public $info_url =>
    string(51) "https://openlibrary.org/books/OL1397864M/Zen_speaks"
  }
  public $LCCN:62019420 =>
  class stdClass#3 (4) {
    public $bib_key =>
    string(13) "LCCN:62019420"
    public $preview =>
    string(6) "noview"
    public $preview_url =>
    string(69) "https://openlibrary.org/books/OL5857365M/The_adventures_of_Tom_Sawyer"
    public $info_url =>
    string(69) "https://openlibrary.org/books/OL5857365M/The_adventures_of_Tom_Sawyer"
  }
}

PHP kwargs are kweasy.