WeberDev.com PHP and MySQL Code

LOG IN
BEGINNER GUIDES  |  PHP CLASSES  |  CODE SEARCH  |  ARTICLES SEARCH  |  PHP FORUMS  |  PHP MANUAL  |  PHP FUNCTIONS LIST  |  WEB SITE TEMPLATES
Start typing to search for PHP and MySQL Code Snippets and Articles Search
Submit a code Example / Snippet Submit Your Code
Search Engine Optimization Monitor SEO Monitor
Web Site UpTime Monitor UpTime Monitor
WeberDev's Monthly code contest PHP Code Contest
Your Personal Examples List My Favorite Examples
Your Personal Articles List My Favorite Articles
Edit Account Info Update Your Profile
PHP Code Search
Web Development Forums
Learn MySQL Playing Trivia
PHPBB2 Templates
Web Development Index
PHP Web Logs (BLogs)
Web Development Resources
Web Development Content
PHPClasses
PHP Editor
PHP Jobs
Vision.To Design
Ajax Tutorials
PHP Programming Help
PHP/MySQL Programming
Webmaster Resources
Webmaster Forum
XML meta language
website builder
Submit Site
Forex Trading Online forex trading platform

Go Back Add a Comment Send this example to a friend Add this Article to your personal favoritest for easy future access to your favorite Code Examples and Articles. Submit a code example Print this code example.
BACK ADD A COMMENT SEND TO A FRIEND ADD TO MY FAVORITES ADD CODE EXAMPLES PRINT
Title : [PHP5] aDB PDO LIKE Database Abstraction. Switch easily from one db server to another, strong errors management, manage transactions, queries preparation and more.
Categories : PHP, PHP Classes, Databases, MS SQL Server, MySQL Click here to Update Your Picture
Johan Barbier
Date : May 25th 2007
Grade : 1 of 5 (graded 1 times)
Viewed : 4926
File : 4645.zip
Images : No Images for this code example.
Search : More code by Johan Barbier
Action : Grade This Code Example
Tools : My Examples List

  Submit your own code examples 
 

This package allows you to switch easily from one db server to another one, from a db connection to another one, keeping the minimum objects necessary, with its factory. It allows a strong errors management thanks to several exception types. Its methods let you, in addition to usual db classes functionnalities, manage transactions, queries preparation, queries results sets limitation...

<?php
/**
* class aDBException extends Exception
* @author johan <johan.barbier@gmail.com>
* @version 20070524
*/
class aDBException extends Exception {
   
/**
     * Class in which Exception was caught
     *
     * @var string
     */
   
private $sCallerClass = null;
   
   
/**
     * Method in which Exception was caught
     *
     * @var string
     */
   
private $sCallerFunc = null;
   
   
/**
     * public function __construct
     * Constructor.
     *
     * @param string $sMessage : Exception message
     * @param string $sClass : Class in which Exception was caught
     * @param string $sFunction : Method in which Exception was caught
     */
   
public function __construct ($sMessage, $iCode, $sClass = 'unknown', $sFunction = 'unknown') {
       
$this -> sCallerClass = $sClass;
       
$this -> sCallerFunc = $sFunction;
       
parent::__construct ($sMessage, $iCode);
    }
   
   
/**
     * public function __toString
     * Display Exception message
     *
     * @return string
     */
   
public function __toString() {
        return
$this -> sCallerClass.' :: '.$this -> sCallerFunc.'() : ['.$this -> code.'] '.$this->message;
    }
 
}
/**
* class aDBExceptionIllegalClass extends aDBException
* @author johan <johan.barbier@gmail.com>
* @version 20070524
*/
class aDBExceptionIllegalClass extends aDBException {
   
/**
     * Class constants : Exception messages and codes
     *
     */
   
const ILLEGAL_CLASS_NAME = '{CLASS} is not implemented';
    const
UNEXPECTED_INSTANCE_ERROR = 'Unexpected instance error';
   
    const
CODE_ILLEGAL_CLASS_NAME = 0;
    const
CODE_UNEXPECTED_INSTANCE_ERROR = 1;
}

