How to call my PHP function multiple times without these ill effects? - php

I am experiencing a problem where the way I am calling a PHP function is causing incorrect output from a a class that otherwise acts properly.
Specifically, I am using the full-name-parser package to split full names in to "first", "middle" and "last" parts. It performs this task accurately.
However, I am trying to do it inside WordPress - specifically during an import via the WPAllImport plugin in which I am importing CSV data as WordPress Users.
WPAllImport supports execution of PHP functions (standard or custom) on import input data, allowing it to be manipulated before saving, eg. [function_name({input_field[1]})].
So I have crafted a wrapper function, get_name_part to allow me to split my input field name_full in to first, middle and last names for import to those corresponding WordPress fields. My get_name_part looks like this...
require_once '/home/mysite/public_html/wp-content/plugins/cxt-user-wpai-import/full-name-parser/vendor/autoload.php';
use ADCI\FullNameParser\Parser;
// name eg. Jokubas Phillip Gardner
// part eg. title, first, middle, last, nick, suffix,
// called via eg. [get_name_part({name_full[1]}, "first")]
function get_name_part($name, $part) {
$parser = new Parser(
[
'part' => $part,
// 'fix_case' => FALSE,
'throws' => FALSE
]
);
// Return the parsed name part
$nameObject = $parser->parse($name);
// Give it back to WPAllImport
return $nameObject;
}
That is, it should take the source name name_full and also a string corresponding to full-name-parser options which describe the name part (either first, middle or last).
In WPAllImport, then, I am calling these three lines, separately, in the WPAllImport fields for first_name, last_name and my custom name_mid...
[get_name_part({name_full[1]}, "first")]
[get_name_part({name_full[1]}, "middle")]
[get_name_part({name_full[1]}, "last")]
In theory, this should allow me to use a single wrapper function to spit back the specified part from the specified full name.
The problem is...
The first operation completes successfully. That is, putting [get_name_part({name_full[1]}, "first")] in my import settings' first_name field successfully saves a parsed first name (eg. "Jokubas") in the first_name WordPress field.
However, things break down after that. [get_name_part({name_full[1]}, "last")] places no name at all in the last_name field. It repeatedly fails.
And [get_name_part({name_full[1]}, "middle")] places an incorrect combination of the actual middle and last (eg. "Phillip Gardner") as middle name.
So the actual output is:
First: Jokubas
Middle: Phillip Gardner
Last: [blank]
This is not consistent with full-name-parser itself, which correctly parses outside of my function and the import environment, like...
First: Jokubas
Middle: Phillip
Last: Gardner
I'm not sure what the reason is, but I feel like it may be something to do with calling the same function three times from the same import step, albeit with different parameters.
I appreciate that I have mentioned a couple of product names above which are not plain PHP. But I feel like there may something I could do in the function code to accommodate the fact that this function is getting called three times, with different parameters, in a single process; that the underlying reason may have something to do with this repetition or contamination.
I have, therefore, considered whether there is a need to destroy all variables at the end of the function. However, executing unset on $name, $part, $parser or $nameObject after the return does not fix anything.
What am I missing?
Thanks.
Edit:
WordPress debug.log shows:
[01-Mar-2018 15:43:06 UTC] WordPress database error Table 'context_wpdb.wpcxt_2_usermeta' doesn't exist for query SHOW COLUMNS FROM wpcxt_2_usermeta made by do_action('admin_init'), WP_Hook->do_action, WP_Hook->apply_filters, call_user_func_array, PMXI_Plugin->adminInit, PMXI_Admin_Manage->edit, PMXI_Admin_Import->template, PMXI_Model->setTable
[01-Mar-2018 15:43:06 UTC] WordPress database error Table 'context_wpdb.wpcxt_2_usermeta' doesn't exist for query SELECT umeta_id, meta_key FROM wpcxt_2_usermeta GROUP BY meta_key ORDER BY umeta_id made by do_action('admin_init'), WP_Hook->do_action, WP_Hook->apply_filters, call_user_func_array, PMXI_Plugin->adminInit, PMXI_Admin_Manage->edit, PMXI_Admin_Import->template, PMXI_Model_List->getBy
The PMXI* prefixes pertain to WPAllImport.
Edit 2:
Here are some valid ways in which full-name-parser operates on its own (ie. slices a supplied full name in to identified components)...
1.
Passing "part" as "all" (or not, since "all" is default) makes $nameObject an object containing all identified portions of the name...
require_once '/home/context/public_html/wp-content/plugins/cxt-user-wpai-import/full-name-parser/vendor/autoload.php';
use ADCI\FullNameParser\Parser;
$parser = new Parser(
[
'part' => 'all',
// 'fix_case' => FALSE,
'throws' => FALSE
]
);
$name = 'Jokubas Phillip Gardner';
$nameObject = $parser->parse($name);
print_r($nameObject);
... so the above outputs all available portions of the name...
FullNameParser\Name Object ( [leadingInitial:ADCI\FullNameParser\Name:private] => [firstName:ADCI\FullNameParser\Name:private] => Jokūbas [nicknames:ADCI\FullNameParser\Name:private] => [middleName:ADCI\FullNameParser\Name:private] => Phillip [lastName:ADCI\FullNameParser\Name:private] => Gardner [academicTitle:ADCI\FullNameParser\Name:private] => [suffix:ADCI\FullNameParser\Name:private] => [errors:ADCI\FullNameParser\Name:private] => Array ( ) )
That is correct behaviour.
2.
But passing only one portion of the name, eg. 'part' as 'last', like follows, makes $nameObject a string, containing only the one stated portion. So...
require_once '/home/context/public_html/wp-content/plugins/cxt-user-wpai-import/full-name-parser/vendor/autoload.php';
use ADCI\FullNameParser\Parser;
$parser = new Parser(
[
'part' => 'last',
// 'fix_case' => FALSE,
'throws' => FALSE
]
);
$name = 'Jokubas Phillip Gardner';
$nameObject = $parser->parse($name);
echo 'Here is my nameObject: ' . $nameObject . '<br />';
The above outputs the following only...
Gardner
That is also correct behaviour.
3.
Full-name-parser has named get* functions dedicated to obtaining individual portions, eg. getFirstName(), but they do not appear to be essential. I was attempting to use the part parameter instead.

