Site settings - using them throughout app


#1

If you have a Slim website (not just using for API), what do you find is the best way to access site settings? In a previous instance of my project, I created a middleware that would get the settings from the db but that’s a lot of queries. Better way?

One idea I had was writing the settings out to a file on save.


Local vs Remote Settings
#2

What type of site settings are you referring to?


#3

site name, email messages, emails, etc.


#4

I’m not certain if I’m following… but for my site name I’m storing that within a Twig include. So my “master” template will include another template which is just a set of variable definitions.

{% set siteName = 'My Awesome Site' %} {% set supportEmail = 'support@example.com' %}

In this manner, Twig also caches them upon first use. Is this what you mean?


#5

I guess my thing is that I want to be able to update it without having to go into the code.


#6

hmmmm… I guess I look at those changes as changes that I do want to be under version control. Otherwise, if they are actual “settings” and thus might vary between production, staging, and dev environments… I’ll put them into my settings.php which is not under version control. I’d then load/fetch them from the container via other objects. (Like keys to external services).


#7

hm… ok. Thanks. Maybe I’ll just throw them in my settings file.


#8

Hey @shavonn - similar to what @tflight has suggested, I use a config system which is loosely based around ZF2 and the other larger frameworks. Basically, create a config folder, with *.global.php and *.local.php settings files which are then read into the application on start and accessed from the container, so can be injected anywhere in your app. Something like the following should work:

public function autoloadConfigFiles()
    {
        $globalConfig = [];
        $localConfig  = [];

        $globalFiles = glob('config/autoload/*.global.php');
        $localFiles  = glob('config/autoload/*.local.php');

        foreach ($globalFiles as $globalFile) {
            $globalConfig = array_merge_recursive($globalConfig, require($globalFile));
        }

        if (isset($localConfig)) {
            foreach ($localFiles as $localFile) {
                $localConfig = array_merge_recursive($localConfig, require($localFile));
            }
        }

        $config = array_replace_recursive($globalConfig, $localConfig);

        // Set the config:
        return $config;
    }

That way you can overwrite global configs files (like production files etc) with local ones when you’re devving and vice versa. If you then do something like this:

// You can access the config from anywhere really
$config     = $someService->autoloadConfigFiles();

/** @var \Slim\App $slimApp */
$slimApp = new \Slim\App($config);

Alternatively, you can add it to a container in your dependencies or set up, or any other way you want to, but that gives you the ability to create multiple config files for separate parts of the application etc and then inject them / access them / use them from anywhere if they are in a container.

Hope that helps :slight_smile:


#9

I have a site_settings table that is loaded with one DB call into an array. i.e. The load settings method runs SQL along the lines of select key_name, value from site_settings which is reasonably quick

The load settings functionality is called from within a container factory so that it doesn’t run unless needed. i.e. the first time that the app does $container->get('site_settings')['some_setting'], the database query runs precisely once.

Note that for. some other configuration (e.g. db configuration), I use a config.php with a local.config.php override as documented in https://akrabat.com/configuration-in-slim-framework/.


#10

Most of my projects are not that large. I will have a settings.php file that I load when I instantiate Slim. I then pass the settings via Dependancy Injection to any Controller/Service that requires them.

index.php

$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);

dependencies.php

$container['EmailService'] = function($c) {
    return new EmailService(
       $c->Mandrill,
       $c->settings['EmailSettings']
    );
};

#11

The load settings functionality is called from within a container factory so that it doesn’t run unless needed. i.e. the first time that the app does $container->get(‘site_settings’)[‘some_setting’], the database query runs precisely once.

This is quite interesting. Could you share a code snipped for this? Thanks :slight_smile:


#12

Thanks so much everyone. I think my plan is to create a settings form and write to a file on save that I can read in. Thanks!


#13

It’s not very interesting:

$container['site_settings'] = function ($c) {
    $settingsTable = $c->get(SettingsTable::class);
    return $settingsTable->fetchAll();
}

#14

Simple but effective. Thank you!


#15

Others frameworks uses “Environment Variables” and I think that is a good solution.


#16

Just thought I’d share… I ended up making a form to write to a .env file.

	public function settings($request, $response, $args)
	{
		$settingKeys = array (
			'DB_DRIVER',
			'DB_HOST',
			'DB_NAME',
			'DB_USERNAME',
			'DB_PASSWORD',
			'DB_CHARSET',
			'DB_COLLATION',
			'DB_PORT',
			'GOOGLE_API',
			'ROOT_PATH',
			'PUBLIC_PATH',
			'UPLOAD_FILE_PATH',
			'ADMIN_FILE_PATH',
			'MAIL_HOST',
			'MAIL_AUTH',
			'MAIL_USERNAME',
			'MAIL_PASSWORD',
			'MAIL_SMPTSECURE',
			'MAIL_PORT',
			'MAIL_ISHTML',
			'MAIL_FROM',
			'JWT_SECRET',
		);

		$settings = array();

		foreach ($settingKeys as $key) {
			$settings[$key] = getenv($key);
		}

		return $this->view->render($response, 'views/admin/settings.twig', array('settings' => $settings));
	}

	public function saveSettings($request, $response, $args)
	{
		$posted = $request->getParams();
		unset($posted['csrf_name']);
		unset($posted['csrf_value']);

		try {
			$file = getenv('ROOT_PATH') . '/.env';
			$settings = '';

			foreach ($posted as $key => $value) {
				$settings .= $key . " = '" . $value . "'\n";
			}
			
			file_put_contents($file, $settings);
			$this->flash->addMessage('ok', 'Settings have been updated!');
		} catch (Exception $e) {
			$this->flash->addMessage('error', $e->getMessage());
		}

		return $response->withRedirect($this->router->pathFor('admin.settings.edit'));
	}

and then in my settings twig:

{% block content %}
<form action={{ path_for('admin.settings.save') }} method="post">
	{% for key, value in settings %}
	<div class="form-group">
		<label for="{{key}}">{{key}}</label>
		<input type="text" name="{{key}}" class="form-control" value="{{value}}" />
	</div>
	{% endfor %}
	<button type="submit" class="btn btn-default">Update</button>
	{{ csrf.field | raw }}
</form>
{% endblock %}

#17

I have created a nice little class to load this for me:

class Config
{
    public static function load($directory)
    {
        $config = [];
        $path = realpath($directory);
        $items = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST);
        foreach ($items as $item)
        {
            if (!is_dir($item))
            {
                $parts = pathinfo($item);
                array_push($config, [$parts['filename] => require $item);
            }
        }
    }
}

And I call it by passing the directory. An array is returned so I name my config according to what it is: app.php / db.php / etc etc. All of these return arrays


#18

Revisiting this… Do you cache that setting or save it in a session? How do you keep from having to query your settings again?


#19

I cache to a PHP file via var_export and then rely on opcache.