final public function exportClassAnalysis(
        $recursiveScan = true)
    {
        $result = new ClassAnalysis();

        // All problems with OUR class will get filed under our class name.
        $definitionSource = get_class($this);

        // Ensure that all object-data properties exist in our class definition,
        // and that their values can be converted as described in the class map.
        foreach ($this->_objectData as $propName => $value) {
            // Check if property exists in the class map and get its definition.
            // NOTE: Normally, this function can throw, but not with the way we
            // are calling it, because we KNOW that the property exists in at
            // least the object data, so we DON'T have to catch any errors!
            $propDef = $this->_getPropertyDefinition($propName);

            // Regardless of whether it's defined or not, we MUST now "get" it
            // to validate/convert the property's contents, to ensure it's safe.
            try {
                // Trying to "get" the property forces complete validation of
                // the internal property data and non-recursively creates all
                // sub-objects (if any). In case of any errors, this call will
                // throw an exception with an error description.
                // NOTE: Calling this getter is EXTREMELY important even for
                // UNDEFINED (untyped) properties, because it ensures that no
                // illegal data values (such as Resources or non-LazyJsonMapper
                // objects) exist within the data even in undefined properties.
                // NOTE: In the case of arrays, it validates the whole
                // arrayDepth and ensures that the array is well-formed and
                // contains the exact type at the exact depth specified (but it
                // accepts NULL values anywhere in the chain of arrays, and it
                // accepts empty arrays at non-max depth). It also verifies
                // type-coercion to built-in PHP types. And in the case of
                // objects, it verifies that they are successfully constructed.
                $value = $this->_getProperty($propName); // Throws.

                // Recursively check all internal objects to make sure they also
                // have all properties from their own raw JSON data.
                // NOTE: Nothing in this sub-block throws any exceptions, since
                // everything was validated by _getProperty(). And the deeper
                // exportClassAnalysis() calls don't throw anything either.
                if ($recursiveScan && $value !== null && $propDef->isObjectType) {
                    // NOTE: We don't have to validate array-depth/type
                    // correctness, since _getProperty() already did that for
                    // us. Nothing will be isObjectType unless it's really a
                    // LazyJsonMapper class. Also, since the $value is not NULL
                    // (see above) and it's an "array of [type]" property, then
                    // we KNOW the $value is_array(), since it validated as OK.
                    if ($propDef->arrayDepth > 0) { // Array of objects.
                        array_walk_recursive($value, function (&$obj) use (&$result) {
                            if (is_object($obj)) { // Could be inner NULL too.
                                $result->mergeAnalysis($obj->exportClassAnalysis());
                            }
                        });
                    } else { // Non-"array of" object property.
                        $result->mergeAnalysis($value->exportClassAnalysis());
                    }
                }
            } catch (LazyJsonMapperException $e) {
                // Unable to get the value of this property... which usually
                // means that the property cannot be type-coerced as requested,
                // or that the JSON data doesn't match the definition (such as
                // having a deeper JSON array than what the definition says), or
                // that invalid data was encountered (such as encountering an
                // internal non-Lazy object/resource instead of the expected
                // data), or that the property's class could not be constructed
                // (which may really only happen due to a user's custom _init()
                // function failing, since our own classmap's compilation has
                // already taken care of successfully verifying the classmap
                // compilation of ALL classes referred to by our ENTIRE property
                // hierarchy). All of those are user-caused errors...

                // We'll save the exception details message as-is...
                // TODO: This also means that undefined properties containing
                // weird data (objects or resources) would throw above and get
                // logged as "bad definition" despite NOT having any definition.
                // We may want to add a check here for UndefinedProperty and
                // prepend something to the exception message in that case, to
                // tell the user that their UNDEFINED property contained the bad
                // data. But seriously, they can NEVER have such invalid data
                // inside of a real json_decode() input array, so nobody sane is
                // going to be warned of "bad definition" for their undefined
                // properties! And we can't just log it as "missing" since we
                // NEED all "bad data" to be logged as bad_definitions, since
                // that's what our various data validators look at to detect
                // SERIOUS data errors! Besides, their missing definition WILL
                // also get logged under "missing_definitions" in the next step.
                $result->addProblem(
                    $definitionSource,
                    'bad_definitions',
                    $e->getMessage()
                );
            }

            // Now check if we lacked a user-definition for this JSON property.
            if ($propDef instanceof UndefinedProperty) {
                // NOTE: We need the (string) casting in case of int-keys. Which
                // can happen if the user gives us a manually created, non-JSON
                // constructor array containing non-associative integer keys.
                $result->addProblem(
                    $definitionSource,
                    'missing_definitions',
                    (string) $propName
                );
            }
        }

        // Nicely sort the problems and remove any duplicates.
        $result->sortProblemLists();

        return $result;
    }