Ok, the first attempt was wrong, but I think I found the problem.
This behavior happens if there is a space at the end of the name. Actually the parser can not find the last name in this case, and throws an exception, but you have turned exceptions off.
If you try
$parser = new ADCI\FullNameParser\Parser(['throws' => FALSE]);
var_dump($parser->parse('Jokubas Phillip Gardner '));
You will see:
class ADCI\FullNameParser\Name#2 (8) {
private $leadingInitial =>
NULL
private $firstName =>
string(7) "Jokubas"
private $nicknames =>
NULL
private $middleName =>
string(15) "Phillip Gardner"
private $lastName =>
NULL
private $academicTitle =>
NULL
private $suffix =>
NULL
private $errors =>
array(1) {
[0] =>
string(26) "Couldn't find a last name."
}
}
So the solution is to trim your input.
function get_name_part($name, $part) {
$parser = new Parser(
[
'part' => $part,
// 'fix_case' => FALSE,
// 'throws' => FALSE
]
);
// Return the parsed name part
return $parser->parse(trim($name));
}

'context_wpdb.wpcxt_2_usermeta' doesn't exist.
The obviuos in the error message is that there is a table missing or it is in the wrong schema.
How this impacts and if has to do with your issue it is difficult to know.
To help you troubble shoot the issue here there is a plugin that I took from some page some time ago.
This plugin will let you add log statemets to your code that will come out in the wp-content/debug.log file.
In this way you can inspect the different values for the functions you are calling and see what can be wrong.
For the plugin to work you have to set the following in wp_config.php:
define('WP_DEBUG', true);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', true);
Once the plugin is installed, just add write_log($variable); in your code for it to come out in the debug.log file.
/* Plugin */
if(!defined('ABSPATH')) exit;
if ( ! function_exists('write_log')) {
function write_log ( $log ) {
if (defined('WP_DEBUG_LOG') && true === WP_DEBUG_LOG){
if ( is_array( $log ) || is_object( $log ) ) {
error_log( print_r( $log, true ) );
} else {
error_log( $log );
}
}
}
}
The plugin is meant to be put standalone in the wp-content/plugins folder.
When you are finished, remove the write_log() statements from your code, remove the plugin, and turn off the debug settings in wp_config.php

Related

what is a wordpress/woocommerce hook tha fire after a woocommerce product saved

