2
\$\begingroup\$

I have written this piece of code to help the development of FileMaker developers having to use the dog-awful PHP API.

<?php

require_once ( 'fm_api/FileMaker.php' );
require_once ( 'config/config.php' );
/**
 * Interface between the FileMaker API and PHP - Written by RichardC 
 */
class FMDB {
    /**
     * Setting up the classwide variables
     */
    protected $fm;
    protected $layout = '';
    protected $debugCheck = true;
    public $lastObj = null;

    //Filemaker LessThan/Equal to and GreaterThan/Equal to characters - Does not work in all IDEs
    public $ltet = '≤';
    public $gtet = '≥';

    /** Constructor of the class */
    public function __construct() {
        $this->fm = new FileMaker( FMDB_NAME, FMDB_IP, FMDB_USERNAME, FMDB_PASSWORD );
    }


    /**
     * Checks whether there is an error in the resource given.
     * 
     * @param   obj     $request_object
     * 
     * @return  int
     */
    public static function isError( $request_object ) {

        $preg = preg_grep( '/^([^*)]*)error([^*)]*)$/', array_keys( $request_object ) );

        if( is_array( $request_object ) && !empty( $preg ) ){
            return $preg[0];
        }

        return ( FileMaker::isError( $request_object ) ? $request_object->getCode() : 0 );
    }

    /** 
     * Just a quick debug function that I threw together for testing
     * 
     * @param   string  $func
     * @param   array   $arrReturn
     * @param   string  $type   'file' || 'console'
     * 
     * @return  mixed
     */
    protected function debug( $func, $arrReturn, $type='file' ){
        $debugStr = '';

        if( $func == '' || empty( $func ) ){
            return '';
        }

        switch( $type ){

            case 'default':
            case 'file':

                $fo = fopen( DEBUG_LOCATION . '/logFile.txt', 'a+'); 

                foreach( $arrReturn as $k => $v ){

                    if( is_array( $v ) ){
                        foreach( $v as $n => $m ){
                            //$debugStr .= '<script type="text/javascript">console.log("[Debug] ' . $func . ' - ['. $n .'] -> ' . $m . ' ");</script>';
                            fwrite( $fo, '[Debug ' . date( 'd-m-Y H:i:s' ) . '] ' . $func . ' - ['. $n .'] -> ' . $m . "\n\r" );
                        }
                    }else{
                        //$debugStr .= '<script type="text/javascript">console.log("[Debug] ' . $func . ' - ['. $k .'] ' . $v . ' ");</script>';
                        fwrite( $fo,  '[Debug '. date( 'd-m-Y H:i:s' ) . '] ' . $func . ' - ['. $k .'] ' . $v . "\n\r" );
                    }
                }
                fclose( $fo );

                return true;

                break;

            case 'console':
                foreach( $arrReturn as $k => $v ){
                    if( is_array( $v ) ){
                        foreach( $v as $n => $m ){
                            $debugStr .= '<script type="text/javascript">console.log("[Debug] ' . $func . ' - ['. $n .'] -> ' . $m . ' ");</script>';
                        }
                    }else{
                        $debugStr .= '<script type="text/javascript">console.log("[Debug] ' . $func . ' - ['. $k .'] ' . $v . ' ");</script>';
                    }
                }

                return $debugStr;

                break;
        }
    }

    /**
     * Selects data from a FileMaker Layout from the given criteria
     * 
     * @param   string  $layout
     * @param   array   $arrSearchCriteria
     * @param   bool    $xml
     * 
     * @return  array
     */
    public function select( $layout, $arrSearchCriteria ) {
        $arrOut = array();

        if ( ( !is_array( $arrSearchCriteria ) ) ) {
            return false;
        }

        $findReq = $this->fm->newFindCommand( $layout );

        foreach ( $arrSearchCriteria as $field => $value ) {
            $findReq->addFindCriterion( $this->fm_escape_string( $field ), $this->fm_escape_string( $value ) );
        }

        $results = $findReq->execute();

        if ( $this->isError( $results ) === 0 ) {
            $fields = $results->getFields();

            $records = $results->getRecords();

            //Set the last used layout and object
            $this->layout = $layout;
            $this->lastObj = $records;

            //Loops through the records retrieved
            $i = 0;
            foreach ( $records as $record ) {
                $i++;
                foreach ( $fields as $field ) {
                    $arrOut[$i]['rec_id']   = $record->getRecordId();
                    $arrOut[$i][$field]     = $record->getField( $field );
                }
            }
        } else {
            $arrOut['errorCode'] = $this->isError( $results );
        }

        if( $this->debugCheck ){
            foreach( $arrOut as $k => $v ){
                echo $this->debug( 'SELECT', array(
                    $k  =>  $v 
                ));
            }
        }
        return $arrOut;
    }

