I am updating a private attribute of a class that has a singleton pattern implemented and I am changing its value in one function of a class but in other function its value is not changing. Here is my class:
class notifications
{
private static $contacts = array(
"+11112223333" => array(
"status" => "pending"
),
"+14445556666" => array(
"status" => "pending"
)
);
private $next = false;
private $log;
// Singleton instance
private static $instance;
private function __construct()
{
$this->log = fopen("log.txt", "a+") or die("Unable to open file!");
}
// getInstance method
public static function getInstance()
{
if(!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
// Send Notification via Call
public function notifyCall($message)
{
$client = new Services_Twilio($this->account_sid, $this->auth_token);
try {
$keys = array_keys(notifications::$contacts);
foreach ($keys as $key) {
fwrite($this->log, "sending notification to ".$key."\n");
// Sending call logic here
$this->next = false;
while(!$this->next) {
echo $this->next;
if($this->next === -1)
die;
}
}
}
catch(Exception $e) {
fwrite($this->log, $e->getMessage());
throw new Exception($e->getMessage());
}
fclose($this->log);
http_response_code(200);
}
// Set status for call
public function setStatus($recipient, $status)
{
$recipient = str_replace(" ", "+", $recipient);
$file = fopen("status.txt", "a+");
fwrite($file, $recipient." : ".$status."\n");
$keys = array_keys(notifications::$contacts);
notifications::$contacts[$recipient]["status"] = $status;
if(notifications::$contacts[$recipient]["status"] === "in-progress") {
fwrite($this->log, "notifying next contact");
$this->next = -1;
}
else if(notifications::$contacts[$recipient]["status"] === "busy" || notifications::$contacts[$recipient]["status"] === "no-answer") {
fwrite($this->log, "Next!");
$this->next = true;
}
foreach ($keys as $key) {
echo "$key: ".notifications::$contacts[$key]['status']."\n";
}
fclose($file);
}
}
Here is how i use this class
require_once("notifications.php");
$recipient = $_POST["To"];
$status = $_POST["CallStatus"];
$notification = notifications::getInstance();
$notification->setStatus($recipient, $status);
Please note that both classes and status update are on separate files and they have different URLs but the contacts and their statuses are shared among both files
What I get as output in my log file is that it started sending call to first contact and the status is received as well and recorded in setStatusfunction as well a next is printed in log file but on the sendNotificationCall function it does not actually end while loop nor it send notification to next contact.
Can anyone please guide me what I am dong wrong here? I also tried making $next as static like $contacts but it did not worked as well so I am confused about the mistake.
Related
I am using php 7.4.9 and have a class which reads information from a file. These informations should be all the time availabe from outside the class and it also should possible to modify that array, so that this class can write back these information on request.
I have looked for a while but could not fined a useful solution.
I got the functions working, but the array loose the values from call to call.
Edit 2020/12/12
This is the uses structure of my code
<?php
.......
function show(){
$id3 = ID3::create();
$mp3 = &ID3::$mp3Array;
if($mode == "manual"){
if($file == ""){
return "";
}
$fName = $dir . "/" . $file;
$id3->open($fName);
.......
}else if($mode == "save"){
$fName = $dir . "/" . $file;
$id3->save($fName);
return "Save done!";
}
} // end of show
class ID3{
public static $mp3Array = array();
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
function open($fName){
$mp3 = self::$mp3Array;
. // $mp3 will be filled
.........
}
function save($fName) {
$mp3 = &ID3::§mp3Array;
error_log("TagSave: ".var_export($mp3, true),0); // is always empty
foreach($mp3 as $key => $value){
........
}
}
} //end of class>
?>
If I try to save the modified array, it is always empty, if show is called again!
I have also implemented the #Logifire proposal 'create'. I got a valid pointer but the arrayis still empty.
Maybe I should point out, that it is web page. The html code sends information (form) back to the php program.
I figured out, that use of global $id3 = NULL; does not work, because the php grogramm will be always called and set the variable again to NULL each time.
I have also implemented the following code on the beginning
<?php
error_log("PHP call",0);
$id3count = 0;
if(array_key_exists("Test_id3",$GLOBALS)){
error_log("GLOBALS[Test_id3] exist!",0);
}else{
error_log("GLOBALS[Test_id3] does not exist!",0);
$GLOBALS['Test_id3'] = "NEW";
}
The $GLOBAL['Test_id3'] never exist, if the programm will be called!
I got the functions working, but the array loose the values from call to call.
As I understand you, your setup is not a long running app, you can not keep state between requests (calls).
But if you are aware of that, the issue may be you have a new instance of the class each time you call it within the same request flow, you may use a singleton if this is the case. I suggest using accessors in your class.
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
// open, write, close file..
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create();
Based on your edited question (2020/12/12), I extended the example code:
class MyDataList {
private array $my_array = [];
private $file_path = '';
private function __construct()
{
}
public static function create(string $file_path): self {
static $object;
if ($object === null) {
$object = new self();
$stringified = file_get_contents($file_path) ?: '';
$array = json_decode($stringified, true) ?: [];
$object->file_path = $file_path;
$object->my_array = $array;
}
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_path, $stringified);
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create('/path/to/file');
Note: Be aware, you need to apply error handling
Comment answers:
Is the filepath connected to the array?
Well, you will write your data as JSON to a file each time you "modify" the array via the setArray()
Does it means, that the array is stored into a file and read out each time I try to connect again?
For each request you call create() it will instantiate the internal state of the array based on the stored data in the file. ATM. The file_get_contents call may have been wrapped and only called if the $object was not instantiated. (Now updated in the example)
So I have to call setArray($array); to save the data. I was looking for a soluting to keep the data without an management to save and read the array. Is this not possible with PHP?
Maybe you want to use a session variable to store your data? But it is individual per user and not long lived data - Link: https://www.php.net/manual/en/reserved.variables.session.php
In a standard PHP setup you can not have data/state between requests, but there are solution like Swoole which makes PHP a long running app: https://www.php.net/manual/en/book.swoole.php
I need a possibility to modify the array directly.
Is it a reference to the array you want? https://3v4l.org/OsBC6
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array &$new_array): void {
$this->my_array = &$new_array;
}
public function getArray(): array {
return $this->my_array;
}
}
There is no easy way to do with PHP!
Finally I use the proposal from Logifire, but had to modified it to fullfill my requirements.
I needed more than 1 array.
One array can ibclude binary data values, which json can't handle. So I have to use base64 for the binary data values.
Here my code:
public array $mp3Array = array();
public array $findArray = array();
private $file_dir = "";
public static function create(string $fileDir): self {
static $object;
if ($object === null) {
$object = new self();
$stringified1 = file_get_contents($fileDir."/mp3Array.obj") ?: '';
$array1 = json_decode($stringified1, true) ?: [];
$stringified2 = file_get_contents($fileDir."/findArray.obj") ?: '';
$array2 = json_decode($stringified2, true) ?: [];
$object->file_dir = $fileDir;
$object->mp3Array = $object->arrayDecode($array1);
$object->findArray = $array2;
}
return $object;
}
private function arrayEncode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayEncode($val);
}else if ($key == "data"){
$tmp[$key] = base64_encode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
private function arrayDecode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayDecode($val);
}else if ($key == "data"){
$tmp[$key] = base64_decode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
public function setMp3(array $new_array): void {
$this->mp3Array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function saveMp3(): void {
$base64 = $this->arrayEncode($this->mp3Array);
$stringified = json_encode($base64);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function setFind(array $new_array): void {
$this->findArray = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}
public function saveFind(): void {
$stringified = json_encode( $this->findArray);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}
I build a chat app using PHP Ratchet.
I store all my connection in SplObjectStorage.
Each connection will have user id that I will attach him by this:
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
// Store the new connection to send messages to later
$querystring = $conn->WebSocket->request->getQuery();
foreach ($querystring as $value)
{
if($key == "senderId")
$senderId = $value;
}
$this->clients->attach($conn, $senderId);
echo "New connection! ({$conn->resourceId}) senderId({$senderId})\n";
}
When a message arrive I want the fastest way to get the $conn object related to the specific user id.
I can use the trivial foreach like this:
public function onMessage(ConnectionInterface $from, $msg) {
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
, $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
foreach ($this->clients as $client)
{
if ($from->getInfo() !== $client->getInfo()) {
// do stuff
}
}
I wonder if there is a faster method. Maybe using some function like this:
$conn = $this->clients->getClientWithInfo("WANTED-INFO");
The wanted approach is to void the loop over all my connection in order the send a message to a specific user.
I want to get the connection that is associated with a the user id.
In my opinion there is only one solution to make it work, like you expected => extending the SplObjectStorage class. But then you have two options.
First you can be lazy and add a getClientWithInfo method to the class which find the object for you:
class ConnectionStorageSimple extends SplObjectStorage
{
public function getClientWithInfo($info)
{
$this->rewind();
while ($this->valid()) {
$object = $this->current(); // similar to current($s)
$data = $this->getInfo();
if ($info === $data) {
$this->rewind();
return $object;
}
$this->next();
}
return null;
}
}
$conStorage = new ConnectionStorageSimple();
$con1 = new \stdClass();
$con1->id = 1;
$con2 = new \stdClass();
$con2->id = 2;
$conStorage->attach($con1, 1);
$conStorage->attach($con2, 2);
var_dump($conStorage->getClientWithInfo(1));
var_dump($conStorage->getClientWithInfo(2));
/**
This will output something like that:
class stdClass#2 (1) {
public $id =>
int(1)
}
class stdClass#3 (1) {
public $id =>
int(2)
}
*/
The other option is, that you build your one info-object mapping based on the parent function. This is a little more complex:
<?php
class ConnectionStorage extends SplObjectStorage
{
private $objInfoMapping = array();
public function attach($object, $data = null)
{
if (null !== $data) {
$this->objInfoMapping[$data] = $object;
}
parent::attach($object, $data);
}
public function detach($object)
{
$this->detach($object);
parent::detach($object);
}
public function addAll($storage)
{
$this->addStorage($storage);
parent::addAll($storage);
}
public function removeAll($storage)
{
$this->objInfoMapping = array();
parent::removeAll($storage);
}
public function removeAllExcept($storage)
{
$this->objInfoMapping = array();
$this->addStorage($storage);
parent::removeAllExcept($storage);
}
public function unserialize($serialized)
{
parent::unserialize($serialized);
$this->addStorage($this);
}
public function offsetUnset($object)
{
$this->detach($object);
parent::offsetUnset($object);
}
protected function detachObject($obj)
{
$info = $this[$obj];
if (array_key_exists($info, $this->objInfoMapping)) {
unset($this->objInfoMapping[$info]);
}
}
protected function addStorage(SplObjectStorage $storage)
{
$storage->rewind();
while ($storage->valid()) {
$object = $storage->current(); // similar to current($s)
$data = $storage->getInfo();
$this->objInfoMapping[$data] = $object;
$storage->next();
}
}
public function getClientWithInfo($info)
{
if (array_key_exists($info, $this->objInfoMapping)) {
return $this->objInfoMapping[$info];
}
}
}
$conStorage = new ConnectionStorage();
$con1 = new \stdClass();
$con1->id = 1;
$con2 = new \stdClass();
$con2->id = 2;
$conStorage->attach($con1, 1);
$conStorage->attach($con2, 2);
var_dump($conStorage->getClientWithInfo(1));
var_dump($conStorage->getClientWithInfo(2));
/**
This will also output something like that:
class stdClass#2 (1) {
public $id =>
int(1)
}
class stdClass#3 (1) {
public $id =>
int(2)
}
*/
The main difference between the two classes is, that the second example will perform better on big datasets, because you do not have to iterate over all objects of the storage.
And because you store just object references to the own array, the extra memory consumption should not be so big.
Disclaimer: The classes are just to illustrate the possibilities. The first one should be save to use, but the second one should be tested more
Hope this helps.
this is what I did, see that it's simpler and fast.
namespace mine;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ws implements MessageComponentInterface {
protected $clients;
protected $clientids;
public function __construct() {
$this->clients = new \SplObjectStorage;
$this->clientids = array();
}
public function multicast($msg) {
foreach ($this->clients as $client) $client->send($msg);
}
public function send_to($to,$msg) {
if (array_key_exists($to, $this->clientids)) $this->clientids[$to]->send($msg);
}
public function onOpen(ConnectionInterface $conn) {
$socket_name = "{$conn->resourceId}#{$conn->WebSocket->request->getHeader('X-Forwarded-For')}";
$this->clients->attach($conn,$socket_name);
$this->clientids[$socket_name] = $conn;
}
public function onMessage(ConnectionInterface $from, $msg) {
}
public function onClose(ConnectionInterface $conn) {
unset($this->clientids[$this->clients[$conn]]);
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e) {
$conn->close();
}
}
this adds 2 functions, one for multicast, and another to message a client by the socket_name which is a string ID (I chose a combination of socket id and ip to stop possible collisions).
so to send to a client:
$ws->send_to(socket_name,message);
obviously $ws is the websocket created at initisation:
$ws = new mine\ws();
$ws_server = new Ratchet\Server\IoServer( new Ratchet\Http\HttpServer( new Ratchet\WebSocket\WsServer( $ws ) ), $socket );
I need help with PHPUnit and some methods. How should you guys write tests in PHPUnit to reach a high code coverage for the following properties and methods?
I'm pretty new to PHPUnit and could need some help. I've just write some test cases for more basic code. This class generates flash messages for the end user, and store it in a session.
Extremely grateful for some help. Any ideas?
private $sessionKey = 'statusMessage';
private $messageTypes = ['info', 'error', 'success', 'warning']; // Message types.
private $session = null;
private $all = null;
public function __construct() {
if(isset($_SESSION[$this->sessionKey])) {
$this->fetch();
}
}
public function fetch() {
$this->all = $_SESSION[$this->sessionKey];
}
public function add($type = 'debug', $message) {
$statusMessage = ['type' => $type, 'message' => $message];
if (is_null($this->all)) {
$this->all = array();
}
array_push($this->all, $statusMessage);
$_SESSION[$this->sessionKey] = $this->all;
}
public function clear() {
$_SESSION[$this->sessionKey] = null;
$this->all = null;
}
public function html() {
$html = null;
if(is_null($this->all))
return $html;
foreach ($this->all as $message) {
$type = $message['type'];
$message = $message['message'];
$html .= "<div class='message-" . $type . "'>" . $message . "</div>";
}
$this->clear();
return $html;
}
I have setup an setup-case, like this:
protected function setUp() {
$this->flash = new ClassName();
}
Also tried one test case:
public function testFetch() {
$this->assertEquals($this->flash->fetch(), "statusMessage", "Wrong session key.");
}
But gets an error message telling me: "Undefined variable: _SESSION"
If I then try:
public function testFetch() {
$_SESSION = array();
$this->assertEquals($this->flash->fetch(), "statusMessage", "Wrong session key.");
}
I get another error message telling: "Undefined index: statusMessage"
Try something like this:
function testWithoutSessionKey() {
$_SESSION = array();
$yourClass = new YourclassName();
$this->assertNull($yourClass->html()); }
function testWithSomeSessionKey() {
$_SESSION = array( 'statusMessage' => array(...));
$yourClass = new YourclassName();
$this->assertSame($expect, $yourClass->html());
}
You can't instantiate your class in the setup because your constructor need that the SESSION variable may exist(so you can test that can have some value inside).
You can evalutate (assert) only the ouptput of a method, so you can't assert that the message of the return of the method fetch.
In your method testFecth you have found a bug! Thanks to the test for this. Try fixing it with checking as you do in the construct :
public function fetch() {
if (isset($_SESSION[$this->sessionKey]))
$this->all = $_SESSION[$this->sessionKey];
}
Hope this help
For a small home project I am working on I have been looking for OO design patterns for Memcache implementation, but so far haven't found something I feel fits, so maybe my approach is wrong.
I have a DB connection class and an baseModel class so I want to implement caching on the baseModel where appropriate.
I have implemented the Database connection and the Cacher as Singlton patterns.
I cannot seem to get the Cacher class to read the data or trigger the echo "<p>Getting from cache"; line after I refresh the page on the base Model "loadFromDb" function
Here are the classes:
class Cacher {
protected static $cacher = null;
private static $settings;
public static function getCache() {
if (self::$cacher != null) {
return self::$cacher;
}
try {
self::$settings = parse_ini_file("./configs/main.ini");
self::$cacher = new Memcache();
self::$cacher->connect(
self::$settings['cache_server_host']
, self::$settings['cache_server_port']
);
} catch (Exception $e) {
// TODO log error and mitigate..
echo "Error connecting memcache";
die();
}
var_dump(self::$cacher->getstats());
return self::$cacher;
}
public static function getData($key) {
if (self::$cacher == null) {
self::getCache();
}
return self::$cacher->get($key);
}
public static function setData($key, $data, $expire = 0) {
if (self::$cacher == null) {
self::getCache();
}
if (self::$cacher)
return self::$cacher->set($key, $data, MEMCACHE_COMPRESSED, $expire);
}
}
class ModelBase {
protected $fields = array();
protected $class = null;
function __construct($class_name) {
$this->class = $class_name;
$this->fields = Database::getFields($class_name);
}
public function loadFromDB($id, $fromCache = true) {
$key = "loadFromDB_{$this->class}_{$id}";
if ($fromCache) {
$data = Cacher::getData($key);
if ($data) {
echo "<p>Getting from cache";
return unserialize($data);
} else {
echo "<p>No cache data. going to DB";
}
}
$values = Database::loadByID($this->class, $this->fields[0], $id);
foreach ($values as $key => $val) {
$this->$key = $val;
}
$dataSet = Cacher::setData($key, serialize($this));
echo "<p>Data set = $dataSet";
}
}
Memcache service is running and I can read data directly back if I read the cache directly after I write it, but what I want is to read the data from the DB only the first time the page loads, after that use the cache....
Any comments welcome...
Try doing it like (let the result of the cache decide if you should query the db):
<?php
public function loadFromDB($id) {
$key = "loadFromDB_{$this->class}_{$id}";
//query cache
$data = Cacher::getData($key);
//is it found
if (!empty($data)) {
//echo "<p>Getting from cache";
return unserialize($data);
}
//no so lest query db
else {
//echo "<p>No cache data. going to DB";
$values = Database::loadByID($this->class, $this->fields[0], $id);
foreach ($values as $key => $val) {
$this->$key = $val;
}
//store it in cache
//$dataSet = Cacher::setData($key, serialize($this));
$dataSet = Cacher::setData($key, serialize($values));//<<store the db result not the class
}
//echo "<p>Data set = $dataSet";
return unserialize($dataSet);
}
?>
Sorry for the terrible headline, let me try to explain below.
I have written a bunch of small functions that either returns true or false.
validateName()
validateEmail()
validateAddr()
validateBirtd()
validateUsername()
Now I am looping through a lot of data imported with a CSV file, and checkin which data is valid or not (returns true or false).
I do it this way:
if (validateName($data[0]) == true AND validateEmail($data[1]) == true AND validateAddr($data[3]) == true AND validateBirtd($data[5]) == true AND validateUsername($data[6])==true) {
// create array to import etc etc
}else{
// create other array with data who failed validation, to show user later..etc etc
}
My question is - is there a more clever way to do this? Would it be possible to create a list of for each failed validation ? Say 3 entrys has fails the validateEmail() function, and 10 both fails validateEmail and validateName().
Would there be a way for me to sort this so I can tell the user "these entrys failed email validation" and "these entrys failed Name and email validation".
I thought about validating one field at a time, but this way I would have duplicates if one entry has more than one validation error.
Would be cool if there was some kind of logic that I don't know of where I could do this
You could create a function.
function validate($data) {
$errors = array();
$fields = array('Name', 'Email', 'Addr', 'Birtd', 'UserName');
foreach ($fields as $i => $field) {
$func = 'validate'.$field;
if (!$func($data[$i])) {
$errors[] = $field;
}
}
return $errors;
}
$errors = validate($data);
if (empty($errors)) {
// create array to import etc etc
} else {
// errors
echo 'There are errors with ' . implode(',', $errors);
}
You can use Iterators to get CSV content and filter at the same time. you can also add different callback to each CSV index
Example
$csv = new CSVFilter(new CSVIterator("log.txt"));
$csv->addFilter(0, "validateName"); //<------------ Means validate Index at 0
$csv->addFilter(1, "validateEmail");
$csv->addFilter(2, "validateAddr");
$csv->addFilter(3, "validateBirtd");
$csv->addFilter(4, "validateName");
$csv->addFilter(5, "validateUsername");
foreach ( $csv as $data ) {
var_dump($data);
}
//To get Errors
var_dump($csv->getErrors());
CSV Filter
class CSVFilter extends FilterIterator {
protected $filter = array();
protected $errors = array();
public function __construct(Iterator $iterator) {
parent::__construct($iterator);
}
public function addFilter($index, Callable $callable) {
$this->filter[$index] = $callable;
$this->errors[$callable] = 0;
}
public function getErrors() {
return $this->errors;
}
public function accept() {
$line = $this->getInnerIterator()->current();
$x = true;
foreach ($this->filter as $key => $var ) {
if (isset($line[$key])) {
$func = $this->filter[$key];
$func($var) or $this->errors[$func] ++ and $x = false;
}
}
return $x;
}
}
CSVIterator
class CSVIterator implements \Iterator {
protected $fileHandle;
protected $line;
protected $i;
public function __construct($fileName) {
if (! $this->fileHandle = fopen($fileName, 'r')) {
throw new \RuntimeException('Couldn\'t open file "' . $fileName . '"');
}
}
public function rewind() {
fseek($this->fileHandle, 0);
$this->line = fgetcsv($this->fileHandle);
$this->i = 0;
}
public function valid() {
return false !== $this->line;
}
public function current() {
return array_map("trim", $this->line);
}
public function key() {
return $this->i;
}
public function next() {
if (false !== $this->line) {
$this->line = fgetcsv($this->fileHandle);
$this->i ++;
}
}
public function __destruct() {
fclose($this->fileHandle);
}
}
Simple Random Functions
function validateName($var) {
return mt_rand(0, 5);
}
function validateEmail($var) {
return mt_rand(0, 5);
}
function validateAddr($var) {
return mt_rand(0, 5);
}
function validateBirtd($var) {
return mt_rand(0, 5);
}
function validateUsername($var) {
return mt_rand(0, 5);
}
If you something a little more encapsulated, you can try this. It allows you to write different validators for each CSV file you may be validating. Additionally, you could write methods in either class that would allow you to perform additional tasks on each row. I just find it a little cleaner and easier to maintain than having a bunch of globally-named functions.
Note: I'm obviously using some pretty basic validator examples and exceptions. The idea here is that I'm providing a layout for you to follow; you can customize any specific behaviors however you'd like.
usage
$c = new UserCsvValidator('user_data.csv');
try {
$c->validate();
}
catch (Exception $e) {
echo $e->getMessage();
}
implementation; parent class
<?php
class CsvValidator {
private $filename;
private $fh;
protected $fields = array();
public function validate__construct($filename, ) {
$this->filename = $filename;
// open file
if ( ($this->fh = fopen($this->filename, 'r')) === false) {
throw new Exception("could not open file: {$this->filename}");
}
}
public function validate() {
while( ($row=fgetcsv($this->fh)) !== false) {
// create hash
if ( ($hash = array_combine($this->fields, $row)) === false) {
throw new Exception("invalid row" . print_r($row, true));
}
// validate
foreach ($hash as $field => $value) {
// determine method call
$method = "validate_{$field}";
if (!method_exists($this, $method)) {
throw new Exception("validation method not defined: {$method}");
}
// validate the field
if (call_user_func(array($this, $method), $value) === false) {
throw new Exception("invalid value for {$field}: {$value}");
}
}
}
}
}
implementation; subclass
<?php
class UserCsvValidator extends CsvValidator {
protected $fields = array('name', 'email', 'address', 'birth_date', 'username');
// example functions for each field
protected function validate_name($value) {
return !empty($value);
}
protected function validate_email($value) {
return strpos($value, '#') !== false;
}
protected function validate_address($value) {
return !empty($value);
}
protected function validate_birth_date($value) {
return date('Y-m-d', strtotime($value)) == $value;
}
protected function validate_username($value) {
return !empty($value);
}
}