I want to sent woocommerce product data after user create and save one, to web service of post company via soap-client. so i need a hook that fire after user create a product. i search a lot and find some word press and woocommerce hook but none of them do my work. after sending date,web service return an id that should be set as product sku. hear is my function code: (argument are from save_post_product hook)
function productPublished ($ID , $post , $update){
$product = wc_get_product( $post->ID);
$user = get_option ('myUsername');
$pass = get_option ('myPassword');
$name = $product->get_name();
$price = $product->get_regular_price();
$weight = $product->get_weight() * 1000;
$count = $product->get_stock_quantity();
$description = $product->get_short_description();
$result = $client -> AddStuff ([
'username' => $user,
'password' => $pass,
'name' => $name,
'price' => $price,
'weight' => $weight,
'count' => $count,
'description' => $description,
'percentDiscount' => 0
]);
$stuff_id=(int)$result->AddStuffResult;
update_post_meta($product->get_id(),'_sku',$stuff_id);
}
i use save_post_product action. it seems like it fire after user click new product before entering name and price and etc. because default product data sent to web service and sku generated and saved before i enter any data.
i use transition_post_status and add this code to my function:
if($old_status != 'publish' && $new_status == 'publish' &&
!empty($post->ID) && in_array( $post->post_type, array( 'product') )){
code...}
the result was the same as save_post_product.
i use publish_product action and result didn't changed.
i use draft_to_publish hook. it seems to fires after i enter product name and description. name sent to web-service but price and weight didn't. sku did not saved to database(why??).
i know that there is another question here that claim save_post fires after post published and save to DB. but i think woocommerce is different. it seems save post after entering name and description and before entering price and etc.
any suggestion???
The action(s) you want are these:
add_action('woocommerce_update_product', 'productPublished');
add_action('woocommerce_new_product', 'productPublished');
function productPublished($product_id){
//...
}
You can find them both (where they are triggered from) in the Woo source code here:
https://docs.woocommerce.com/wc-apidocs/source-class-WC_Product_Data_Store_CPT.html#237
I actually looked them up backwards by first finding where the source code for saving products was, and then looked for hooks in those methods (create/update).
//found on Line 134 method create
do_action( 'woocommerce_new_product', $id );
//found on Line 237 method update
do_action( 'woocommerce_update_product', $product->get_id() );
You'll also have to change this line:
function productPublished ($ID , $post , $update){
$product = wc_get_product( $post->ID);
}
To
function productPublished($product_id){
$product = wc_get_product( $product_id);
//....
}
I don't think the other arguments (that are missing) matter to your code. Such as if it's an update or a new product, I also don't see $post used except to get the product ID, which we already have.
UPDATE (determine args for callback)
If you are not sure about the callback's arguments, you can look in the source code (as I did above) Or if you can find it in documentation Or as a last resort you can simply output them. The best way to output them is one of these 3
func_get_args() - "Returns an array comprising a function's argument list" http://php.net/manual/en/function.func-get-args.php
debug_print_backtrace() - "Prints a backtrace (similar to stacktrace)" https://secure.php.net/manual/en/function.debug-print-backtrace.php
Exception::getTraceAsString() try/catch and throw an exception to output the stacktrace http://php.net/manual/en/exception.gettraceasstring.php
Here is an example I built with a minimally/simplified working hook system modeled after WordPress's. For testing reasons and because it's really not that hard to build when you know how it works:
//global storage (functions, not classes)
global $actions;
$actions = [];
//substitute wordpress add_action function (for testing only)
function add_action($action, $callback, $priorty=10, $num_args=1){
global $actions;
$actions[$action][] = [
'exec' => $callback,
'priorty'=>$priorty,
'num_args' => $num_args
];
}
//substitute wordpress do_action function (for testing only)
function do_action($action, ...$args){
// PHP5.6+ variadic (...$args) wraps all following arguments in an array inside $args (sort of the opposite of func_get_args)
global $actions;
if(empty($actions[$action])) return;
//sort by priory
usort($actions[$action], function($a,$b){
//PHP7+ "spaceship" comparison operator (<=>)
return $b['priorty']<=>$a['priorty'];
});
foreach($actions[$action] as $settings){
//reduce the argument list
call_user_func_array($settings['exec'], array_slice($args, 0, $settings['num_args']));
}
}
//test callback
function callback1(){
echo "\n\n".__FUNCTION__."\n";
print_r(func_get_args());
}
//test callback
function callback2(){
echo "\n\n".__FUNCTION__."\n";
try{
//throw an empty exception
throw new Exception;
}catch(\Exception $e){
//pre tags preserve whitespace (white-space : pre)
echo "<pre>";
//output the stacktrace of the callback
echo $e->getTraceAsString();
echo "\n\n</pre>";
}
}
//Register Hook callbacks, added in opposite order, sorted by priority
add_action('someaction', 'callback2', 5, 4);
add_action('someaction', 'callback1', 1, 5);
//execute hook
do_action('someaction', 1234, 'foo', ['one'=>1], new stdClass, false);
Output:
callback2
<pre>#0 [...][...](25): callback2(1234, 'foo', Array, Object(stdClass))
#1 [...][...](52): do_action('someaction', 1234, 'foo', Array, Object(stdClass), false)
#2 {main}
</pre>
callback1
Array
(
[0] => 1234
[1] => foo
[2] => Array
(
[one] => 1
)
[3] => stdClass Object
(
)
[4] =>
)
Sandbox
Stacktrace As you can see in the first output, we have a complete stacktrace of the application (minus the redacted info), including the function calls and the arguments used for those calls. Also note in this example, I registered it 2nd but the priority (set in add_action) made it execute first. Lastly, only 4 of the 5 arguments were used (also do to how add_action was called).
So do_action was called like this (with 'action' and 5 other args):
do_action('someaction', 1234, 'foo', Array, Object(stdClass), false)
And the actual callback was called like this (without 'action' and only 4 other args):
callback2(1234, 'foo', Array, Object(stdClass))
Function Arguments This is a bit more strait forward, but it doesn't give you the original call so you won't know the maximum number of args (which you can get from the call to do_action in the stacktrace). But if you just want a quick peek at the incoming data, it's perfect. Also I should mention this one uses all 5 args, which you can clearly see in the array for the second output. The [4] => is false, this is typically how print_r displays false (as just empty).
Debug Backtrace Unfortunately, debug_print_backtrace is disabled (for security reasons) in the sandbox, and Exception stacktrace is heavily redacted (typically it has file names and lines where functions are called from and located at) also for security reasons. Both of these can return arguments from things like connecting to the Database, which would contain the DB password in plain text, just for example. Anyway, debug_print_backtrace is pretty close to what an exception stack trace looks like anyway.
Summery
But in any case, this should give you an idea of what the data looks like. We can use functions like this (and Reflection) to interrogate the application at run time. I am sure there are other/more ways to do similar things too.
PS. It should go without saying, but I will say it anyway, these methods above work with any PHP function, and can be quite useful. Also as noted above, you should never show stacktraces on a live production machine.
Anyway, good luck.