/**
* class aDBExceptionTypesError extends aDBException
* @author johan <johan.barbier@gmail.com>
* @version 20070524
*/
class aDBExceptionTypesError extends aDBException {
   
/**
     * Class constants : Exception messages and codes
     *
     */
   
const MUST_BE_AN_ARRAY = '{PARAM} must be an array';
    const
MUST_BE_AN_INT = '{PARAM} must be an integer';
   
    const
CODE_MUST_BE_AN_ARRAY = 0;
    const
CODE_MUST_BE_AN_INT = 1;
}

/**
* class aDBExceptionInvalidClassCalls extends aDBException
* @author johan <johan.barbier@gmail.com>
* @version 20070524
*/
class aDBExceptionInvalidClassCalls extends aDBException {
   
/**
     * Class constants : Exception messages and codes
     *
     */
   
const NO_QUERY_TO_PREPARE = 'No query has been prepared';
    const
NEEDLE_NOT_FOUND = '{NEEDLE} was not found in prepared query {QUERY}';
    const
PARAM_TYPE_NOT_FOUND = 'Parameter type asked for query preparation does not exist';
    const
PROP_NOT_GETABLE = '{PROP} is not a getable property';
    const
INVALID_OPTION = '{OPTION} is not a valid option';
   
    const
CODE_NO_QUERY_TO_PREPARE = 0;
    const
CODE_NEEDLE_NOT_FOUND = 1;
    const
CODE_PARAM_TYPE_NOT_FOUND = 2;
    const
CODE_PROP_NOT_GETABLE = 3;
    const
CODE_INVALID_OPTION = 4;
}

/**
* class aDBExceptionDbConnectorErrors extends aDBException
* @author johan <johan.barbier@gmail.com>
* @version 20070524
*/
class aDBExceptionDbConnectorErrors extends aDBException {
   
/**
     * Class constants : Exception messages and codes
     *
     */
   
const CONNEXION_FAILED = 'Connexion failed with message [{MSG}]';
    const
QUERY_FAILED = 'Query [{QRY}] failed with message [{MSG}]';
    const
FETCH_FAILED = 'Fetch failed with message [{MSG}]';
    const
INVALID_SEEK_POSITION = 'Invalid seek position ({OFFSET})';
    const
INVALID_QRY_RESOURCE = 'Invalid query resource';
    const
REQUEST_ERROR = 'Undefined error in the request';
    const
CONNECTION_LINK_MISSING = 'No database connection found';
    const
COULD_NOT_FREE_RESULT = 'Free result failed with message [{MSG}]';
   
    const
CODE_CONNEXION_FAILED = 0;
    const
CODE_QUERY_FAILED = 1;
    const
CODE_FETCH_FAILED = 2;
    const
CODE_INVALID_SEEK_POSITION = 3;
    const
CODE_INVALID_QRY_RESOURCE = 4;
    const
CODE_REQUEST_ERROR = 5;
    const
CODE_CONNECTION_LINK_MISSING = 6;
    const
CODE_COULD_NOT_FREE_RESULT = 7;
}

/**
* class aDBFactory
* @author johan <johan.barbier@gmail.com>
* @version 20070524
*/
class aDBFactory {
   
   
/**
     * static property : array of aDB instances
     *
     * @var array of aDB object
     */
   
private static $_instance;
   
   
/**
     * public static getInstance
     * Factory.
     * Checks that the requested DB type is implemented.
     * Then, checks if there is an existing instance of it. If not, creates it with configuration and options if any.
     * If yes, gets it! Checks if there is a new configuration to apply and apply it if so. Same story for the options.
     * Returns the instance...
     *
     * @param string $saDBType : name of the requested aDB class
     * @param array $aConConf : array of aDB connection configuration
     * @param unknown_type $aOptions : array of aDB options
     * @return aDB object : the requested aDB instance
     */
   
public static function getInstance ($saDBType, $aConConf = null, $aOptions = null) {
        if (!
class_exists($saDBType)) {
            throw new
aDBExceptionIllegalClass (str_replace ('{CLASS}', $saDBType, aDBExceptionIllegalClass::ILLEGAL_CLASS_NAME), aDBExceptionIllegalClass::CODE_ILLEGAL_CLASS_NAME, get_class($this), __FUNCTION__);
        }
        if (isset (
self::$_instance[$saDBType])) {
            if (!
self::$_instance[$saDBType] instanceof $saDBType) {
                throw new
aDBExceptionIllegalClass (aDBExceptionIllegalClass::UNEXPECTED_INSTANCE_ERROR, aDBExceptionIllegalClass::CODE_UNEXPECTED_INSTANCE_ERROR, get_class($this), __FUNCTION__);
            } else {
                if (!
is_null ($aConConf)) {
                   
self::$_instance[$saDBType] -> connect ($aConConf);
                }
                if (!
is_null ($aOptions)) {
                    foreach (
$aOptions as $sOption => $mValue) {
                       
self::$_instance[$saDBType] -> setOption ($sOption, $mValue);
                    }
                }
                return
self::$_instance[$saDBType];
            }
        } else {
           
self::$_instance[$saDBType] = new $saDBType ($aConConf, $aOptions);
            return
self::$_instance[$saDBType];
        }
    }
}