final public function exportClassAnalysis(
        $recursiveScan = true)
    {
        $result = new ClassAnalysis();

        // All problems with OUR class will get filed under our class name.
        $definitionSource = get_class($this);

        // Ensure that all object-data properties exist in our class definition,
        // and that their values can be converted as described in the class map.
        foreach ($this->_objectData as $propName => $value) {
            // Check if property exists in the class map and get its definition.
            // NOTE: Normally, this function can throw, but not with the way we
            // are calling it, because we KNOW that the property exists in at
            // least the object data, so we DON'T have to catch any errors!
            $propDef = $this->_getPropertyDefinition($propName);

            // Regardless of whether it's defined or not, we MUST now "get" it
            // to validate/convert the property's contents, to ensure it's safe.
            try {
                // Trying to "get" the property forces complete validation of
                // the internal property data and non-recursively creates all
                // sub-objects (if any). In case of any errors, this call will
                // throw an exception with an error description.
                // NOTE: Calling this getter is EXTREMELY important even for
                // UNDEFINED (untyped) properties, because it ensures that no
                // illegal data values (such as Resources or non-LazyJsonMapper
                // objects) exist within the data even in undefined properties.
                // NOTE: In the case of arrays, it validates the whole
                // arrayDepth and ensures that the array is well-formed and
                // contains the exact type at the exact depth specified (but it
                // accepts NULL values anywhere in the chain of arrays, and it
                // accepts empty arrays at non-max depth). It also verifies
                // type-coercion to built-in PHP types. And in the case of
                // objects, it verifies that they are successfully constructed.
                $value = $this->_getProperty($propName); // Throws.

                // Recursively check all internal objects to make sure they also
                // have all properties from their own raw JSON data.
                // NOTE: Nothing in this sub-block throws any exceptions, since
                // everything was validated by _getProperty(). And the deeper
                // exportClassAnalysis() calls don't throw anything either.
                if ($recursiveScan && $value !== null && $propDef->isObjectType) {
                    // NOTE: We don't have to validate array-depth/type
                    // correctness, since _getProperty() already did that for
                    // us. Nothing will be isObjectType unless it's really a
                    // LazyJsonMapper class. Also, since the $value is not NULL
                    // (see above) and it's an "array of [type]" property, then
                    // we KNOW the $value is_array(), since it validated as OK.
                    if ($propDef->arrayDepth > 0) { // Array of objects.
                        array_walk_recursive($value, function (&$obj) use (&$result) {
                            if (is_object($obj)) { // Could be inner NULL too.
                                $result->mergeAnalysis($obj->exportClassAnalysis());
                            }
                        });
                    } else { // Non-"array of" object property.
                        $result->mergeAnalysis($value->exportClassAnalysis());
                    }
                }
            } catch (LazyJsonMapperException $e) {
                // Unable to get the value of this property... which usually
                // means that the property cannot be type-coerced as requested,
                // or that the JSON data doesn't match the definition (such as
                // having a deeper JSON array than what the definition says), or
                // that invalid data was encountered (such as encountering an
                // internal non-Lazy object/resource instead of the expected
                // data), or that the property's class could not be constructed
                // (which may really only happen due to a user's custom _init()
                // function failing, since our own classmap's compilation has
                // already taken care of successfully verifying the classmap
                // compilation of ALL classes referred to by our ENTIRE property
                // hierarchy). All of those are user-caused errors...

                // We'll save the exception details message as-is...
                // TODO: This also means that undefined properties containing
                // weird data (objects or resources) would throw above and get
                // logged as "bad definition" despite NOT having any definition.
                // We may want to add a check here for UndefinedProperty and
                // prepend something to the exception message in that case, to
                // tell the user that their UNDEFINED property contained the bad
                // data. But seriously, they can NEVER have such invalid data
                // inside of a real json_decode() input array, so nobody sane is
                // going to be warned of "bad definition" for their undefined
                // properties! And we can't just log it as "missing" since we
                // NEED all "bad data" to be logged as bad_definitions, since
                // that's what our various data validators look at to detect
                // SERIOUS data errors! Besides, their missing definition WILL
                // also get logged under "missing_definitions" in the next step.
                $result->addProblem(
                    $definitionSource,
                    'bad_definitions',
                    $e->getMessage()
                );
            }

            // Now check if we lacked a user-definition for this JSON property.
            if ($propDef instanceof UndefinedProperty) {
                // NOTE: We need the (string) casting in case of int-keys. Which
                // can happen if the user gives us a manually created, non-JSON
                // constructor array containing non-associative integer keys.
                $result->addProblem(
                    $definitionSource,
                    'missing_definitions',
                    (string) $propName
                );
            }
        }

        // Nicely sort the problems and remove any duplicates.
        $result->sortProblemLists();

        return $result;
    }
