Processing an array into multiple files correctly with PHP - php

All right so I have a problem and I'm looking for the best way to model it.
At the moment I have an array called $lang and it is defined in multiple files. It is initialized as so in each file: $lang = array_merge($lang, array( "Key" => "Value", )); so that when multiple files are included on a page, the $lang array contains all keys and values from their respective files into one big array.
Now I want to build a front-end where it displays all of the attributes from the array, a user can change the attributes, and save them. At the moment I am including them as so:
foreach(glob("language/*.php") as $filename){
include $filename;
}
I display them all fine, but when I want to re-submit them as a form, I don't know how to specify which Key => Value belonged to which file, as they were all merged when they were included.
Is there some clever way I can differentiate which file a certain Key => Value belonged to as I have set it up right now, or should I step back and set up the model differently?

sounds like you need to store the filename in each array using a multidimensional array, eg
array("filename"=>array("Key" => "Value")

Perhaps you could make some kind of language key to filename mapping:
$map = array();
foreach(glob("language/*.php") as $filename){
$lang = array();
include $filename;
foreach($lang as $k=>$v){
$map[$k] = $filename;
}
}
EDIT:
But it's probably a better idea to refactor your code and use some of the other answers suggestions.

your input fields could look something like this, with the filename in them:
<input type="text" name="data[file1][key1]" value="new value" />
<input type="text" name="data[file2][key1]" value="new value" />
That way you can differentiate them and write the files back in different files.

The two ways I can think of this are sorting by file:
array(
'filename' => array(
'key' => 'value',
)
)
or sorting by key:
array(
'key' => array(
'value',
'filename'
)
)
It really depends on how you want to deal with it later. I don't think there's a "correct" answer here.

The main problem I see with your code is that you hardencode the $lang variable plus some functional magic inside the data-file(s). Consider the following instead to differ more between data and logic:
language/sample.php:
return array("key" => "value");
loading script:
foreach(glob("language/*.php") as $filename){
$filedata = include($filename);
$lang[$filename] = $filedata;
# - OR -
$lang = array_merge($lang, $filedata);
}
You can now use the language data-files more modular because they are not bound to $lang any longer. For example to display an editor per file. Or to add the needed meta-data as well.

Related

Add values to complex array

I have a modules array for my software, and I need to know how I would add to the array through PHP without the user having to directly add it (i.e. automated). I can do this with a simple array that looks like this:
$array = array( 'key'=>'value' );
however, my array looks like this:
$modules = array('Forums'=>array('file'=>)...
So, how could I add values to the array with a PHP function where say, a user clicks a button to add a new module, and all it asks for is the name of the module and the filename?
foreach($modules as $name => $module) if ($module['enabled']) {
require_once('include/scripts/'.$module['file']);
}
If the above were used to load the module, would #Darren's comment still apply?
What I commented is how to do it. I assume you're storing this array in a cache/session where it's semi-persistent right? What you want to do is append the item to the array. Say your array looks like this:
$modules = array(
'Forums' => array('file' => 'link/to/file.php', 'enabled' => TRUE),
.....etc
);
All you need to do is add it to the array:
$modules['Example_Module'] = array('file' => 'link/to/this/module', 'enabled' => TRUE);
Which will allow you to continue using that include code block you have.
See this: Example
It sticks to the structure you require.

Calling many Variables stored in outside file

The code I had help with the last couple weeks works great. The problem is it's creating way more needed includes and external files than I first thought and getting to be a challenge to keep track of.
I was told to use MySQL. That would be fine if the data was going to be used over again. The data is only used long enough to build the pages, print to pdf and then it's no longer needed and the files are deleted.
I have three templates that are used to create all the needed pages. Only the data is different but never the same to allow it to be saved beyond it's use.
The problem I started having is when 30+ pages are loaded into a single browser window so it can get processed to pdf, this is calling a few hundred includes and some are being missed. When each page is called by itself it all loads fine.
The other thing I can think of is to try and get the variables belonging to each page in it's own single file and have the page access that file. When I call the file with include "file.php"; it just prints everything to the screen and not where they are needed. That way each file would have 10 - 15 variables in it for each page This would eliminate over 400 external files down to 1+ images for each page.
Is putting them all in one separate file and then called possible?
I hope I explained this correctly.
Thanks in advance.
// What I would like in one file.
$item1 = "Data for Item one";
$photo1 = "img src string to image for item 1";
etc...
$item12 = "Data for Item 12";
$photo12 = "img src string to image for item 12";
This would then call the items in the proper location of the page.
echo "$item1";
echo "photo1";
etc...
echo "$item12";
echo "photo12";
You can use a MySQL database to store information like that. (Settings, etc.)
But you can also use arrays for this which is probably more advisable.
Solution with arrays
An array gives you the possibility to easily store and manage data of a similar type.
You create a new array like this:
$settings = array();
To store a value in it, you have several options:
name it as an integer (0, 1, 2, 3 etc.)
name it as a string ('item1', 'path2' etc.)
$settings = array('path1', 'path2');
This just stored 0 => 'path1' and 1 => 'path1'
To get the value of a key in an array:
echo $settings[0]; //or $settings{0}, outputs 'path1'
echo $settings[1]; //outputs 'path2'
Or you store it as a string:
$settings = array('picture1' => 'path1', 'picture2' => 'path2');
echo $settings['picture1']; //outputs 'path1'
Also, multidimensional arrays are possible:
$settings = array(
'paths' => array(
'picture1' => 'path1',
'picture2' => 'path2'
),
'language' => 'english'
);
You get a value of a multidimensional array like this:
//for every dimension a new [], outputs 'path1'
echo $settings['paths']['picture1'];
Then you can just easily store all your settings and require_once 'settings.php';.
If you want to learn more about arrays, go to the php.net documentation.
Example:
/php/settings.php
$settings = array(
'items' => array(
'item1' => 'Data for Item1',
'item12' => 'Data for Item12',
),
'photos' => array(
'photo1' => 'img src string to image for item 1',
'photo12' => 'img src string to image for item 12',
),
);
index.php
<?php
require_once '/php/settings.php';
echo $settings['items']['item1']; //outputs 'Data for Item1'
//Or you can even use a foreach loop
foreach($settings['items'] as $key) {
echo $key;
echo '<br>';
}
That prints out:
Data for Item1
Data for Item12
Hope this helped.

Can PHP Array Keys have an Alias?

This is just a curious question, the reasoning behind it is purely to be slightly more lazy on my part. Here is what I mean..
Say I have a website, where htaccess makes nice urls, and sends that data to the $_GET['p'] array key as the current 'page'. In the index file, I setup the page, and the first thing I do is setup some page settings in a config file, $_PAGE array. Now, say I have multiple pages I want to have the same settings, (and down in the page, other things may slightly change that do not correspond to the settings. So currently, I have something that looks like the following 2 php files.
// index.php
include('page.array.php');
echo '<title>'.$_PAGE[$_GET['p']]['title'].'</title>';
// page.array.php
$_PAGE = array(
'some/page/' => array(
'title' => 'This is an example'
)
)
$_PAGE['some/aliased/page/'] = $_PAGE['some/page/'];
Notice that at the end ofthe page array, in order to 'alias' a page I must add this to the end after the array has been created.
Is there any method in php that maybe I am just unaware of, that could make me a tad bit lazier (and at the same time add to cleaner code), and make it so I can simply alias the key? I notice the following doesn't work, and I suppose my question is, is there any way to create the alias within the same array during the creation of the array?
This example deosn't work:
// page.array.php
$_PAGE = array(
'some/page/' => array(
'title' => 'This is an example'
),
'some/aliased/page/' => $_PAGE['some/page/']
)
Maybe a way to refer to "this" array, from within itself?
If this is not possible, I don't have an issue with the "Not Possible" answer. Though if you have a better method of solving this, other then the way I have described above, in the sake of being lazier, I would be interested in reading it :)
I don't believe you can have array values that mirror other values in the array like this. The first thing that comes to mind though would be for you to construct your $_PAGE array from within a switch statement, using fall-through values as aliases:
// Define path for testing, and empty page array
$path = "some/aliased/page";
$page = Array();
// Time to evaluate our path
switch ($path) {
// If it's either of these two cases
case "some/page":
case "some/aliased/page":
// Assign this array to $page
$page = Array("Title" => "Two Paths, One Page.");
break;
// If it's this case
case "some/other/path":
// Assign this array to $page
$page = Array("Title" => "Something else.");
break;
// If the path isn't found, default data
default:
$page = Array("Title" => "Page not found");
}
// Output the result
var_dump($page);
Execute it: http://sandbox.onlinephpfunctions...ebd3dee1f37c5612c25
It's possible:
$_PAGE = array('some/page/' => array('title' => 'This is an example'));
$_PAGE['some/aliased/page/'] = &$_PAGE['some/page/'];
$_PAGE['some/page/'] = 7;
var_dump($_PAGE);
Use the & to get a reference to a (non-object) variable instead of its value.

PHP library for creating/manipulating fixed-width text files

We have a web application that does time-tracking, payroll, and HR. As a result, we have to write a lot of fixed-width data files for export into other systems (state tax filings, ACH files, etc). Does anyone know of a good library for this where you can define the record types/structures, and then act on them in an OOP paradigm?
The idea would be a class that you hand specifications, and then work with an instance of said specification. IE:
$icesa_file = new FixedWidthFile();
$icesa_file->setSpecification('icesa.xml');
$icesa_file->addEmployer( $some_data_structure );
Where icesa.xml is a file that contains the spec, although you could just use OOP calls to define it yourself:
$specification = new FixedWidthFileSpecification('ICESA');
$specification->addRecordType(
$record_type_name = 'Employer',
$record_fields = array(
array('Field Name', Width, Vailditation Type, options)
)
);
EDIT: I'm not looking for advice on how to write such a library--I just wanted to know if one already existed. Thank you!!
I don't know of a library that does exactly what you want, but it should be rather straight-forward to roll your own classes that handle this. Assuming that you are mainly interested in writing data in these formats, I would use the following approach:
(1) Write a lightweight formatter class for fixed width strings. It must support user defined record types and should be flexible with regard to allowed formats
(2) Instantiate this class for every file format you use and add required record types
(3) Use this formatter to format your data
As you suggested, you could define the record types in XML and load this XML file in step (2). I don't know how experienced you are with XML, but in my experience XML formats often causes a lot of headaches (probably due to my own incompetence regarding XML). If you are going to use these classes only in your PHP program, there's not much to gain from defining your format in XML. Using XML is a good option if you will need to use the file format definitions in many other applications as well.
To illustrate my ideas, here is how I think you would use this suggested formatter class:
<?php
include 'FixedWidthFormatter.php' // contains the FixedWidthFormatter class
include 'icesa-format-declaration.php' // contains $icesaFormatter
$file = fopen("icesafile.txt", "w");
fputs ($file, $icesaFormatter->formatRecord( 'A-RECORD', array(
'year' => 2011,
'tein' => '12-3456789-P',
'tname'=> 'Willie Nelson'
)));
// output: A2011123456789UTAX Willie Nelson
// etc...
fclose ($file);
?>
The file icesa-format-declaration.php could contain the declaration of the format somehow like this:
<?php
$icesaFormatter = new FixedWidthFormatter();
$icesaFormatter->addRecordType( 'A-RECORD', array(
// the first field is the record identifier
// for A records, this is simply the character A
'record-identifier' => array(
'value' => 'A', // constant string
'length' => 1 // not strictly necessary
// used for error checking
),
// the year is a 4 digit field
// it can simply be formatted printf style
// sourceField defines which key from the input array is used
'year' => array(
'format' => '% -4d', // 4 characters, left justified, space padded
'length' => 4,
'sourceField' => 'year'
),
// the EIN is a more complicated field
// we must strip hyphens and suffixes, so we define
// a closure that performs this formatting
'transmitter-ein' => array(
'formatter'=> function($EIN){
$cleanedEIN = preg_replace('/\D+/','',$EIN); // remove anything that's not a digit
return sprintf('% -9d', $cleanedEIN); // left justified and padded with blanks
},
'length' => 9,
'sourceField' => 'tein'
),
'tax-entity-code' => array(
'value' => 'UTAX', // constant string
'length' => 4
),
'blanks' => array(
'value' => ' ', // constant string
'length' => 5
),
'transmitter-name' => array(
'format' => '% -50s', // 50 characters, left justified, space padded
'length' => 50,
'sourceField' => 'tname'
),
// etc. etc.
));
?>
Then you only need the FixedWidthFormatter class itself, which could look like this:
<?php
class FixedWidthFormatter {
var $recordTypes = array();
function addRecordType( $recordTypeName, $recordTypeDeclaration ){
// perform some checking to make sure that $recordTypeDeclaration is valid
$this->recordTypes[$recordTypeName] = $recordTypeDeclaration;
}
function formatRecord( $type, $data ) {
if (!array_key_exists($type, $this->recordTypes)) {
trigger_error("Undefinded record type: '$type'");
return "";
}
$output = '';
$typeDeclaration = $this->recordTypes[$type];
foreach($typeDeclaration as $fieldName => $fieldDeclaration) {
// there are three possible field variants:
// - constant fields
// - fields formatted with printf
// - fields formatted with a custom function/closure
if (array_key_exists('value',$fieldDeclaration)) {
$value = $fieldDeclaration['value'];
} else if (array_key_exists('format',$fieldDeclaration)) {
$value = sprintf($fieldDeclaration['format'], $data[$fieldDeclaration['sourceField']]);
} else if (array_key_exists('formatter',$fieldDeclaration)) {
$value = $fieldDeclaration['formatter']($data[$fieldDeclaration['sourceField']]);
} else {
trigger_error("Invalid field declaration for field '$fieldName' record type '$type'");
return '';
}
// check if the formatted value has the right length
if (strlen($value)!=$fieldDeclaration['length']) {
trigger_error("The formatted value '$value' for field '$fieldName' record type '$type' is not of correct length ({$fieldDeclaration['length']}).");
return '';
}
$output .= $value;
}
return $output . "\n";
}
}
?>
If you need read support as well, the Formatter class could be extended to allow reading as well, but this might be beyond the scope of this answer.
I have happily used this class for similar use before. It is a php-classes file, but it is very well rated and has been tried-and-tested by many. It is not new (2003) but regardless it still does a very fine job + has a very decent and clean API that looks somewhat like the example you posted with many other goodies added.
If you can disregard the german usage in the examples, and the age factor -> it is very decent piece of code.
Posted from the example:
//CSV-Datei mit Festlängen-Werten
echo "<p>Import aus der Datei fixed.csv</p>";
$csv_import2 = new CSVFixImport;
$csv_import2->setFile("fixed.csv");
$csv_import2->addCSVField("Satzart", 2);
$csv_import2->addCSVField("Typ", 1);
$csv_import2->addCSVField("Gewichtsklasse", 1);
$csv_import2->addCSVField("Marke", 4);
$csv_import2->addCSVField("interne Nummer", 4);
$csv_import2->addFilter("Satzart", "==", "020");
$csv_import2->parseCSV();
if($csv_import->isOK())
{
echo "Anzahl der Datensätze: <b>" . $csv_import2->CSVNumRows() . "</b><br>";
echo "Anzahl der Felder: <b>" . $csv_import2->CSVNumFields() . "</b><br>";
echo "Name des 1.Feldes: <b>" . $csv_import2->CSVFieldName(0) . "</b><br>";
$csv_import2->dumpResult();
}
My 2 cents, good-luck!
I don't know of any PHP library that specifically handles fixed-width records. But there are some good libraries for filtering and validating a row of data fields if you can do the job of breaking up each line of the file yourself.
Take a look at the Zend_Filter and Zend_Validate components from Zend Framework. I think both components are fairly self-contained and require only Zend_Loader to work. If you want you can pull just those three components out of Zend Framework and delete the rest of it.
Zend_Filter_Input acts like a collection of filters and validators. You define a set of filters and validators for each field of a data record which you can use to process each record of a data set. There are lots of useful filters and validators already defined and the interface to write your own is pretty straightforward. I suggest the StringTrim filter for removing padding characters.
To break up each line into fields I would extend the Zend_Filter_Input class and add a method called setDataFromFixedWidth(), like so:
class My_Filter_Input extends Zend_Filter_Input
{
public function setDataFromFixedWidth($record, array $recordRules)
{
if (array_key_exists('regex', $recordRules) {
$recordRules = array($recordRules);
}
foreach ($recordRules as $rule) {
$matches = array();
if (preg_match($rule['regex'], $record, $matches)) {
$data = array_combine($rule['fields'], $matches);
return $this->setData($data);
}
}
return $this->setData(array());
}
}
And define the various record types with simple regular expressions and matching field names. ICESA might look something like this:
$recordRules = array(
array(
'regex' => '/^(A)(.{4})(.{9})(.{4})/', // This is only the first four fields, obviously
'fields' => array('recordId', 'year', 'federalEin', 'taxingEntity',),
),
array(
'regex' => '/^(B)(.{4})(.{9})(.{8})/',
'fields' => array('recordId', 'year', 'federalEin', 'computer',),
),
array(
'regex' => '/^(E)(.{4})(.{9})(.{9})/',
'fields' => array('recordId', 'paymentYear', 'federalEin', 'blank1',),
),
array(
'regex' => '/^(S)(.{9})(.{20})(.{12})/',
'fields' => array('recordId', 'ssn', 'lastName', 'firstName',),
),
array(
'regex' => '/^(T)(.{7})(.{4})(.{14})/',
'fields' => array('recordId', 'totalEmployees', 'taxingEntity', 'stateQtrTotal'),
),
array(
'regex' => '/^(F)(.{10})(.{10})(.{4})/',
'fields' => array('recordId', 'totalEmployees', 'totalEmployers', 'taxingEntity',),
),
);
Then you can read your data file line by line and feed it into the input filter:
$input = My_Filter_Input($inputFilterRules, $inputValidatorRules);
foreach (file($filename) as $line) {
$input->setDataFromFixedWidth($line, $recordRules);
if ($input->isValid()) {
// do something useful
}
else {
// scream and shout
}
}
To format data for writing back to the file, you would probably want to write your own StringPad filter that wraps the internal str_pad function. Then for each record in your data set:
$output = My_Filter_Input($outputFilterRules);
foreach ($dataset as $record) {
$output->setData($record);
$line = implode('', $output->getEscaped()) . "\n";
fwrite($outputFile, $line);
}
Hope this helps!
I think you need a bit more information than you supplied:
What kind of data structures would you like to use for your records and column definitions?
It seems like this is a rather specialized class that would require customization for your specific use case.
I have a PHP class that I wrote that basically does what you are looking for, but relying on other classes that we use in our system. If you can supply the types of data structures you want to use it with I can check if it will work for you and send it over.
Note: I published this answer before from a public computer and I could not get it to appear to be from me (it showed as some random user). If you see it, please ignore the answer from 'john'.
If this is text file with separated fields, - your will need write it yourself.
Probably it is not a large problem. Good organization, will save a lot of time.
Your need universal way of defining structures. I.e. xml.
Your need something to generate ... specially I prefer an Smarty templating for this.
So this one:
<group>
<entry>123</entry>
<entry>123</entry>
<entry>123</entry>
</group>
Can be easy interpreted into test with this template:
{section name=x1 loop=level1_arr}
{--output root's--}
{section name=x2 loop=level1_arr[x1].level2_arr}
{--output entry's--}
{/section}
{/section}
This is just idea.
But imagine:
You need xml
You need template
i.e. 2 definitions to abstract any text structure
Perhaps the dbase functions are what you want to use. They are not OOP, but it probably would not be too difficult to build a class that would act on the functions provided in the dbase set.
Take a look at the link below for details on dbase functionality available in PHP. If you're just looking to create a file for import into another system, these functions should work for you. Just make sure you pay attention to the warnings. Some of the key warnings are:
There is no support for indexes or memo fields.
There is no support for locking.
Two concurrent web server processes modifying the same dBase file will very likely ruin your database.
http://php.net/manual/en/book.dbase.php
I'm sorry i cant help you with a direct class i have seen some thing that does this but i can't remember where so sorry for that but it should be simple for a coder to build,
So how i have seen this work in an example:
php reads in data
php then uses a flag (E.G a $_GET['type']) to know how to output the data E.G Printer, HTML, Excel
So you build template files for each version then depending on the flag you load and use the defined template, as for Fixed Width this is a HTML thing not PHP so this should be done in templates CSS
Then from this you can output your data how ever any user requires it,
Smarty Templates is quite good for this and then the php header to send the content type when required.

Trying to understand the code for the MeioUpload behavior for CakePHP

I was reading through the source code for MeioUpload to make sure I understand what it's doing, and for the most part the code is pretty easy to understand. However, I came upon a section of code which I just can't seem to figure out, and so I'm trying to determine if it's a mistake on the author's part or if I'm just missing something.
Essentially, this function is passed the filename of a default image, and adds that filename to a list of reserved words (and generates a replacement string for it). I have put an arrow and question marks (in comments) next to the line of code I can't figure out:
/**
* Include a pattern of reserved word based on a filename,
* and it's replacement.
* #author Vinicius Mendes
* #return null
* #param $default String
*/
function _includeDefaultReplacement($default){
$replacements = $this->replacements;
list($newPattern, $ext) = $this->splitFilenameAndExt($default);
if(!in_array($newPattern, $this->patterns)){
$this->patterns[] = $newPattern;
$newReplacement = $newPattern;
if(isset($newReplacement[1])){ // <--- ???
if($newReplacement[1] != '_'){
$newReplacement[1] = '_';
} else {
$newReplacement[1] = 'a';
}
} elseif($newReplacement != '_') {
$newReplacement = '_';
} else {
$newReplacement = 'a';
}
$this->replacements[] = $newReplacement;
}
}
As I understand it, $newReplacement should always be a string, not an array. That is because ultimately it gets its value from the first element of the array returned from this function:
function splitFilenameAndExt($filename){
$parts = explode('.',$filename);
$ext = $parts[count($parts)-1];
unset($parts[count($parts)-1]);
$filename = implode('.',$parts);
return array($filename,$ext);
}
So that if() statement makes no sense to me. It seems to be trying to catch a condition which could never occur. Or am I wrong and that section of code does serve a purpose?
Well, I can't explain the actual reasoning behind why it's doing it, but when you use a particular index on a string value like that, you're accessing a particular character of the string. That is, it's checking whether the filename has a second character, which it then replaces with either '_' or 'a'. If the filename is only one character long, it replaces the whole thing with either '_' or 'a'.
I can explain in more detail what that function does if you like, but I don't really have any understanding of what it's trying to accomplish.
Chad Birch has already answered my question (my original confusion was due to not understanding that $var[n] can be used to find the nth character of a string.), but just in case others are wondering, here's an explanation of what these functions are trying to accomplish:
MeioUpload is a file/image upload behavior for CakePHP. Using it, you can set any field in your model to behave as an upload field, like so:
var $actsAs = array(
'MeioUpload' => array(
'picture' => array(
'dir' => 'img{DS}{model}{DS}{field}',
'create_directory' => true,
'allowed_mime' => array('image/jpeg', 'image/pjpeg', 'image/png'),
'allowed_ext' => array('.jpg', '.jpeg', '.png'),
'thumbsizes' => array(
'normal' => array('width'=>180, 'height'=>180),
'small' => array('width'=>72, 'height'=>72)
),
'default' => 'default.png'
)
)
);
In the above example, MeioUpload will treat the field named "picture" as an upload field. This model happens to be named "product," so the upload directory would be "/img/product/picture/." The above configurations also specify that 2 thumbnails should be generated. So if I were to upload an image named "foo.png", the following files would be saved on the server:
/img/product/picture/foo.png
/img/product/picture/thumb.foo.png *
/img/product/picture/thumb.small.foo.png
* - thumbsizes labeled 'normal' do not have their key appended to their filenames
Additionally, the default images are also stored in the same directory:
/img/product/picture/default.png
/img/product/picture/thumb.default.png
/img/product/picture/thumb.small.default.png
But since we don't want the user-uploaded images, default images, or auto-generated thumbnails to overwrite one another, the author has created the following pair of arrays:
var $patterns = array(
"thumb",
"default"
);
var $replacements = array(
"t_umb",
"d_fault"
);
which are used to prevent filename conflicts when saving uploaded files:
$filename = str_replace($this->patterns,$this->replacements,$filename);
_includeDefaultReplacement() is used to add new reserved words when the default image is named something else.

Categories