/**
* class sqlIterator implements Iterator, SeekableIterator, Countable
* @author johan <johan.barbier@gmail.com>
* @version 20070524
*/
class sqlIterator implements Iterator, SeekableIterator, Countable {

   
/**
     * Number of items to retrieve
     *
     * @var integer
     */
   
private $iCount;

   
/**
     * Starting offset
     *
     * @var integer
     */
   
private $iOffset = 0;

   
/**
     * Total number of items for the request
     *
     * @var integer
     */
   
private $iMax;

   
/**
     * Current internal position
     * @var integer
     */
   
private $iPos = -1;
   
   
/**
     * aDB object
     *
     * @var aDB
     */
   
private $aDB;
   
   
/**
     * Query resource
     *
     * @var resource
     */
   
private $rQry;
   
   
/**
     * fetch mode
     *
     * @var aDB class constant
     */
   
private $aDB_MODE;
    private
$keptaDB_MODE;
   
   
/**
     * Output
     *
     * @var mixed
     */
   
private $mOutput = false;

   
/**
     * public function __construct
     * Constructor
     * set some parameters
     *
     * @param aDB $aDB : aDB object
     * @param resource $rQry : query resource
     * @param aDB class constant $aDB_MODE : fetch mode
     * @param integer $iOffset : starting offset
     * @param integer $iCount : fetch length
     * @param boolean $bIsSeekable : resource must be rewinded or not
     */
   
public function __construct (aDB $aDB, $rQry, $aDB_MODE = aDB::BOTH, $iOffset = 0, $iCount = null, $bIsSeekable = true) {
        if (!
is_int($iOffset)) {
           
$this -> aDB -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '4d parameter', aDBExceptionTypesError::MUST_BE_AN_INT), aDBExceptionTypesError::CODE_MUST_BE_AN_INT, get_class($this), __FUNCTION__);
           
$iOffset = 0;
        }
        if (!
is_int($iCount) && !is_null ($iCount)) {
           
$this -> aDB -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '5d parameter', aDBExceptionTypesError::MUST_BE_AN_INT), aDBExceptionTypesError::CODE_MUST_BE_AN_INT, get_class($this), __FUNCTION__);
           
$iCount = null;
        }
       
$this -> keptaDB_MODE = $aDB_MODE;
       
$this -> aDB_MODE = (($iMode = aDB::FETCH_GROUP^$aDB_MODE) !== 0 && $iMode !== $aDB_MODE && $iMode < $aDB_MODE)?$iMode:$aDB_MODE;
       
$this -> rQry = $rQry;
       
$this -> aDB = $aDB;
       
$this -> iOffset = $iOffset;
       
$this -> iCount = $iCount;
       
$this -> iMax = $this -> count ();
        if (
true === $bIsSeekable && $this -> aDB_MODE !== aDB::FETCH_EXTRACT) {
           
$this -> rewind ();
        }
    }

   
/**
     * publid function current
     * returns current result set
     *
     * @return array
     */
   
