I'm building my own personal CMS right now with PHP and MySql and I'm not sure of the best way to store site-wide variables such as the site name. What would be the proper way? (If you could provide a brief explanation that would be really nice too. ;)
EDIT: The variables would need to be editable from within the CMS.
In my opinion the best way is to store it in a config file (I use .ini files, but you could easily use a file like below).
I prefer this to storing it in a MySQL database, as it allows you to still get variables (like site name, or admin email address) even if the database is down
<?php
define('SITE_NAME','My CMS');
define('SITE_URL','http://www.example.com');
define('ADMIN_EMAIL','admin#example.com');
?>
EDIT:
Create a table like this
id key value
-------------------
1 name My Cms
2 url http://www.example.com
And access it like this (at the top of every page)
<?php
$config = array();
$query = mysql_query('SELECT * FROM config');
while ($row = mysql_fetch_row($query)) {
$key = $row['key'];
$value = $row['value'];
$config[$key] = $value;
}
echo $config['name'];
?>
There is no "best" sollution for all purposes.
Here are some examples how you can do this:
Simple PHP file:
This sollution is very simple but not very flexible:
you just 'define' constants:
define('SITE_NAME', 'all about Foo');
define ('ADMIN_NAME', 'The Dude');
define ('ADMIN_MAIL', 'dude#foo.com');
Pro: very very simple.
Cons: changes require that you edit the code. Only flat keys (no tree/registry)
ini File
PHP comes with functions to parse ini files. Here is an example rom the php manual:
; 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"
you can parse this in a multi dimensional array:
$ini_array = parse_ini_file("sample.ini", true);
print_r($ini_array);
Will 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
)
)
)
Pros: lexible. A well known standart syntax.
Cons: Hard to edit in backend
See: http://php.net/manual/de/function.parse-ini-file.php
Simple SQL
Another simple aproach, but this uses a database to store the conig keys.
The table structure is:
config{config_name UNIQUE KEY VARCHAR[64] ; config_value VARCHAR[128]}
This is only pseudo code and i you need more information about PHP and SQL feel free to google it.
SQL Tree
Allows you to create a tree like structure. To archive this you use the same table structure but include '/' in your key names:
admin/name | 'The Dude'
admin/mail | 'dude#foo.com'
Then you load your COMPLETE config in an array and parse it:
function parseRawConfig($rawConfig){
$result = array();
foreach($rawConfig as $key => $value){
$nodeNames = explode('/',$key);
$nodeCount = count($nodes);
$node = $result;
for($i=1;$i<$nodeCount;$i++){
if(!array_key_exists($nodeNames[$i],$node)){
$node[$nodeNames[$i]] = array();
}
}
$node[$nodeNames[$nodeCount-1]] = $value;
}
return $result;
}
Then you can access your keys like this:
echo $config['admin']['mail'];
This makes it easy to generate a nice tree view for your backend, but to save the changes you will have to 'reconstruct' the original path.
Pro: structured hierarchical data.
Con: Complicated, hard to implement, requires db connection
XML
Write all your conig settings in a xml file.
You can either use a app specific xml or a generic registry like style:
<cmsConf>
<site>all about foo</site>
<admin>
<name>The Dude</name>
<mail>dude#foo.com</mail>
</admin>
</cmsConf>
<reg>
<key name="site">all about foo</key>
<node name="admin">
<key name="name">The Dude</key>
<key name="mail">dude#foo.com</key>
</node>
</reg>
PHP provides plenty of classes and functions for reading/writting xml, and using xml makes it really easy to create interfaces to other applications.
The pros/cons of xml are a huge topic. To huge to sum them up in a few sentences.
IMPORTANT
These are just some very simple examples and if you choose one I recommend you dig deeper into the topic!
My fav is XML ;)
a configuration file in whatever format you like.
I usually create one table with all config variables. Every column will get a clear name of what its setting means. Then I use the following query to store it in a single array for easy use.
<?php
$result = mysql_query("SELECT * FROM configtable WHERE siteID=$siteID LIMIT 1");
$siteConfig = mysql_fetch_assoc($result);
?>
This way it is very easy to add new config parameters, you only need to add a column to the configtable and you can directly use the value in your site. Plus you only have one variable so an accidental reuse of a config variable wont happen.
Related
Everyone, hello!
I'm currently trying to write to an .ini file from PHP, and I'm using Teoman Soygul's answer and code from here: How to read and write to an ini file with PHP
This works out great, although, when I save the data to it, it shows up strange in my .ini:
[Server] = ""
p_ip = "192.168.10.100"
p_port = 80
p_password = 1234
[Variable] = ""
string1_find = "Caution"
Most notably it also seems to see attempt to give the categories Server and Variable an empty value. Also, sometimes it saves the variable between consistency and sometimes not. How come there is no consistency here?
The code I'm using to find/post in PHP is this:
...
$a=array("[Server]"=>'',"p_ip"=>$_POST['pip'],"p_port"=>$_POST['pport'], "p_password"=>$_POST['pass'],
"[Variable]"=>'',"string1_find"=>$_POST['string1_find'],
...
If anyone could point me into the right direction, that would really be appreciated. Thank you!
You are not using right, you should be passing a multidimentional array instead:
$data = array(
'Server' => array(
'p_ip' => '192.168.10.100',
'p_port' => 80,
'p_password' => 1234,
),
'Variable' => array(
'string1_find' => 'Caution'
)
);
//now call the ini function from Soygul's answer
write_php_ini($data, 'file.ini');
Here is my output:
[Server]
p_ip = "192.168.10.100"
p_port = 80
p_password = 1234
[Variable]
string1_find = "Caution"
Notice that you need to create an extra array per new section and then you can start listing your custom definitions.
I have this odd problem, I have to read XML structure stored on a mySQL database, one like this:
<geocercaPoligonal>
<nombreGeocerca>Kennedy</nombreGeocerca>
<longitud>-79.89726930856705</longitud>
<latitud>-2.1599807309506396</latitud>
<longitud>-79.9029341340065</longitud>
<latitud>-2.172760363061884</latitud>
</geocercaPoligonal>
When I get the query on PHP (using PDO Fetch:Assoc) and output the result, it results on this:
' Kennedy -79.89726930856705 -2.1599807309506396 -2.172760363061884 -79.9029341340065 '
All tags are omitted and this leads to error when reading this data, I must get values from specific tags, I get a XML load string
The DB::getRow its a prepare/execute PDO function
$geocerca = DB::getRow('SELECT * FROM GeocercasxUsuario WHERE IdGeocerca = :IdGeocerca', ['IdGeocerca' => $IdGeocerca]);
$infoXML = simplexml_load_string($geocerca['InformacionGeografica']);
//The field from the database with the XML string
It gets this info
SimpleXMLElement::__set_state(array(
'nombreGeocerca' => 'Kennedy',
'longitud' => array(
0 => '-79.89726930856705',
1 => '-2.172760363061884',
),
'latitud' => array(
0 => '-2.1599807309506396',
1 => '-79.9029341340065',
),
))
It doesn't read the XML structure properly, how could I fix this? It's something from PHP or mySQL?
What in particular is not being read properly? The SimpleXML object contains all the data from your XML.
You can convert the object into an array so it is easier to work with as follows:
$json_string = json_encode($infoXML);
$result_array = json_decode($json_string, TRUE);
After trying some things, I switched my select statement to get specific XML data like this:
Instead of
SELECT * FROM GeocercasxUsuario WHERE IdUsuario = n (just for example)
I changed it to
SELECT ExtractValue(InformacionGeografica,'/geocercaPoligonal/latitud') AS Latitud, ExtractValue(InformacionGeografica,'/geocercaPoligonal/longitud') AS Longitud, ...(other fields)... FROM GeocercasxUsuario WHERE IdUsuario = n
This way I get an XML array of the values of each tag, pretty useful if you only want inner values :)
Below is my Save Configuration file:
<?php
require_once 'Config/Lite.php';
$config = new Config_Lite();
$config->read('/var/www/html/svnmanager/Config/testing');
$config->set('/lol', 'user', 'JohnDoe')
->set('/lol', 'password', 'lemo')
->set('db2', 'user', '');
// set with ArrayAccess
$config['general'] = array('lang' => 'fr');
echo $config;
$config->save();
?>
and following is the output:
debug = ""
[db]
user = "JohnDoe"
password = "d0g1tcVs$HgIn1"
[db2]
user = ""
password = "d0g1tcVs$HgIn1"
[general]
lang = "fr"
[/lol]
user = "JohnDoe"
password = "ddada"
How do remove the double qoutes when saving the file?
for example:
[/lol]
user = JohnDoe
password = ddada
Add
$config->setQuoteStrings(false);
before saving it to the file
I'm going to start off with a rant: You are always better off using FLOSS libraries as intended/documented rather than hacking them to do what you want, if at all possible - even when the code is the only documentation available. For example, if a new version of Config_Lite comes out and you upgrade to that, you'll have "lost" your fixes.
(And, as if to prove my point, version 0.2.0 was released today at http://pear.php.net/package/Config_Lite/download/0.2.0)
To be more specific to answering your question, you need to call the setQuoteStrings method before you either explicitly save the .ini file output to a file using the write method or do anything that treats $config as a string value.
Typically, I'd do things in this order:
Create the [config] object first.
Set whatever options applicable (such as turning off quoted strings in this case)
Call whatever other methods as required (e.g. set values to sections etc)
Use resultant object (e.g. save the .ini file)
tl;dr:
$config = ....
$confg->setQuoteStrings(false);
$config->set(...);
echo $config;
$config->save();
Found myself a solution. You need to change the protected $quoteStrings = true; to protected $quoteStrings = false; in your Lite.php file :)
I think example will be much better than loooong description :)
Let's assume we have an array of arrays:
("Server1", "Server_1", "Main Server", "192.168.0.3")
("Server_1", "VIP Server", "Main Server")
("Server_2", "192.168.0.4")
("192.168.0.3", "192.168.0.5")
("Server_2", "Backup")
Each line contains strings which are synonyms. And as a result of processing of this array I want to get this:
("Server1", "Server_1", "Main Server", "192.168.0.3", "VIP Server", "192.168.0.5")
("Server_2", "192.168.0.4", "Backup")
So I think I need a kind of recursive algorithm. Programming language actually doesn't matter — I need only a little help with idea in general. I'm going to use php or python.
Thank you!
This problem can be reduced to a problem in graph theory where you find all groups of connected nodes in a graph.
An efficient way to solve this problem is doing a "flood fill" algorithm, which is essentially a recursive breath first search. This wikipedia entry describes the flood fill algorithm and how it applies to solving the problem of finding connected regions of a graph.
To see how the original question can be made into a question on graphs: make each entry (e.g. "Server1", "Server_1", etc.) a node on a graph. Connect nodes with edges if and only if they are synonyms. A matrix data structure is particularly appropriate for keeping track of the edges, provided you have enough memory. Otherwise a sparse data structure like a map will work, especially since the number of synonyms will likely be limited.
Server1 is Node #0
Server_1 is Node #1
Server_2 is Node #2
Then edge[0][1] = edge[1][0] = 1, indicated that there is an edge between nodes #0 and #1 ( which means that they are synonyms ). While edge[0][2] = edge[2][0] = 0, indicating that Server1 and Server_2 are not synonyms.
Complexity Analysis
Creating this data structure is pretty efficient because a single linear pass with a lookup of the mapping of strings to node numbers is enough to crate it. If you store the mapping of strings to node numbers in a dictionary then this would be a O(n log n) step.
Doing the flood fill is O(n), you only visit each node in the graph once. So, the algorithm in all is O(n log n).
Introduce integer marking, which indicates synonym groups. On start one marks all words with different marks from 1 to N.
Then search trough your collection and if you find two words with indexes i and j are synonym, then remark all of words with marking i and j with lesser number of both. After N iteration you get all groups of synonyms.
It is some dirty and not throughly efficient solution, I believe one can get more performance with union-find structures.
Edit: This probably is NOT the most efficient way of solving your problem. If you are interested in max performance (e.g., if you have millions of values), you might be interested in writing more complex algorithm.
PHP, seems to be working (at least with data from given example):
$data = array(
array("Server1", "Server_1", "Main Server", "192.168.0.3"),
array("Server_1", "VIP Server", "Main Server"),
array("Server_2", "192.168.0.4"),
array("192.168.0.3", "192.168.0.5"),
array("Server_2", "Backup"),
);
do {
$foundSynonyms = false;
foreach ( $data as $firstKey => $firstValue ) {
foreach ( $data as $secondKey => $secondValue ) {
if ( $firstKey === $secondKey ) {
continue;
}
if ( array_intersect($firstValue, $secondValue) ) {
$data[$firstKey] = array_unique(array_merge($firstValue, $secondValue));
unset($data[$secondKey]);
$foundSynonyms = true;
break 2; // outer foreach
}
}
}
} while ( $foundSynonyms );
print_r($data);
Output:
Array
(
[0] => Array
(
[0] => Server1
[1] => Server_1
[2] => Main Server
[3] => 192.168.0.3
[4] => VIP Server
[6] => 192.168.0.5
)
[2] => Array
(
[0] => Server_2
[1] => 192.168.0.4
[3] => Backup
)
)
This would yield lower complexity then the PHP example (Python 3):
a = [set(("Server1", "Server_1", "Main Server", "192.168.0.3")),
set(("Server_1", "VIP Server", "Main Server")),
set(("Server_2", "192.168.0.4")),
set(("192.168.0.3", "192.168.0.5")),
set(("Server_2", "Backup"))]
b = {}
c = set()
for s in a:
full_s = s.copy()
for d in s:
if b.get(d):
full_s.update(b[d])
for d in full_s:
b[d] = full_s
c.add(frozenset(full_s))
for k,v in b.items():
fsv = frozenset(v)
if fsv in c:
print(list(fsv))
c.remove(fsv)
I was looking for a solution in python, so I came up with this solution. If you are willing to use python data structures like sets
you can use this solution too. "It's so simple a cave man can use it."
Simply this is the logic behind it.
foreach set_of_values in value_collection:
alreadyInSynonymSet = false
foreach synonym_set in synonym_collection:
if set_of_values in synonym_set:
alreadyInSynonymSet = true
synonym_set = synonym_set.union(set_of_values)
if not alreadyInSynonymSet:
synonym_collection.append(set(set_of_values))
vals = (
("Server1", "Server_1", "Main Server", "192.168.0.3"),
("Server_1", "VIP Server", "Main Server"),
("Server_2", "192.168.0.4"),
("192.168.0.3", "192.168.0.5"),
("Server_2", "Backup"),
)
value_sets = (set(value_tup) for value_tup in vals)
synonym_collection = []
for value_set in value_sets:
isConnected = False # If connected to a term in the graph
print(f'\nCurrent Value Set: {value_set}')
for synonyms in synonym_collection:
# IF two sets are disjoint, they don't have common elements
if not set(synonyms).isdisjoint(value_set):
isConnected = True
synonyms |= value_set # Appending elements of new value_set to synonymous set
break
# If it's not related to any other term, create a new set
if not isConnected:
print ('Value set not in graph, adding to graph...')
synonym_collection.append(value_set)
print('\nDone, Completed Graphing Synonyms')
print(synonym_collection)
This will have a result of
Current Value Set: {'Server1', 'Main Server', '192.168.0.3', 'Server_1'}
Value set not in graph, adding to graph...
Current Value Set: {'VIP Server', 'Main Server', 'Server_1'}
Current Value Set: {'192.168.0.4', 'Server_2'}
Value set not in graph, adding to graph...
Current Value Set: {'192.168.0.3', '192.168.0.5'}
Current Value Set: {'Server_2', 'Backup'}
Done, Completed Graphing Synonyms
[{'VIP Server', 'Main Server', '192.168.0.3', '192.168.0.5', 'Server1', 'Server_1'}, {'192.168.0.4', 'Server_2', 'Backup'}]
I find print_r in PHP extremely useful, but wonder if there is anything remotely equivalent in Perl?
Note #tchrist recommends Data::Dump over Data::Dumper. I wasn't aware of it, but from the looks of it, seems like it's both far easier to use and producing better looking and easier to interpret results.
Data::Dumper :
A snippet of the examples shown in the above link.
use Data::Dumper;
package Foo;
sub new {bless {'a' => 1, 'b' => sub { return "foo" }}, $_[0]};
package Fuz; # a weird REF-REF-SCALAR object
sub new {bless \($_ = \ 'fu\'z'), $_[0]};
package main;
$foo = Foo->new;
$fuz = Fuz->new;
$boo = [ 1, [], "abcd", \*foo,
{1 => 'a', 023 => 'b', 0x45 => 'c'},
\\"p\q\'r", $foo, $fuz];
########
# simple usage
########
$bar = eval(Dumper($boo));
print($#) if $#;
print Dumper($boo), Dumper($bar); # pretty print (no array indices)
$Data::Dumper::Terse = 1; # don't output names where feasible
$Data::Dumper::Indent = 0; # turn off all pretty print
print Dumper($boo), "\n";
$Data::Dumper::Indent = 1; # mild pretty print
print Dumper($boo);
$Data::Dumper::Indent = 3; # pretty print with array indices
print Dumper($boo);
$Data::Dumper::Useqq = 1; # print strings in double quotes
print Dumper($boo);
As usually with Perl, you might prefer alternative solutions to the venerable Data::Dumper:
Data::Dump::Streamer has a terser output than Data::Dumper, and can also serialize some data better than Data::Dumper,
YAML (or Yaml::Syck, or an other YAML module) generate the data in YAML, which is quite legible.
And of course with the debugger, you can display any variable with the 'x' command. I particularly like the form 'x 2 $complex_structure' where 2 (or any number) tells the debugger to display only 2 levels of nested data.
An alternative to Data::Dumper that does not produce valid Perl code but instead a more skimmable format (same as the x command of the Perl debugger) is Dumpvalue. It also consumes a lot less memory.
As well, there is Data::Dump::Streamer, which is more accurate in various edge and corner cases than Data::Dumper is.
I use Data::Dump, it's output is a bit cleaner than Data::Dumper's (no $VAR1), it provides quick shortcuts and it also tries to DTRT, i.e. it will print to STDERR when called in void context and return the dump string when not.
I went looking for the same thing and found this lovely little Perl function, explicitly meant to generate results like print_r().
The author of the script was asking your exact question in a forum here.
print objectToString($json_data);
Gives this output:
HASH {
time => 1233173875
error => 0
node => HASH {
vid => 1011
moderate => 0
field_datestring => ARRAY {
HASH {
value => August 30, 1979
}
}
field_tagged_persons => ARRAY {
HASH {
nid => undef
}
}
...and so on...