Return Different PHP Array Values Based on A User ID-Related Constant

I am working with an array of tokens for an HTML template. Two of them ('{SYS_MENU}' and '{SUB_MENU}') are used to generate control buttons for the web application. Right now the buttons show up on the login page before the user's credential's are validated, and I need to change the code so that the buttons are hidden until after users login and reach the main menu. When someone types the http: address into their browser and arrives at the login page the system starts a session for them in the MySQL sessions table with USER_ID = 0. After they login the USER_ID changes to whatever number was assigned to them at initial registration (Example: USER_ID = 54), and after they logout at the end of the session back to 0. Tying this constant to the buttons seems like the best solution and I have found it to work in the past under similar circumstances.
Here is the original array:
$template_vars = array(
'{LANG_DIR}' => $lang_text_dir,
'{TITLE}' => theme_page_title($section),
'{CHARSET}' => $charset,
'{META}' => $meta,
'{GAL_NAME}' => $CONFIG['gallery_name'],
'{GAL_DESCRIPTION}' => $CONFIG['gallery_description'],
'{SYS_MENU}' => theme_main_menu('sys_menu'),
'{SUB_MENU}' => theme_main_menu('sub_menu'),
'{ADMIN_MENU}' => theme_admin_mode_menu(),
'{CUSTOM_HEADER}' => $custom_header,
'{JAVASCRIPT}' => theme_javascript_head(),
'{MESSAGE_BLOCK}' => theme_display_message_block(),
);
The first thing I did was to work with the references directly in the HTML template. I saw an example on w3schools that made it look like you could just type a PHP script into HTML and have it resolve. That didn't do anything except echo a bunch of text randomly into the page. I then found another citation that said you had to activate the PHP with an .HTACCESS entry before it would work directly in HTML. But that didn't close the deal either.
I know that changing '{SYS_MENU}' and '{SUB_MENU}' values in the array to => "", produces the results that I want (I.E. make the menu buttons disappear). So my next thought was I'll create an IF statement that returns two versions of the array based on circumstances, something like:
if(USER_ID != 0)
{
return $template_vars = //FIRST VERSION OF ARRAY WITH FULL VALUES//
}
else
{
return $template_vars = //SECOND VERSION OF ARRAY WITH ONLY => ""//
}
But all that did was cause the application load to terminate at a white screen with no error feedback.
My most recent attempt came from something I read here on Stack Overflow. I know that you cannot put IF statements into an array. But the article at this link described a workaround:
If statement within an array declaration ...is that possible?
So I rewrote the array as follows:
template_vars = array(
'{LANG_DIR}' => $lang_text_dir,
'{TITLE}' => theme_page_title($section),
'{CHARSET}' => $charset,
'{META}' => $meta,
'{GAL_NAME}' => $CONFIG['gallery_name'],
'{GAL_DESCRIPTION}' => $CONFIG['gallery_description'],
'{SYS_MENU}' => ('USER_ID != 0' ? theme_main_menu('sys_menu') : ""),
'{SUB_MENU}' => ('USER_ID != 0' ? theme_main_menu('sub_menu') : ""),
'{ADMIN_MENU}' => theme_admin_mode_menu(),
'{CUSTOM_HEADER}' => $custom_header,
'{JAVASCRIPT}' => theme_javascript_head(),
'{MESSAGE_BLOCK}' => theme_display_message_block(),
);
But that seems to have no effect at all. The application doesn't crash but the buttons are static whether you are logged in or logged out.
My question is: What am I missing? I can see that this is possible. But I've been trying things for a day and a half and just seem to be dancing around the solution. Your thoughts would be greatly appreciated.
The problem here is that you are calling return. With a global include file like this there is not context to return to so the application terminates. What you want to do is just assign the variables.
if(USER_ID != 0)
{
$template_vars = //FIRST VERSION OF ARRAY WITH FULL VALUES//
}
else
{
$template_vars = //SECOND VERSION OF ARRAY WITH ONLY => ""//
}