public function current () {
        if (
$this -> aDB_MODE === aDB::FETCH_EXTRACT) {
            if (
is_array ($this -> mOutput)) {
                foreach (
$this -> mOutput as $sK => $sV) {
                    global $
$sK;
                    $
$sK = $sV;
                }
            }
            return
true;
        } else {
            switch (
$this -> keptaDB_MODE) {
                case
aDB::FETCH_GROUP:
                case
aDB::FETCH_GROUP|aDB::BOTH:
                   
$mFirst = array_shift ($this -> mOutput);
                   
array_shift ($this -> mOutput);
                    return array (
$mFirst => $this -> mOutput);
                    break;
                case
aDB::FETCH_GROUP|aDB::FETCH_ASSOC:
                case
aDB::FETCH_GROUP|aDB::FETCH_NUM:
                   
$mFirst = array_shift ($this -> mOutput);
                    return array (
$mFirst => $this -> mOutput);
                    break;
                default:
                    return
$this -> mOutput;
                    break;
            }
        }
    }
   
   
/**
     * public function next
     * goes to the next result set
     *
     */
   
public function next () {
       
$this -> iPos ++;
        if (
$this -> aDB_MODE === aDB::FETCH_EXTRACT) {
           
$this -> mOutput = $this -> aDB -> __fetch ($this -> rQry, aDB::FETCH_ASSOC);
        } else {
           
$this -> mOutput = $this -> aDB -> __fetch ($this -> rQry, $this -> aDB_MODE);
        }
    }

   
/**
     * public function valid
     * Checks the validity of the current position
     *
     * @return boolean
     */
   
public function valid () {
        if ((
$this -> iOffset + $this -> iPos) >= $this -> iMax) {
            return
false;
        }
        if (!
is_null ($this -> iCount) && $this -> iCount > 0 && $this -> iPos >= $this -> iCount) {
            return
false;
        }
        if (
false === $this -> mOutput) {
           
$this -> next ();
        }
        return
true;
    }

   
/**
     * public function rewind
     * moves the offset to the first position
     *
     */
   
public function rewind () {
       
$this -> seek ($this -> iOffset);
    }
   
   
/**
     * public function seek
     * seeks a given offset
     *
     * @param integer $iOffset
     */
   
public function seek ($iOffset) {
        if (
false === $this -> aDB -> __seek ($iOffset)) {
           
$this -> aDB -> interceptException ('aDBExceptionDbConnectorErrors', str_replace ('{OFFSET}', $iOffset, aDBExceptionDbConnectorErrors::INVALID_SEEK_POSITION), aDBExceptionDbConnectorErrors::CODE_INVALID_SEEK_POSITION, get_class($this), __FUNCTION__);
            return
false;
        }
       
$this -> iOffset = $iOffset;
       
$this -> iPos = -1;
        return
true;
    }
   
/**
     * public function key
     * Returns the current internal position
     *
     * @return integer
     */
   
public function key () {
        return
$this -> iPos;
    }
   
   
/**
     * public function count
     * count the total number of result sets
     *
     * @return integer
     */
   
public function count () {
        return
$this -> aDB -> count ($this -> rQry);
    }

   
/**
     * public function getOffset
     * Returns the current request position
     *
     * @return integer
     */
   
public function getOffset () {
        return
$this -> iPos + $this -> iOffset;
    }
}