    /**
     * Sets Fields within a given Layout with the given criteria
     *
     * @param   array   $arrFields
     * 
     * @example $objFMDB->setFields(array('fieldName' => 'ValueToUpdate'));
     * 
     * @return  bool
     */
    public function setFields( $arrFields ) {
        $blOut = false;
        if ( ( !is_array( $arrFields ) ) ) {
            return false;
        }
        $layout = ( empty( $layout ) ? ( $this->layout ) : ( $layout ) );
        $records = $this->lastObj;

        if ( isset( $records ) && !empty( $records ) ) {
            foreach ( $records as $record ) {
                foreach ( $arrFields as $fieldName => $value ) {
                    $setFields[] = $record->setField( $this->fm_escape_string( $fieldName ), $this->fm_escape_string( $value ) );
                }
            }
            $commit = $record->commit();
            if ( $this->isError( $commit ) === 0 ) {
                $blOut = true;
            } else {
                return $this->isError( $commit );
            }
        }

        // Housekeeping
        unset( $record, $commit, $fieldName, $value );

        return $blOut;
    }    

   /**
     * Updates a set of fields on a layout where the clauses match
     * 
     * @param   string  $layout
     * @param   array   $arrFields
     * @param   array   $arrSearchCriteria
     * 
     * @return  bool
     */
    public function update( $layout, $arrFields, $arrSearchCriteria ){

        //Loop through the parameters and check they are set and not empty
        foreach( func_get_args() as $arg ){
            if( ( $arg == '' ) || ( empty( $arg ) ) ){
                return false;
            }
        }

        $findReq = $this->fm->newFindCommand( $layout );

        foreach ( $arrSearchCriteria as $field => $value ) {
            $findReq->addFindCriterion( $this->fm_escape_string( $field ), $this->fm_escape_string( $value ) );
        }

        //Perform the find
        $result = $findReq->execute();

        if ( $this->isError( $result ) !== 0 ) {
            return $this->isError( $findReq );    
        }

        $records = $result->getRecords();

        //Loop through the found records 
        foreach ( $records as $record ) {

            //Loop through the fields given in the argument and set the fields with the values
            foreach ( $arrFields as $f => $v ) {
                $record->setField( $this->fm_escape_string( $f ), $this->fm_escape_string( $v ) );
            }

            //Commit the setFields
            $commit = $record->commit();

            if ( $this->isError( $commit ) !== 0 ) {
                return $this->isError( $commit );
            }
        }

        //Housekeeping
        unset( $result, $commit, $record, $findReq );

        return true;
    }

    /**
     * Updates a record by the given ID of the record on a specified layout
     * 
     * @param   string  $layout
     * @param   array   $arrFields
     * @param   int     $iRecordID
     * 
     * @return  bool
     */
    public function updateRecordByID( $layout, $arrFields, $iRecordID ) {
        $blOut = false;
        if ( ( $layout == '' ) || ( !is_array( $arrFields ) ) || ( !is_numeric( $iRecordID ) ) ) {
            return false;
        }
        $findReq = $this->fm->getRecordById( $layout, $iRecordID );

        if ( $this->isError( $findReq ) === 0 ) {

            foreach ( $findReq as $record ) {
                foreach ( $arrFields as $f => $v ) {
                    $record->setField( $this->fm_escape_string( $f ), $this->fm_escape_string( $v ) );
                }
                $commit = $record->commit();
            }

            if ( $this->isError( $commit ) === 0 ) {
                $blOut = true;
            } else {
                return $this->isError( $commit );
            }
        } else {
            return $this->isError( $findReq );
        }

        unset( $result, $commit, $record, $findReq );
        return $blOut;
    }