final public function exportClassAnalysis ( $ recursiveScan = true ) { $ result = new ClassAnalysis ( ) ; // All problems with OUR class will get filed under our class name. $ definitionSource = get_class ( $ this ) ; // Ensure that all object-data properties exist in our class definition, // and that their values can be converted as described in the class map. foreach ( $ this -> _objectData as $ propName => $ value ) { // Check if property exists in the class map and get its definition. // NOTE: Normally, this function can throw, but not with the way we // are calling it, because we KNOW that the property exists in at // least the object data, so we DON'T have to catch any errors! $ propDef = $ this -> _getPropertyDefinition ( $ propName ) ; // Regardless of whether it's defined or not, we MUST now "get" it // to validate/convert the property's contents, to ensure it's safe. try { // Trying to "get" the property forces complete validation of // the internal property data and non-recursively creates all // sub-objects (if any). In case of any errors, this call will // throw an exception with an error description. // NOTE: Calling this getter is EXTREMELY important even for // UNDEFINED (untyped) properties, because it ensures that no // illegal data values (such as Resources or non-LazyJsonMapper // objects) exist within the data even in undefined properties. // NOTE: In the case of arrays, it validates the whole // arrayDepth and ensures that the array is well-formed and // contains the exact type at the exact depth specified (but it // accepts NULL values anywhere in the chain of arrays, and it // accepts empty arrays at non-max depth). It also verifies // type-coercion to built-in PHP types. And in the case of // objects, it verifies that they are successfully constructed. $ value = $ this -> _getProperty ( $ propName ) ; // Throws. // Recursively check all internal objects to make sure they also // have all properties from their own raw JSON data. // NOTE: Nothing in this sub-block throws any exceptions, since // everything was validated by _getProperty(). And the deeper // exportClassAnalysis() calls don't throw anything either. if ( $ recursiveScan && $ value !== null && $ propDef -> isObjectType ) { // NOTE: We don't have to validate array-depth/type // correctness, since _getProperty() already did that for // us. Nothing will be isObjectType unless it's really a // LazyJsonMapper class. Also, since the $value is not NULL // (see above) and it's an "array of [type]" property, then // we KNOW the $value is_array(), since it validated as OK. if ( $ propDef -> arrayDepth > 0 ) { // Array of objects. array_walk_recursive ( $ value , function ( & $ obj ) use ( & $ result ) { if ( is_object ( $ obj ) ) { // Could be inner NULL too. $ result -> mergeAnalysis ( $ obj -> exportClassAnalysis ( ) ) ; } } ) ; } else { // Non-"array of" object property. $ result -> mergeAnalysis ( $ value -> exportClassAnalysis ( ) ) ; } } } catch ( LazyJsonMapperException $ e ) { // Unable to get the value of this property... which usually // means that the property cannot be type-coerced as requested, // or that the JSON data doesn't match the definition (such as // having a deeper JSON array than what the definition says), or // that invalid data was encountered (such as encountering an // internal non-Lazy object/resource instead of the expected // data), or that the property's class could not be constructed // (which may really only happen due to a user's custom _init() // function failing, since our own classmap's compilation has // already taken care of successfully verifying the classmap // compilation of ALL classes referred to by our ENTIRE property // hierarchy). All of those are user-caused errors... // We'll save the exception details message as-is... // TODO: This also means that undefined properties containing // weird data (objects or resources) would throw above and get // logged as "bad definition" despite NOT having any definition. // We may want to add a check here for UndefinedProperty and // prepend something to the exception message in that case, to // tell the user that their UNDEFINED property contained the bad // data. But seriously, they can NEVER have such invalid data // inside of a real json_decode() input array, so nobody sane is // going to be warned of "bad definition" for their undefined // properties! And we can't just log it as "missing" since we // NEED all "bad data" to be logged as bad_definitions, since // that's what our various data validators look at to detect // SERIOUS data errors! Besides, their missing definition WILL // also get logged under "missing_definitions" in the next step. $ result -> addProblem ( $ definitionSource , 'bad_definitions' , $ e -> getMessage ( ) ) ; } // Now check if we lacked a user-definition for this JSON property. if ( $ propDef instanceof UndefinedProperty ) { // NOTE: We need the (string) casting in case of int-keys. Which // can happen if the user gives us a manually created, non-JSON // constructor array containing non-associative integer keys. $ result -> addProblem ( $ definitionSource , 'missing_definitions' , ( string ) $ propName ) ; } } // Nicely sort the problems and remove any duplicates. $ result -> sortProblemLists ( ) ; return $ result ; }
Analyze the entire object and check for undefined or bad JSON properties.

This lets you analyze the object data & map to look for the following:

1. Undefined JSON properties that need to be defined in your classes
so that their existence becomes permanent and therefore safer to use
(since defined properties are always retrievable even if they don't
exist in the current object instance's data). And actually defining
the properties is also the ONLY way that you can enforce a specific
data-type, since undefined properties default to untyped (`mixed`).

2. Bad class map definitions that don't match the actual JSON data.
It does this by checking all of the encountered classes within the JSON
data to ensure that they construct successfully (which they ALWAYS
will, unless the user has overridden `_init()` and throws from it), as
well as verifying that all basic PHP type coercions work, and that the
data is formatted as-described in the definition (such as having the
correct array-depth, or data defined as objects actually being objects).

The scan is performed by looking at EVERY item in the object's internal
JSON data array, and checking for a corresponding class map definition
(if nothing exists, it's treated as "missing from the class map"), and
then attempting conversion to its specified type (if that fails, it's
treated as "bad class map definition"). All undefined values (which lack
any class map definition) are ALSO validated as if they were `mixed`,
to ensure that NOTHING can contain illegal values.

If a recursive scan is requested, then all sub-objects (`LazyJsonMapper`
data containers) will ALSO recursively perform the same analysis on their
own internal data, so that the whole JSON data tree is recursively
verified. Their analysis results will be merged with our return value.

Note that calling this function will cause the WHOLE object tree to be
constructed and ALL of its mapped properties to be converted/parsed to
verify the class map, which takes some time and permanently increases the
object's memory usage (since conversion causes any internal sub-objects
to be stored as actual objects instead of merely as plain sub-arrays)!

Therefore, you should ONLY use this function when necessary: When you
need advanced and powerful debugging of your class maps!

@param bool $recursiveScan Whether to also verify missing/bad properties
in sub-objects in the analysis. Recommended.

@return ClassAnalysis An object describing the problems with this class.
Analyze the entire object and check for undefined or bad JSON properties .