Symfony sfWidgetFormDoctrineChoice with multiple option on

I have created a form in which i embed another form. My question is about this embedded form - I'm using a sfWidgetFormDoctrineChoice widget with option multiple set to true. The code for this embedded form's configure method:
public function configure()
{
unset($this['prerequisite_id']);
$this->setWidget('prerequisite_id', new sfWidgetFormDoctrineChoice(array(
'model' => 'Stage',
'query' => Doctrine_Query::create()->select('s.id, s.name')->from('Stage s')->where('s.workflow_id = ?', $this->getOption('workflow_id') ),
'multiple' => true
)));
$this->setValidator('prerequisite_id', new sfValidatorDoctrineChoice(array(
'model' => 'Stage',
'multiple' => true,
'query' => Doctrine_Query::create()->select('s.id, s.name')->from('Stage s')->where('s.workflow_id = ?', $this->getOption('workflow_id') ),
'column' => 'id'
)));
}
I unset the prerequisite_id field because it is included in the base form, but I want it to be a multiple select.
Now, when I added the validator, everything seems to work (it passes the validation), but it seems like it has problems saving the records if there is more than one selection sent.
I get this PHP warning after submitting the form:
Warning: strlen() expects parameter 1 to be string, array given in
D:\Development\www\flow_dms\lib\vendor\symfony\lib\plugins\sfDoctrinePlugin\lib\database\sfDoctrineConnectionProfiler.class.php
on line 198
and more - I know, why - in symfony's debug mode I can see the following in the stack trace:
at Doctrine_Connection->exec('INSERT INTO stage_has_prerequisites
(prerequisite_id, stage_id) VALUES (?, ?)', array(array('12', '79'),
'103'))
So, what Symfony does is send to Doctrine an array of choices - and as I see in the debug sql query, Doctrine cannot render the query correctly.
Any ideas how to fix that? I would need to have two queries generated for two choices:
INSERT INTO stage_has_prerequisites (prerequisite_id, stage_id) VALUES (12, 103);
INSERT INTO stage_has_prerequisites (prerequisite_id, stage_id) VALUES (79, 103);
stage_id is always the same (I mean, it's set outside this form by the form in which it is embedded).
I have spend 4 hours on the problem already, so maybe someone is able to provide some help.
Well, I seem to have found a solution (albeit not the best one, I guess). Hopefully it'll be helpful to somebody.
Finally, after much thinking, I have concluded that if the problem comes from the Doctrine_Record not being able to save the record if it encounters an array instead of a single value, then the easiest solution would be to overwrite the save() method of the Doctrine_Record. And that's what I did:
class StageHasPrerequisites extends BaseStageHasPrerequisites
{
public function save(Doctrine_Connection $conn = null)
{
if( is_array( $this->getPrerequisiteId() ) )
{
foreach( $this->getPrerequisiteId() as $prerequisite_id )
{
$obj = new StageHasPrerequisites();
$obj->setPrerequisiteId( $prerequisite_id );
$obj->setStageId( $this->getStageId() );
$obj->save();
}
}
else
{
parent::save($conn);
}
}
(...)
}
So now if it encounters an array instead of a single value, it just creates a temporary object and saves it for each of this array's values.
Not an elegant solution, definitely, but it works (keep in mind that it is written for the specific structure of the data and it's just the effect of my methodology, namely See What's Wrong In The Debug Mode And Then Try To Correct It Any Way Possible).

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.

does sfWidgetFormSelect provide a string or an int of the selected item?

I'm having an annoying problem. I'm trying to find out what fields of a form were changed, and then insert that into a table. I managed to var_dump in doUpdateObjectas shown in the following
public function doUpdateObject($values)
{
parent::doUpdateObject($values);
var_dump($this->getObject()->getModified(false));
var_dump($this->getObject()->getModified(true));
}
And it seems like $this->getObject()->getModified seems to work in giving me both before and after values by setting it to either true or false.
The problem that I'm facing right now is that, some how, sfWidgetFormSelect seems to be saving one of my fields as a string. before saving, that exact same field was an int. (I got this idea by var_dump both before and after).
Here is what the results on both var dumps showed:
array(1) {["annoying_field"]=> int(3)} array(1) {["annoying_field"]=>string(1)"3"}
This seems to cause doctrine to think that this is a modification and thus gives a false positive.
In my base form, I have
under $this->getWidgets()
'annoying_field' => new sfWidgetFormInputText(),
under $this->setValidators
'annoying_field' => new sfValidatorInteger(array('required' => false)),
and lastly in my configured Form.class.php I have reconfigured the file as such:
$this->widgetSchema['annoying_field'] = new sfWidgetFormSelect(array('choices' => $statuses));
statuses is an array containing values like {""a", "b", "c", "d"}
and I just want the index of the status to be stored in the database.
And also how can I insert the changes into another database table? let's say my Log table?
Any ideas and advice as to why this is happen is appreciated, I've been trying to figure it out and browsing google for various keywords with no avail.
Thanks!
Edit:
ok so I created another field, integer in my schema just for testing.
I created an entry, saved it, and edited it.
this time the same thing happened!
first if you what the status_id to be saved in the database, you should define your status array like this:
{1 => "a", 2 => "b", 3 => "c", 4 => "d"}
So that way he know that 1 should be rendered like "a" and so on. Then, when saving, only the index should be saved.
About saving in another database, my advise is to modify the doSave method defined by the Form class yo match your needs. I only know how Propel deals with it, maybe this could help:
the doSave method dose something like this:
protected function doSave($con = null)
{
if (null === $con)
{
$con = $this->getConnection();
}
$old = $this->getObject()->getModifiedValues($this);//Define this
$new_object = new Log($old);//Create a new log entry
$new_object->save($con));//save it!
$this->updateObject();
$this->getObject()->save($con);
// embedded forms
$this->saveEmbeddedForms($con);
}
Hope this helps!
Edit:
This is an example extracted from a model in one of my applications and its working ok:
Schema:
[...]
funding_source_id:
type: integer
required: true
[...]
Form:
$this->setWidget('funding_source_id', new sfWidgetFormChoice(array(
'choices' => array(1 => 'asdads', 2 => '123123123' , 3 => 'asd23qsdf'),
)));
$this->setValidator('funding_source_id', new sfValidatorChoice(array(
'choices' => array(1 => 'asdads', 2 => '123123123' , 3 => 'asd23qsdf'),
'required' => true
)));
About the log thing, that could be quite more complex, you should read the current implementation of the doSave method in the base form class, currently sfFomrObject on Symfony1.4., and when and how it delegates object dealing with modified values.
Okay,
It turns out I forgot to do a custom validator to use the array key instead.

Categories