/**
* abstract class aDB
* @author johan <johan.barbier@gmail.com>
* @version 20070521
*/
abstract class aDB {
   
   
/**
     * fetch modes
     *
     */
   
const BOTH = 0;
    const
FETCH_ASSOC = 1;
    const
FETCH_NUM = 2;
    const
FETCH_GROUP = 4;
    const
FETCH_EXTRACT = 8;
   
   
/**
     * Allowed parameter bindings
     *
     */
   
const PARAM_STR = 1000;
    const
PARAM_INT = 1001;
   
   
/**
     * DB Configuration
     *
     * @var array
     */
   
protected $aConConf = array (
   
'HOST' => '', 'LOGIN' => '', 'PWD' => '', 'DB' => '');
   
   
/**
     * aDB options
     *
     * @var array
     */
   
protected $aOptions = array (
   
'AUTOCONNECT' => true,
   
'EXCEPTION_ON_ERROR' => true
   
);
   
   
/**
     * Fetch length
     *
     * @var integer
     */
   
protected $iFetchLength;
   
   
/**
     * Starting offset
     *
     * @var integer
     */
   
protected $iOffset = 0;
   
   
/**
     * DB connection link resource
     *
     * @var resource
     */
   
protected $rLink;
   
   
/**
     * Query
     *
     * @var string
     */
   
protected $sQuery;
   
   
/**
     * Errors log when no exception
     *
     * @var array
     */
   
protected static $aErrorLog;
   
   
/**
     * public function __construct
     * Constructor.
     * Automatic connection if AUTOCONNECT option is true
     *
     * @param array $aConConf
     * @param array $aOptions
     */
   
public function __construct ($aConConf = null, $aOptions = null) {
        if (
is_array ($aOptions)) {
            foreach (
$aOptions as $sK => $sV) {
                if (isset (
$this -> aOptions[$sK])) {
                   
$this -> aOptions[$sK] = $sV;
                }
            }       
        }
        if (
true === $this -> aOptions['AUTOCONNECT'] && !is_null ($aConConf)) {
           
$this -> connect ($aConConf);
        }
    }
   
   
/**
     * public function set_limit
     * Sets a limit to the query : starting offset and fetch length
     * Is this method is called without any parameter, it will cancel previous set_limit call (useful if you used a limitation, and then do not want any more limitation)
     *
     * @param integer $iOffset
     * @param integer $iCount
     */
   
public function set_limit ($iOffset = 0, $iCount = null) {
        if (!
is_int($iOffset)) {
           
$this -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '1st parameter', aDBExceptionTypesError::MUST_BE_AN_INT), aDBExceptionTypesError::CODE_MUST_BE_AN_INT, get_class($this), __FUNCTION__);
            return
false;
        }
        if (!
is_int($iCount) && !is_null ($iCount)) {
           
$this -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '2d parameter', aDBExceptionTypesError::MUST_BE_AN_INT), aDBExceptionTypesError::CODE_MUST_BE_AN_INT, get_class($this), __FUNCTION__);
            return
false;
        }
       
$this -> iFetchLength = $iCount;
       
$this -> iOffset = $iOffset;
        return
true;
    }
   
   
/**
     * public function next_limit
     * Update the limit : aDB::iFetchLength remains the same, but aDB::iOffset is incremennted with aDB::iFetchLength.
     * So, the offset is positionned on the very next result set.
     */
   
public function next_limit () {
        if (!
is_int($this -> iOffset)) {
           
$this -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '1st parameter', aDBExceptionTypesError::MUST_BE_AN_INT), aDBExceptionTypesError::CODE_MUST_BE_AN_INT, get_class($this), __FUNCTION__);
            return
false;
        }
        if (!
is_int($this -> iFetchLength) && !is_null ($this -> iFetchLength)) {
           
$this -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '2d parameter', aDBExceptionTypesError::MUST_BE_AN_INT), aDBExceptionTypesError::CODE_MUST_BE_AN_INT, get_class($this), __FUNCTION__);
            return
false;
        }
       
$this -> iOffset += $this -> iFetchLength;
        return
true;
    }
   
   
/**
     * public function connect
     * Connects to the DB server
     * If DB NAME has been set in the configuration, automatically selects it.
     *
     */
   
public function connect ($aConConf) {
        if (
is_array ($aConConf)) {
            foreach (
$aConConf as $sK => $sV) {
                if (isset (
$this -> aConConf[$sK])) {
                   
$this -> aConConf[$sK] = $sV;
                }
            }
        } else {
           
$this -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '1st parameter', aDBExceptionTypesError::MUST_BE_AN_ARRAY), aDBExceptionTypesError::CODE_MUST_BE_AN_ARRAY, get_class($this), __FUNCTION__);
            return
false;
        }
        if (
false === ($this -> rLink = $this -> _connect ())) {
           
$this -> interceptException ('aDBExceptionDbConnectorErrors', str_replace ('{MSG}', $this -> _errorMsg (), aDBExceptionDbConnectorErrors::CONNEXION_FAILED), aDBExceptionDbConnectorErrors::CODE_CONNEXION_FAILED, get_class($this), __FUNCTION__);
            return
false;
        }
        if (!empty (
$this -> aConConf['DB'])) {
           
$this -> select_db ($this -> aConConf['DB']);
        }
        return
true;
    }
   
   
/**
     * public function select_db
     * Selects a db
     *
     * @param string $sDbName
     */
   
