What is the best way to persist PHP application setings? - php

I have a small application that I'm developing, that I may want to give/sell to others. I want to persist some settings, and create an admin interface to modify them. What would be the best way to store them away? A DB table seems like overkill for the 10-20 settings I'll have, and I want the retrieval of these settings to be as fast as possible. Is a flat file another viable option? What are the pitfalls associated with using a flat file? What would be the fastest/easiest way to interface with a flat file storing multiple keys and values?

I often use PHP's parse_ini_file for this.
So if you write an .ini file with this:
; This is a sample configuration file
; Comments start with ';', as in php.ini
[first_section]
one = 1
five = 5
animal = BIRD
[second_section]
path = "/usr/local/bin"
URL = "http://www.example.com/~username"
[third_section]
phpversion[] = "5.0"
phpversion[] = "5.1"
phpversion[] = "5.2"
phpversion[] = "5.3"
And read it in with this PHP code:
define('BIRD', 'Dodo bird');
$ini_array = parse_ini_file("sample.ini", true);
print_r($ini_array);
You will get this output:
Array
(
[first_section] => Array
(
[one] => 1
[five] => 5
[animal] => Dodo bird
)
[second_section] => Array
(
[path] => /usr/local/bin
[URL] => http://www.example.com/~username
)
[third_section] => Array
(
[phpversion] => Array
(
[0] => 5.0
[1] => 5.1
[2] => 5.2
[3] => 5.3
)
)
)

PHP has functions to read .ini files.
http://is2.php.net/manual/en/function.parse-ini-file.php

It really depends on the type of "settings" you want to store. Are they "bootstrap" settings like DB host, port, and login? Or are they application settings specifically for your application?
The problem with letting an admin interface write a file on the file system is the permissions needed in order to write to the file. Anytime you open up the web server to write files, you increase the possibility that an error in your code could allow severe privilege escalation.
Databases are designed to allow reads and writes without introducing the potential system security risks.
We use "generated" PHP files to store static configuration data in (like database access info). It is generated by a utility script by a user on the command line. After that, all non-static information is stored in a database table. The database table, in turn, is easy to update from an Admin area. It is easy to extend and upgrade as you upgrade your applicecation.
It's also much easier to centralize the "data" that needs backed up in one place.
May I suggest using memcached or something similar to speed it up?
Just a couple thoughts...

I think XML via SimpleXMLElement is very useful for that kind of thing.
The configuration (config.xml):
<config version="1">
<foo>value</foo>
<bar>
<baz>Boo</baz>
</bar>
</config>
The code to read it:
$config = simplexml_load_file('config.xml');
$version = (int) $config['version'];
$foo = (string) $config->foo;
$baz = (string) $config->bar->baz;
Code to write it to a file:
$config = new SimpleXMLElement('<config version="1"/>');
$config->foo = 'value';
$config->bar->baz = 'Boo';
$config->asXML('config.xml');
The main pitfall of using flat files is that it could lead to possible data corruption on script crash or concurrent editing.

Consider a PHP object (or array) serialized to a file. No parsing code to write, fast enough, extensible. The one caveat is to think of a strategy to "upgrade" the settings file as your code grows to support further settings.

I simply use a PHP array for config files.
<?php
return array
(
'user' => 'name',
'pass' => 'word',
'one_day' => 60 * 60 * 24
);
?>
Some of the advantages are that, like morendil mentioned, no parsing required, its as fast as it gets, you can store all kinds of complex variables / equations and you can do the following:
<?php
$config = include 'path/to/file.php';
?>

Related

phpmyadmin export naming templates / variables