    /**
     * Inserts a record into the layout
     *  
     * @param   string  $layout
     * @param   array   $arrFields
     * 
     * @return  bool
     */
    public function insert( $layout, $arrFields ) {
        $blOut = false;
        if ( ( $layout == '' ) || ( !is_array( $arrFields ) ) ) {
            return false;
        }

        // Auto-Sanitize the input data
        foreach ( $arrFields as $field => $value ) {
            $fields[$this->fm_escape_string( $field )] = $this->fm_escape_string( $value );
        }

        $addCmd = $this->fm->newAddCommand( $this->fm_escape_string( $layout ), $fields );
        $result = $addCmd->execute();

        if ( $this->isError( $result ) === 0 ) {
            $blOut = true;
        } else {
            return $this->isError( $result );
        }

        unset( $addCmd, $result );
        return $blOut;
    }

    /**
     * Gets the layout names within a Database
     * 
     * @return  array
     */
    public function get_layout_names() {
        return $this->fm->listLayouts();
    }

    /**
     * Alias of 'select' 
     * 
     * @param   string  $layout
     * @param   array   $arrSearchCriteria
     * 
     * @return  array
     */
    public function find( $layout, $arrSearchCriteria ) {
        return $this->select( $layout, $arrSearchCriteria );
    }

    /**
     * Counts the number of items in the given array
     * 
     * @param   array   $arrResult
     * 
     * @return  int
     */
    public function fm_num_rows( $arrResult ) {
        $intOut = 0;
        if ( is_array( $arrResult ) ) {
            foreach ( $arrResult as $result ) {
                $intOut = count( $result );
            }
        } else {
            $intOut = count( $arrResult );
        }

        return $intOut;
    }


    /**
     * Runs a script on the layout     
     * 
     * @param   string  $layout
     * @param   string  $scriptName
     * @param   array   $params (optional)
     * 
     * @return  bool
     */
    public function runScript( $layout, $scriptName, $params = array() ) {
        if ( ( empty( $layout ) ) || ( empty( $scriptName ) ) ) {
            return false;
        }

        if ( $this->fm->newPerformScriptCommand( $layout, $scriptName, $params ) ) {
            return true;
        }
        return false;
    }

    /**
     * Get the ID of the last updated/inserted field
     * 
     * @return  int
     */
    public function getLastID() {
    }

    /**
     * Get the ID of the last updated/inserted field
     * 
     * @return  int
     */
    public function getLastID() {
    }

    /**
     * Deletes a record from the table/layout with the given record ID
     *    
     * @return  bool
     */
    public function deleteRecordByID( $layout, $iRecordID ) {

        foreach( func_get_args() as $arg ){
            if( empty( $arg ) || $arg == '' ){
                return false;
            }
        }

        $delete = $this->fm->newDeleteCommand( $layout, $iRecordID ); 
        $delResult = $delete->execute();

        if( $this->isError( $delResult ) ){
            return $this->isError( $delResult );
        }

        unset( $delete, $delResult, $layout, $iRecordID );
        return true;
    }


    /**
     * Deletes a record where the search criteria matches
     * 
     * @param   string  $layout
     * @param   array   $arrSearchCriteria
     * 
     * @return int      The amount of records deleted || errorCode
     */
    public function delete( $layout, $arrSearchCriteria ){
        if( empty( $layout ) || empty( $arrSearchCriteria ) ){
            return 0;
        }

        //Performs the search
        $search = $this->select( $layout, $arrSearchCriteria );

        if( empty( $search ) ){
            return 0;
        }

        //Checks for an error
        if( array_key_exists( 'errorCode', $search ) ){
            return $search['errorCode'];
        }

        $i = 0;
        foreach( $search as $records ){

            $delete = $this->deleteRecordByID( $layout, $records['rec_id'] );

            // Errors return as strings so thats why the check is to make sure its an integer
            if( !is_int( $delete ) ){
                return $delete; //replace $delete with 0; after testing
            }
            $i++;
        }

        return $i;
    }

    /*
     * Gets the ID of the record in the last Select
     * 
     * @return  int
     */
    public function getRecordId() {
        return $this->lastObj->getRecordId();
    }