public function select_db ($sDbName = null) {
        if (!isset (
$this -> rLink)) {
           
$this -> interceptException ('aDBExceptionDbConnectorErrors', aDBExceptionDbConnectorErrors::CONNECTION_LINK_MISSING, aDBExceptionDbConnectorErrors::CODE_CONNECTION_LINK_MISSING, get_class($this), __FUNCTION__);
            return
false;
        }
        if (!
is_null ($sDbName)) {
           
$this -> aConConf['DB'] = $sDbName;
        }
        if (
false === $this -> _select_db ($this -> aConConf['DB'])) {
           
$this -> interceptException ('aDBExceptionDbConnectorErrors', str_replace ('{MSG}', $this -> _errorMsg (), aDBExceptionDbConnectorErrors::CONNEXION_FAILED), aDBExceptionDbConnectorErrors::CODE_CONNEXION_FAILED, get_class($this), __FUNCTION__);
            return
false;
        }
        return
true;
    }
   
   
/**
     * public function query
     * Queries the DB server
     *
     * @param string $sQuery : query string
     * @param boolean $bOverWriteQry : is sets to true, overwrites aDB::sQuery property; if not, does nothing
     * @return resource : query resource
     */
   
public function query ($sQuery, $bOverWriteQry = true) {
        if (!isset (
$this -> rLink)) {
           
$this -> interceptException ('aDBExceptionDbConnectorErrors', aDBExceptionDbConnectorErrors::CONNECTION_LINK_MISSING, aDBExceptionDbConnectorErrors::CODE_CONNECTION_LINK_MISSING, get_class($this), __FUNCTION__);
            return
false;
        }
        if (
false === ($rRes = @$this -> _query ($sQuery))) {
           
$this -> interceptException ('aDBExceptionDbConnectorErrors', str_replace (array ('{QRY}', '{MSG}'), array ($sQuery, $this -> _errorMsg ()), aDBExceptionDbConnectorErrors::QUERY_FAILED), aDBExceptionDbConnectorErrors::CODE_QUERY_FAILED, get_class($this), __FUNCTION__);
            return
false;
        }
        if (
true === $bOverWriteQry) {
           
$this -> rQry = $rRes;
        }
        return
$rRes;
    }
   
   
/**
     * public function fetch
     * using current query resource or given query resource, instanciates an iterator to move through result sets
     *
     * @param aDB Class Constant $aDB_MODE : fetch mode
     * @param resource $rQry : query resource
     * @param boolean $bIsSeekable : if sets to true, query is seekable
     * @return sqlIterator : the Iterator
     */
   
public function fetch ($aDB_MODE = aDB::BOTH, $rQry = null, $bIsSeekable = true) {
        if (
is_null ($rQry) && is_null ($this -> rQry)) {
           
$this -> interceptException ('aDBExceptionDbConnectorErrors', aDBExceptionDbConnectorErrors::INVALID_QRY_RESOURCE, aDBExceptionDbConnectorErrors::CODE_INVALID_QRY_RESOURCE, get_class($this), __FUNCTION__);
            return
false;
        }
        if (!
is_null ($rQry)) {
            return new
sqlIterator ($this, $rQry, $aDB_MODE, $this -> iOffset, $this -> iFetchLength, $bIsSeekable);
        } else {
            return new
sqlIterator ($this, $this -> rQry, $aDB_MODE, $this -> iOffset, $this -> iFetchLength, $bIsSeekable);
        }
    }

   
/**
     * public function fetchColumn
     * using current query resource or given query resource, returns the value of a given column
     *
     * @param integer $iColumn : column number
     * @param resource $rQry : query resource
     * @param boolean $bIsSeekable : if sets to true, query is seekable
     * @return mixed : column value
     */
   
public function fetchColumn ( $iColumn = 0, $rQry = null, $bIsSeekable = true) {
        if (!
is_int($iColumn)) {
           
$this -> interceptException ('aDBExceptionTypesError', str_replace ('{PARAM}', '2d parameter', aDBExceptionTypesError::MUST_BE_AN_INT), aDBExceptionTypesError::CODE_MUST_BE_AN_INT, get_class($this), __FUNCTION__);
            return
false;
        }
        if (
is_null (