I'm writing a PHP web app that uses XML documents from an API. I have to construct many products from the returned XML data.
What I'm doing right now is using xpath and a loop to pull all of the objects into an array of sub-arrays. Then I loop through the array of arrays and pull specific arrays into their own named array. Is there a way to do this with a function or class constructor?
My current code looks something like this:
if ( $products_results_list[$key]["sChgDesc"] == "Small Box" ) {
$small_box = array(
"ChargeDescID" => $products_results_list[$key]["ChargeDescID"],
"sChgDesc" => $products_results_list[$key]["sChgDesc"],
"dcPrice" => $products_results_list[$key]["dcPrice"],
"dcTax1Rate" => ".0" . $products_results_list[$key]["dcTax1Rate"],
"dcInStock" => $products_results_list[$key]["dcInStock"],
);
}
After writing the above if statement about 8 times, with many more times needed, I'm thinking there must be a better practice than just writing everything out.
I want to do something like:
product_constructor($argument, $product_name) {
if ( $arguement ) {
$product_name = array(
"ChargeDescID" => $products_results_list[$key]["ChargeDescID"],
"sChgDesc" => $products_results_list[$key]["sChgDesc"],
"dcPrice" => $products_results_list[$key]["dcPrice"],
"dcTax1Rate" => ".0" . $products_results_list[$key]["dcTax1Rate"],
"dcInStock" => $products_results_list[$key]["dcInStock"],
);
}
and then just call the function or constructor as needed like:
product_constructor( '$products_results_list[$key]["sChgDesc"] == "Small Box"', '$small_box');
I actually tried the above code, but it was throwing errors. Despite working with PHP daily, I still don't know much about constructors, and figured this might be the perfect opportunity to learn how to do this correctly.
I'm not sure if classes are the right choice for this since I'm going to need to pull the products themselves into product package classes later.
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.
Please have a look to below code
function GetAreaName($AreaCode)
{
switch ($AreaCode)
{
case 201: return 'New Jersey';
case 202: return 'Washington';
// this goes on till
case 999: return '';
}
}
Let's say if the AreaCode is 998 then it would have to go through so many cases!
How could we optimize this function? (No using databases.)
I'm thinking to build an array and do a binary search over it? But this means every time the function is called the array will be rebuild? How do we build the array once, cache it and re-use every time this function is called?
Why not just use a hash table?
class Area {
private $areaCodes = array(
201 => 'New Jersey',
202 => 'Washington',
// this goes on till
999 => '';
);
function getStateByAreaCode ($areaCode) {
if (array_key_exists($areaCode, $this->areaCodes)) {
return $this->areaCodes[$areaCode];
} else {
return false;
}
}
}
Call it like this:
$area = new Area();
$city = $area->getStateByAreaCode(303);
Just save your class in a file and include it when you need it.
You Asked How to Prevent the Array From Being Created Every Request:
By putting this in a class you at least keep it clean. It technically still gets created each request, but unless your array is enormous (WAY bigger than the area codes in the U.S.) it shouldn't pose a performance issue. If you are worried about building the array every time you have a request, then take a look at a code optimizer like APC or the Zend Optimizer. This essentially takes the byte code that PHP generates at run time and caches it.
Sounds like you should just store it in your database.
But if you can't do that, either abstract it into a config file of some kind and store it in some kind of persisted object, or just use a static variable:
function foo($key) {
static $cache = array(1 => 'abc', 2 => 'def', 3 => 'ghi');
if (array_key_exists($key, $cache)) {
return $cache[$key];
} else {
//Somehow signal an error (throw an exception, return boolean false, or something)
}
}
In the above, $cache would only exist once. (If you knew that the values would never be null, you could use isset instead of array_key_exists.)
This isn't very flexible though since changing the data requires you to edit your code. You typically want your data and your code to be decoupled.
That could mean storing it in some kind of file (json, xml, php, whatever), and loading it into some kind of structure that you only create once. You would then pass that object or array around wherever it was needed. (Or, if you wanted to be hacky, you could use a static class. I suggest against this though.)
Switch condition is evaluated once only:
In a switch statement, the condition is evaluated only once and the result is compared to each case statement. In an elseif statement, the condition is evaluated again. If your condition is more complicated than a simple compare and/or is in a tight loop, a switch may be faster. ➫➫➫
There is no optimization required. However, read:
In PHP what's faster, big Switch statement, or Array key lookup
If you want to build a config file, you can consider something like:
$areas = array
(
1 => 'abc',
2 => 'def',
..
);
Then simply compare:
if (!isset($areas[$some_code]))
{
// do something
}
else
{
// ok
}
Try below pseudo code
$areas = array('201' => 'New Jersey',
'202' => 'Washington',
......
........
'999' => '');
function GetAreaName($AreaCode)
{
if(isset($areas[$AreaCode])) {
return $areas[$AreaCode];
} else {
// do something
}
}
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.
I have an array of arrays, each array containing details of a scan by a medical device. I'm getting this data from text logs that are dumped nightly. The format of which is this:
$this->scans = array(
array(
'patientid' => (int),
'patientname' => 'John Skeet',
'reviewed' => 0 or 1
//plus more irrelevant
),
array(
//same as above
), //etc
)
The important array key here is reviewed, as each scan may be reviewed if it is of high enough quality. However, the text logs dump out EVERY scan that is acquired, then goes back through and re-lists the ones that are reviewed.
Now in order to prevent duplicates , I figured I could just use an array_filter to filter out scans that have been both acquired and reviewed (keeping the reviewed version). However, the filter function is filtering out the entire array (except in some rare cases). If someone could take a look and let me know why they think it's happening that would be much appreciated.
$this->scans = array_filter($this->scans, array($this, "scan_cleanup"));
.
private function scan_cleanup($scan) {
//only if the scan was not reviewed
if ($scan['reviewed'] == 0) {
//change reviewed status to see if there is a duplicate
$scan['reviewed'] == 1;
//return false to remove this copy (and keep reviewed)
if (in_array($scan, $this->scans)) {
return false;
}
}
return true;
}
$scan['reviewed'] == 1;
vs
$scan['reviewed'] = 1;
One is a conditional, that does nothing in this context, the other is not there.
You are also not running the return false very often. I'd change the logic a little to make it a little clearer, and simpler by a little refactoring (pulling out a condition-check).
if ($scan['reviewed'] and hasDupe($scan)) {
return false; // filter out
}
return true; // it is passed back, and is output
hasDupe() does the best checks you know for a duplicate record and returns true/false.
Simple case of "==" vs. "=" as far as I can see.
$scan['reviewed'] = 1;
That oughta do the trick. Sometimes the simplest problems are the hardest to spot ;-)