    /**
     * Escapes a string manually
     * 
     * @param   string  $input
     * 
     * @return  string
     */
    public function fm_escape_string( $input ) {
        if ( is_array( $input ) ) {
            return array_map( __method__, $input );
        }

        if ( !empty( $input ) && is_string( $input ) ) {
            return str_replace( array( '\\', '/', "\0", "\n", "\r", "'", '"', "\x1a", '<', '>' ), array( '\\\\', '\/', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z', '\<\\/', '\\/>' ), $input );
        }
        return $input;
    }
}
?>

Please let me know what you think!

[Edit] Ps. Please note that this is still a work in progress.

\$\endgroup\$
4
  • \$\begingroup\$ Please include all relevant code in your post, as suggested in the FAQ \$\endgroup\$ – yannis Jan 26 '12 at 15:53
  • 1
    \$\begingroup\$ Done. The reason I didn't was because its like 600 lines long :P \$\endgroup\$ – DarkMantis Jan 26 '12 at 15:55
  • \$\begingroup\$ I did an unconventional edit on your question, removing @author, @since and @version comments, to prune the code a bit. I'm sure you noticed, since you did a subsequent edit, I'm only bringing it up because as it was rather unconventional I asked a Meta question about it and would love to hear what you think. \$\endgroup\$ – yannis Jan 26 '12 at 16:53
  • \$\begingroup\$ Yeah that's nto a problem, I Just posted everything that was in my file. Obviously its good to have DocBlocks in your code, but yeah point taken. Maybe it was right to remove the comment. As for the other post, I think that is a good question! Thanks again! \$\endgroup\$ – DarkMantis Jan 26 '12 at 17:18
1
\$\begingroup\$
  1. FMDB - A longer class name would help a lot (as a reader).

  2. If the following fields are constants they shouldn't be public fields. I'd give them uppercase names too.

    public $ltet = '≤';
    public $gtet = '≥';
    
  3. The following comments look unnecessary:

    //$debugStr .= '<script type="text/javascript">console.log("[Debug] ' . $func . ' - ['. $n .'] -> ' . $m . ' ");</script>';
    fwrite( $fo, '[Debug ' . date( 'd-m-Y H:i:s' ) . '] ' . $func . ' - ['. $n .'] -> ' . $m . "\n\r" );
    
    //$debugStr .= '<script type="text/javascript">console.log("[Debug] ' . $func . ' - ['. $k .'] ' . $v . ' ");</script>';
    fwrite( $fo,  '[Debug '. date( 'd-m-Y H:i:s' ) . '] ' . $func . ' - ['. $k .'] ' . $v . "\n\r" );
    

    The first part of the string ('[Debug '. date( 'd-m-Y H:i:s' ) . '] ' . $func . ') could be extracted to a common local variable or function.

  4. fopen and fwrite returns with FALSE on error. You should check this.

  5. I'd modify the insert function a little bit:

    public function insert( $layout, $arrFields ) {
        if ( ( $layout == '' ) || ( !is_array( $arrFields ) ) ) {
            return false;
        }
        ...
        if ( $this->isError( $result ) !== 0 ) {
            return $this->isError( $result );
        }
        ...
        return true;
    }
    

    The $blOut variable is unnecessary.

  6. Using guard clauses makes the code flatten and more readable. An example from updateRecordByID:

    if ( $this->isError( $findReq ) === 0 ) {
    
        foreach ( $findReq as $record ) {
            foreach ( $arrFields as $f => $v ) {
                $record->setField( $this->fm_escape_string( $f ), $this->fm_escape_string( $v ) );
            }
            $commit = $record->commit();
        }
    
        if ( $this->isError( $commit ) === 0 ) {
            $blOut = true;
        } else {
            return $this->isError( $commit );
        }
    } else {
        return $this->isError( $findReq );
    }
    

    It could be written like this:

    if ( $this->isError( $findReq ) !== 0 ) {
        return $this->isError( $findReq );
    }
    
    foreach ( $findReq as $record ) {
        foreach ( $arrFields as $f => $v ) {
            $record->setField( $this->fm_escape_string( $f ), $this->fm_escape_string( $v ) );
        }
        $commit = $record->commit();
    }
    
    if ( $this->isError( $commit ) !== 0 ) {
        return $this->isError( $commit );
    }
    $blOut = true;
    

    References: Replace Nested Conditional with Guard Clauses in Refactoring: Improving the Design of Existing Code; Flattening Arrow Code

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thank you for your comments. I didn't mean to include the comments (issue 3 that you mentioned) that would have been an accident. As for the rest, I will include this in my next push to github. Thanks again! \$\endgroup\$ – DarkMantis Jan 27 '12 at 8:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.