While exporting SQL from phpmyadmin these is an option to give the exported file some variables to be included in the name of the file itself ( and also save it like some sort of "template" under settings-->export) , for example :
__DB__ or
#DATABASE#
#HTTPHOST#
#TABLE# ( if exporting from a table )
#SERVER# ( for IP )
#USER# or __USER__ (not always...)
etc..
I also know of some time variables like
%F and the standard %Y,%M,%D, %m ,%s etc ( strftime ) ..
so for example #DATABASE#-#SERVER#-%F will give me a file name like :
dbname-127.0.0.1-2019-03-21.sql
I have searched for documentation on these, but could not find on the official phpmyadmin docs . I also noticed that they do not react the same on all servers .
My question is:
Are there any other variables - What is ( or where to find ) the complete list of documented variables and the correct usage format ( __x__ or #NAME# ) ?
How ( or where ) are they set on a server / user basis - and can one set / enable / disable these on own server - or even add new ones ?
The __xx__ variables exist for backward compatibility when things like that were stored in cookies. From the code:
/* Replacement mapping */
/*
* The __VAR__ ones are for backward compatibility, because user
* might still have it in cookies.
*/
$replace = array(
'#HTTP_HOST#' => $vars['http_host'],
'#SERVER#' => $vars['server_name'],
'__SERVER__' => $vars['server_name'],
'#VERBOSE#' => $vars['server_verbose'],
'#VSERVER#' => $vars['server_verbose_or_name'],
'#DATABASE#' => $vars['database'],
'__DB__' => $vars['database'],
'#TABLE#' => $vars['table'],
'__TABLE__' => $vars['table'],
'#PHPMYADMIN#' => $vars['phpmyadmin_version'])
Since there is no way to have a default set of export values for all users you could create a set for your users and store them in the pma__userconfig table in the phpmyadmin database. The values are stored in JSON format.

How can I echo a single element of this object?

I'm trying to use this php library along with Sheetsu to pull single bits of data from a Google spreadsheet for output on a web page. My php skills are minimal and spotty, I'm afraid, and so I'm missing a final crucial component.
When I set up a test file and run my query, the code dumps everything into a $collection object. If I output print_r($collection); I get this:
Sheetsu\Collection Object (
[models:Sheetsu\Collection:private] => Array (
[0] => Sheetsu\Model Object (
[id] => 2.05.1
[title] => The Mead of Poetry
[answer] => Kvasir was created from the spit of the Aesir and Vanir. He was very wise.
)
)
)
My data's there, everything's working as expected, but I've never seen a structure like that before.
How can I just echo, say, just the [answer] value? I'm not sure what syntax to use to drill into that.
Thanks!
try
$collection->get(0)
or
$collection->getFirst()
or
$collection->getAll()[0]
etc.
models is a private property, you can't access it directly
so to access answer it would be something like
$collection->get(0)->answer
Try echo $collection[0]->answer;

Store information about a directory

Other than the foldername, is there a way to get/set information about a directory to the actual folder itself?
I want to set a directory priority so folders are displayed in a certain order by assigning a number to each.
This is possible with Extended File Attributes:
https://secure.wikimedia.org/wikipedia/en/wiki/Extended_file_attributes
Extended file attributes is a file system feature that enables users to associate computer files with metadata not interpreted by the filesystem, whereas regular attributes have a purpose strictly defined by the filesystem (such as permissions or records of creation and modification times).
Try the xattr API to get/set them:
http://docs.php.net/manual/en/book.xattr.php
Example from Manual:
$file = 'my_favourite_song.wav';
xattr_set($file, 'Artist', 'Someone');
xattr_set($file, 'My ranking', 'Good');
xattr_set($file, 'Listen count', '34');
/* ... other code ... */
printf("You've played this song %d times", xattr_get($file, 'Listen count'));
You can do it for NTFS for sure: http://en.wikipedia.org/wiki/NTFS#Alternate_data_streams_.28ADS.29
Don't know if such a feature exist for *nix file systems.
Why do you want to anchor your program logic in the filesystem of the OS? That isn't a proper way to store such a information. One reason is that you leave your application domain and other programs could override your saved information.
Or if you move your application to a newer server, you may run in trouble that you cant transfer this information (e.g. as the new environment has another filesystem).
It is also bad practice to suppose a specific filesystem where your application is running.
A better way is to store this in your application (e.g. database if you need it persistent).
A simple array can do this job, with key as priority and value an array with objects of Directory for example.
It could look like this:
array(
0 => array( // highest prio
0 => DirObject,
1 => DirObject,
2 => DirObject
),
1 => array(
0 => DirObject,
1 => DirObject,
...
), ...
Then you can present your folders with an flatten function or a simple foreach. And can easily save it as serialized/jsoned string in a database.

Need more speed in PHP foreach loop

I'm doing some integrations towards MS based web applications which forces me to fetch the data to my php application via SOAP which is fine.
I got the structure of a file system in an xml which I convert to an object. All documents have an ID and it's path. To be able to place the documents in a tree view I've built some methods to calculate the documents whereabouts through the files and folder structure. This works fine until I started to try with large file lists.
What I need is a faster method (or way to do things) than a foreach loop.
The method below is the troublemaker.
/**
* Find parent id based on path
* #param array $documents
* #param string $parentPath
* #return int
*/
private function getParentId($documents, $parentPath) {
$parentId = 0;
foreach ($documents as $document) {
if ($parentPath == $document->ServerUrl) {
$parentId = $document->ID;
break;
}
}
return $parentId;
}
// With 20 documents nested in different folders this method renders in 0.00033712387084961
// With 9000 documents nested in different folders it takes 60 seconds
The array sent to the object looks like this
Array
(
[0] => testprojectDocumentLibraryObject Object
(
[ParentID] => 0
[Level] => 1
[ParentPath] => /Shared Documents
[ID] => 163
[GUID] => 505d70ea-51d7-4ef0-bf79-8e912553249e
[DocIcon] =>
[FileType] =>
[Title] => Folder1
[BaseName] => Folder1
[LinkFilename] => Folder1
[ContentType] => Folder
[FileSizeDisplay] =>
[_UIVersionString] => 1.0
[ServerUrl] => /Shared Documents/Folder1
[EncodedAbsUrl] => http://dev1.example.com/Shared%20Documents/Folder1
[Created] => 2011-10-08 20:57:47
[Modified] => 2011-10-08 20:57:47
[ModifiedBy] =>
[CreatedBy] =>
[_ModerationStatus] => 0
[WorkflowVersion] => 1
)
...
A bit bigger example of the data array is available here
http://www.trikks.com/files/testprojectDocumentLibraryObject.txt
Thanks for any help!
=== UPDATE ===
To illustrate the time different stuff takes I've added this part.
Packet downloaded in 8.5031080245972 seconds
Packet decoded in 1.2838368415833 seconds
Packet unpacked in 0.051079988479614 seconds
List data organized in 3.8216209411621 seconds
Standard properties filled in 0.46236896514893 seconds
Custom properties filled in 40.856066942215 seconds
TOTAL: This page was created in 55.231353998184 seconds!
Now, this is a custom property action that im describing, the other stuff is already somewhat optimized. The data sent from the WCF service is compressed and encoded ratio 10:1 (like 10mb uncompressed : 1mb compressed).
The current priority is to optimize the custom properties part, where the getParentId method takes 99% of the execution time!
You may see faster results by using XMLReader or expat instead of simplexml. Both of these reqd the xml sequentially and won't store the entire document in memory.
Also make sure you have the APC extension on, for the actual loop it's a big big difference. Some benchmarks on the actual loop would be nice.
Lastly, if you cannot make it faster.. rather than trying to optimize reading the large xml document, you should look into ways where this 'slowness' is not an issue. Some ideas include an asynchronous process, proper caching, etc..
Edit
Are you actually calling getParentId for every document? This just occurred to me. If you have a 1000 documents then this would imply already 1000*1000 loops. If this is truly the case, you need to rewrite your code so it becomes a single loop.
How are you populating the array in the first place? Perhaps you could arrange the items in a hierarchy of nested arrays, where each key relates to one part of the path.
e.g.
['Shared Documents']
['Folder1']
['Yet another folder']
['folderA']
['folderB']
Then in your getParentId() method, extract the various parts of the path and just search that section of data:
private function getParentId($documents, $parentPath) {
$keys = explode('/', $parentPath);
$docs = $documents;
foreach ($keys as $key) {
if (isset($docs[$key])) {
$docs = $docs[$key];
} else {
return 0;
}
}
foreach $docs as $document) {
if ($parentPath == $document->ServerUrl) {
return $document->ID;
}
}
}
I haven't fully checked that will do what you're after, but it might help set you on a helpful path.
Edit: I missed that you're not populating the array yourself initially; but doing some sort of indexing ahead of time might still save you time overall, especially if getParentId is called on the same data multiple times.
As usual this was a matter of programming design. And there are a few lessons to be learned from this.
In a file system the parent is always a folder, to speed up such a process in php you can put all the folders in a separate array with it's corresponding ID as the key and search that array when you want to find the parent of a file, instead of searching the entire file structure array!
Packet downloaded in 6.9351849555969 seconds
Packet decoded in 1.2411289215088 seconds
Packet unpacked in 0.04874587059021 seconds
List data organized in 3.7993721961975 seconds
Standard properties filled in 0.4488160610199 seconds
Custom properties filled in 0.15889382362366 seconds
This page was created in 11.578738212585 seconds!
Compare the custom properties by the one from my original post
Cheers

Best way to manage text displayed to users in PHP

Ok for sure this has been asked and answered already but i somehow can't find a proper tutorial.
I want to keep the text displayed to users somewhere else and to prevent my code from becoming too large and unreadable.
My site won't be internationalized. I just want to have some kind of file with key-value structure and get the text from there. I want to keep the text in files, not in the database as some tutorials suggest.
I found a solution which will work but i am not sure whether this is a good approach.
I am thinking of using parse_ini_file and to keep my texts in .ini file. Is there something wrong with this approach? Could you suggest something better?
I put all language data in arrays. Its easy and also we can add multi-language support
lang/en.php
<?php
return array(
'index' => 'Homepage',
'feedback' => 'Feedback'
'logout' => 'Logout from profile',
)
?>
lang/ru.php
<?php
return array(
'logout' => 'Выйти из профиля',
)
?>
Then we can load languages:
$lang = include('lang/en.php');
if(isset($_GET['lang']))
{
$lang = array_merge($lang, include('lang/ru.php'));
}
After all it $lang will look like:
Array
(
[index] => Homepage
[feedback] => Feedback
[logout] => Выйти из профиля
)
And we can very simple use it:
function __($name) {
global $lang;
return $lang[$name];
}
Somewhere in the site template:
...
<title><?=__('index')?></title>
</head>
<body>
<?=__('feedback')?>
why not use a plain text file with commas or some uncommon character to hold this data? you can read it and parse it into an array with
$file = file_get_contents("/path/to/file");
$lines = explode('\r', $file);
foreach($lines as $line) $message[substr($line, 0, strpos($line, ','))] = substr($line, strpos($line, ','));
then you should have an array like $messages[3] = "No soup for you!";
the file might look like:
1,The site is down.
2,Try again.
3,No soup for you!
4,Signs point to yes.
(I probably have some of the arguments misplaced in those functions - i always forget which is the needle and which the haystack.)
You can process your data in a script. In this script, you call a certain source (e.g. the ini file you suggest). Then you use a template engine. For this engine, you point towards a template file and give the template all the variables.
The template generates the html and inserts the variables at the right place. This way, you keep you php (business logic) code clean, away from the presentation (the template). Also you can manage the variables in one file (ini/xml but this can be something completely different).
For template engines, Smarty is the most known of all. There are also pure php-based template systems, just Google for them to find one that suits your needs.
I do like this:
$defaultLang = array('Home','Logout',etc)
$otherLang=array( 'ru' => array('Home_in_ru','logout_in_ru',etc);
you translate like this:
echo translate('Home');
function is:
function translate($msg) {
if ($_GET['lang']=='en')
return $msg;
return $otherLang[$_GET['lang']][array_search($msg,$defaultLang)];
}
// Note the function is simplified up there
As you can see the default case deosnt' need to load anything or do any operation, the function just returns back the argument passed
i like the answer with the lang/en.php file. but instead of a file for each language, i use a file for each web page (or class, etc). this keeps file sizes lower and i create a 3D array:
`return array( "EN" => array( "title" => "Welcome - Good Morning", ...),
"TG" => array( "title" => "Mabuhay - Magandang Umaga Po", ...)
);'
Real easy to add new language strings too...
This makes it real easy for language translation contractors since they can see the native language in close proximity to the foreign in 1 editor,,,

Categories