# Reading and Writing Configuration

https://dev.wisecp.com/es/reading-and-writing-configuration

Settings live in two places with different rules. PHP files are loaded on demand, and database rows hold values that do not belong in a file.

## Overview

A configuration file is a PHP file that returns an array. Reading one is a path expression rather than a file operation. You name the file, then walk into the array with slashes. The file is loaded the first time something asks for it.

The second store is a table, for values that are per-installation state rather than configuration a developer ships. Both are reached through the same class with different method names, and choosing the wrong one is the mistake worth avoiding.

## Reference

### File-Based Settings

```php
public static function get($arg = null);
public static function set($key, $values, $merge = false): array|false;
public static function save($name = '', $data = []): bool;
public static function merge($arg = []): bool;
```

- **get($arg)**: The first path segment is the file, the rest walks into the array it returns. Any segment that is missing answers `false`, so a stored false and an unset key look the same. With no argument you get every file loaded so far, which is not every file that exists.
- **set($key, $values, $merge)**: Changes the loaded copy for this request and writes nothing. First argument is the file name, second the values. Answers `[$key => $merged]`, or `false` when the values are empty or the file does not exist.
- **save($name, $data)**: Writes the array back to its file and refreshes the loaded copy. A later read in the same request sees the new value. With no second argument it saves whatever is currently in memory for that file. Answers whether the write succeeded.
- **merge($arg)**: Folds whole entries into the loaded set for this request, so its keys are file names, not settings. Useful for a virtual file that has no counterpart on disk.
- **coremio/configuration**: Where the files live, one per name: `general`, `options`, `theme`, `crypt`, `sms` and the rest.

### What save Expects and Writes

A file is a returned array and may be wrapped in its own name or not. Both shapes are accepted. If the file on disk is wrapped and your data is not, it is wrapped for you before writing.

```php
<?php
return [
    'cache'    => 'on',
    'rich-url' => 'on',
];
```

```php
Config::save("general", ['cache' => 'on', 'rich-url' => 'on']);
Config::save("general", ['general' => ['cache' => 'on', 'rich-url' => 'on']]);
```

The source is generated by the array exporter and written through the file manager. That manager invalidates the compiled copy of the file it replaced. A raw write has to do that itself.

### Database-Backed Settings

```php
public static function getd($name = '');
public static function setd($name = '', $content = '');
public static function deld($name = '');
```

- **getd($name)**: Reads one named row. A stored value that is valid JSON comes back decoded. An array is an array again, and a stored number is a number. That holds only while the decoded value is truthy. A stored zero, an empty array and an empty object come back as the written text: `0`, `[]` and `{}`. A row that does not exist answers `null`.
- **setd($name, $content)**: Inserts or updates by name. An array is encoded for you; anything else is stored as given. A new row is stamped with a created and an updated time; an update touches the updated one only.
- **deld($name)**: Removes the row. There is no soft delete, and a later read answers `null`.

### Choosing a Store

| Value | Store | Why |
| --- | --- | --- |
| A switch an operator sets once | File | Read on almost every request; a file read is cheaper than a query |
| Credentials for a service | File, in the module's own file | Travels with the module and is not scattered across tables |
| State the application updates itself | Database | Written often, and a file write is not the right tool for that |
| Something one operator prefers | Neither | Per-person preference belongs to that person, not to the installation |

## Example

Read the file, change the value, write it, and read it back in the same request.

```php
public function save_general(Operation $operation): bool
{
    $operation->demo();

    $data          = Config::get("general");                  // the whole file
    $data['cache'] = Filter::init("POST/cache", "route") === 'on' ? 'on' : 'off';

    if (!Config::save("general", $data))
        throw new Exception(Language::gc("error/settings-not-written"));

    // Same request, and the value is already the new one.
    $now = Config::get("general/cache");

    // State the application owns goes to the row store instead.
    Config::setd("last_settings_change", ['at' => date('c'), 'cache' => $now]);

    return $operation->output(['status' => "successful", 'cache' => $now]);
}
```

```php
$state = Config::getd("last_settings_change");   // array, decoded for you

if ($state === null) {
    // Never written, or removed. Not the same as "written as empty".
}
```

## Pitfalls

> **The third argument of set does the opposite of its name**
> 
> Left at its default it replaces recursively, so siblings inside a nested key survive. Passing true merges at the top level only, which replaces a nested array whole and drops the keys you did not mention. Changing one key under a group is the default behaviour, not the merging one.

> **Changing a value in memory does not persist it**
> 
> The setter and the saver are separate on purpose, so a request can override a setting for itself without rewriting the file. If a change has to survive the request, save it. Saving with no data argument commits whatever the setter left in memory.

> **A configuration file is compiled PHP**
> 
> The server can keep a compiled copy and go on serving it after the file changes. A save then looks successful, and the old value comes back on the next page. Write through the file manager, which invalidates that copy; a raw write has to do it itself.

> **Configuration is installation-wide**
> 
> There is one value for everyone. Storing a personal preference here means the first operator who changes it changes it for every other operator too. That kind of value belongs in a cookie or on the user record.

## Related Articles

- [Utility Helpers](https://dev.wisecp.com/en/utility-helpers)
- [Module Configuration](https://dev.wisecp.com/en/module-configuration)
- [Caching](https://dev.wisecp.com/en/caching)
- [Setting Up a Development Environment](https://dev.wisecp.com/en/setting-up-a-development-environment)
