MetaConfiguration.class.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************************
00003  *   Copyright (C) 2006-2008 by Konstantin V. Arkhipov                     *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU Lesser General Public License as        *
00007  *   published by the Free Software Foundation; either version 3 of the    *
00008  *   License, or (at your option) any later version.                       *
00009  *                                                                         *
00010  ***************************************************************************/
00011 
00015     final class MetaConfiguration extends Singleton implements Instantiatable
00016     {
00017         private $out = null;
00018         
00019         private $classes = array();
00020         private $sources = array();
00021         
00022         private $liaisons = array();
00023         private $references = array();
00024         
00025         private $defaultSource = null;
00026         
00027         private $forcedGeneration   = false;
00028         private $dryRun             = false;
00029         
00030         private $checkEnumerationRefIntegrity = false;
00031         
00035         public static function me()
00036         {
00037             return Singleton::getInstance('MetaConfiguration');
00038         }
00039         
00043         public static function out()
00044         {
00045             return self::me()->getOutput();
00046         }
00047         
00051         public function setForcedGeneration($orly)
00052         {
00053             $this->forcedGeneration = $orly;
00054             
00055             return $this;
00056         }
00057         
00058         public function isForcedGeneration()
00059         {
00060             return $this->forcedGeneration;
00061         }
00062         
00066         public function setDryRun($dry)
00067         {
00068             $this->dryRun = $dry;
00069             
00070             return $this;
00071         }
00072         
00073         public function isDryRun()
00074         {
00075             return $this->dryRun;
00076         }
00077         
00081         public function setWithEnumerationRefIntegrityCheck($orly)
00082         {
00083             $this->checkEnumerationRefIntegrity = $orly;
00084             
00085             return $this;
00086         }
00087         
00091         public function load($metafile, $generate = true)
00092         {
00093             $this->loadXml($metafile, $generate);
00094             
00095             // check sources
00096             foreach ($this->classes as $name => $class) {
00097                 $sourceLink = $class->getSourceLink();
00098                 if (isset($sourceLink)) {
00099                     Assert::isTrue(
00100                         isset($this->sources[$sourceLink]),
00101                         "unknown source '{$sourceLink}' specified "
00102                         ."for class '{$name}'"
00103                     );
00104                 } elseif ($this->defaultSource) {
00105                     $class->setSourceLink($this->defaultSource);
00106                 }
00107             }
00108             
00109             foreach ($this->liaisons as $class => $parent) {
00110                 if (isset($this->classes[$parent])) {
00111                     
00112                     Assert::isFalse(
00113                         $this->classes[$parent]->getTypeId()
00114                         == MetaClassType::CLASS_FINAL,
00115                         
00116                         "'{$parent}' is final, thus can not have childs"
00117                     );
00118                     
00119                     if (
00120                         $this->classes[$class]->getPattern()
00121                             instanceof DictionaryClassPattern
00122                     )
00123                         throw new UnsupportedMethodException(
00124                             'DictionaryClass pattern does '
00125                             .'not support inheritance'
00126                         );
00127                     
00128                     $this->classes[$class]->setParent(
00129                         $this->classes[$parent]
00130                     );
00131                 } else
00132                     throw new MissingElementException(
00133                         "unknown parent class '{$parent}'"
00134                     );
00135             }
00136             
00137             // search for referencing classes
00138             foreach ($this->references as $className => $list) {
00139                 $class = $this->getClassByName($className);
00140                 
00141                 if (
00142                     (
00143                         $class->getPattern() instanceof ValueObjectPattern
00144                     ) || (
00145                         $class->getPattern() instanceof InternalClassPattern
00146                     ) || (
00147                         $class->getPattern() instanceof AbstractClassPattern
00148                     )
00149                 ) {
00150                     continue;
00151                 }
00152                 
00153                 foreach ($list as $refer) {
00154                     $remote = $this->getClassByName($refer);
00155                     if (
00156                         (
00157                             $remote->getPattern() instanceof ValueObjectPattern
00158                         ) && (
00159                             isset($this->references[$refer])
00160                         )
00161                     ) {
00162                         foreach ($this->references[$refer] as $holder) {
00163                             $this->classes[$className]->
00164                                 setReferencingClass($holder);
00165                         }
00166                     } elseif (
00167                         (!$remote->getPattern() instanceof AbstractClassPattern)
00168                         && (!$remote->getPattern() instanceof InternalClassPattern)
00169                         && ($remote->getTypeId() <> MetaClassType::CLASS_ABSTRACT)
00170                     ) {
00171                         $this->classes[$className]->setReferencingClass($refer);
00172                     }
00173                 }
00174             }
00175             
00176             // final sanity checking
00177             foreach ($this->classes as $name => $class) {
00178                 $this->checkSanity($class);
00179             }
00180             
00181             // check for recursion in relations and spooked properties
00182             foreach ($this->classes as $name => $class) {
00183                 foreach ($class->getProperties() as $property) {
00184                     if ($property->getRelationId() == MetaRelation::ONE_TO_ONE) {
00185                         if (
00186                             (
00187                                 (
00188                                     $property->getType()->getClass()->getPattern()
00189                                         instanceof SpookedClassPattern
00190                                 ) || (
00191                                     $property->getType()->getClass()->getPattern()
00192                                         instanceof SpookedEnumerationPattern
00193                                 )
00194                             ) && (
00195                                 $property->getFetchStrategy()
00196                                 && (
00197                                     $property->getFetchStrategy()->getId()
00198                                     != FetchStrategy::LAZY
00199                                 )
00200                             )
00201                         ) {
00202                             $property->setFetchStrategy(FetchStrategy::cascade());
00203                         } else {
00204                             $this->checkRecursion($property, $class);
00205                         }
00206                     }
00207                 }
00208             }
00209             
00210             return $this;
00211         }
00212         
00216         public function buildClasses()
00217         {
00218             $out = $this->getOutput();
00219             
00220             $out->
00221                 infoLine('Building classes:');
00222             
00223             foreach ($this->classes as $name => $class) {
00224                 if (
00225                     !$class->doBuild()
00226                     || ($class->getPattern() instanceof InternalClassPattern)
00227                 ) {
00228                     continue;
00229                 } else {
00230                     $out->infoLine("\t".$name.':');
00231                 }
00232                 
00233                 $class->dump();
00234                 $out->newLine();
00235             }
00236             
00237             return $this;
00238         }
00239         
00243         public function buildSchema()
00244         {
00245             $out = $this->getOutput();
00246 
00247             $out->
00248                 newLine()->
00249                 infoLine('Building DB schema:');
00250             
00251             $schema = SchemaBuilder::getHead();
00252             
00253             $tables = array();
00254             
00255             foreach ($this->classes as $class) {
00256                 if (
00257                     (!$class->getParent() && !count($class->getProperties()))
00258                     || !$class->getPattern()->tableExists()
00259                 ) {
00260                     continue;
00261                 }
00262                 
00263                 foreach ($class->getAllProperties() as $property)
00264                     $tables[
00265                         $class->getTableName()
00266                     ][
00267                         // just to sort out dupes, if any
00268                         $property->getColumnName()
00269                     ] = $property;
00270             }
00271             
00272             foreach ($tables as $name => $propertyList)
00273                 if ($propertyList)
00274                     $schema .= SchemaBuilder::buildTable($name, $propertyList);
00275             
00276             foreach ($this->classes as $class) {
00277                 if (!$class->getPattern()->tableExists()) {
00278                     continue;
00279                 }
00280                 
00281                 $schema .= SchemaBuilder::buildRelations($class);
00282             }
00283             
00284             $schema .= '?>';
00285             
00286             BasePattern::dumpFile(
00287                 ONPHP_META_AUTO_DIR.'schema.php',
00288                 Format::indentize($schema)
00289             );
00290 
00291             return $this;
00292         }
00293         
00297         public function buildSchemaChanges()
00298         {
00299             $out = $this->getOutput();
00300             $out->
00301                 newLine()->
00302                 infoLine('Suggested DB-schema changes: ');
00303             
00304             require ONPHP_META_AUTO_DIR.'schema.php';
00305             
00306             foreach ($this->classes as $class) {
00307                 if (
00308                     $class->getTypeId() == MetaClassType::CLASS_ABSTRACT
00309                     || $class->getPattern() instanceof EnumerationClassPattern
00310                 )
00311                     continue;
00312                 
00313                 try {
00314                     $target = $schema->getTableByName($class->getTableName());
00315                 } catch (MissingElementException $e) {
00316                     // dropped or tableless
00317                     continue;
00318                 }
00319                 
00320                 try {
00321                     $db = DBPool::me()->getLink($class->getSourceLink());
00322                 } catch (BaseException $e) {
00323                     $out->
00324                         errorLine(
00325                             'Can not connect using source link in \''
00326                             .$class->getName().'\' class, skipping this step.'
00327                         );
00328                     
00329                     break;
00330                 }
00331                 
00332                 try {
00333                     $source = $db->getTableInfo($class->getTableName());
00334                 } catch (UnsupportedMethodException $e) {
00335                     $out->
00336                         errorLine(
00337                             get_class($db)
00338                             .' does not support tables introspection yet.',
00339                             
00340                             true
00341                         );
00342                     
00343                     break;
00344                 } catch (ObjectNotFoundException $e) {
00345                     $out->errorLine(
00346                         "table '{$class->getTableName()}' not found, skipping."
00347                     );
00348                     continue;
00349                 }
00350                 
00351                 $diff = DBTable::findDifferences(
00352                     $db->getDialect(),
00353                     $source,
00354                     $target
00355                 );
00356                 
00357                 if ($diff) {
00358                     foreach ($diff as $line)
00359                         $out->warningLine($line);
00360                     
00361                     $out->newLine();
00362                 }
00363             }
00364             
00365             return $this;
00366         }
00367         
00371         public function buildContainers()
00372         {
00373             $force = $this->isForcedGeneration();
00374             
00375             $out = $this->getOutput();
00376             $out->
00377                 infoLine('Building containers: ');
00378             
00379             foreach ($this->classes as $class) {
00380                 foreach ($class->getProperties() as $property) {
00381                     if (
00382                         $property->getRelation()
00383                         && ($property->getRelationId() != MetaRelation::ONE_TO_ONE)
00384                     ) {
00385                         $userFile =
00386                             ONPHP_META_DAO_DIR
00387                             .$class->getName().ucfirst($property->getName())
00388                             .'DAO'
00389                             .EXT_CLASS;
00390                         
00391                         if ($force || !file_exists($userFile)) {
00392                             BasePattern::dumpFile(
00393                                 $userFile,
00394                                 Format::indentize(
00395                                     ContainerClassBuilder::buildContainer(
00396                                         $class,
00397                                         $property
00398                                     )
00399                                 )
00400                             );
00401                         }
00402                         
00403                         // check for old-style naming
00404                         $oldStlye =
00405                             ONPHP_META_DAO_DIR
00406                             .$class->getName()
00407                             .'To'
00408                             .$property->getType()->getClassName()
00409                             .'DAO'
00410                             .EXT_CLASS;
00411                         
00412                         if (is_readable($oldStlye)) {
00413                             $out->
00414                                 newLine()->
00415                                 error(
00416                                     'remove manually: '.$oldStlye
00417                                 );
00418                         }
00419                     }
00420                 }
00421             }
00422             
00423             return $this;
00424         }
00425         
00429         public function checkIntegrity()
00430         {
00431             $out = $this->getOutput()->
00432                 newLine()->
00433                 infoLine('Checking sanity of generated files: ')->
00434                 newLine();
00435             
00436             set_include_path(
00437                 get_include_path().PATH_SEPARATOR
00438                 .ONPHP_META_BUSINESS_DIR.PATH_SEPARATOR
00439                 .ONPHP_META_DAO_DIR.PATH_SEPARATOR
00440                 .ONPHP_META_PROTO_DIR.PATH_SEPARATOR
00441                 .ONPHP_META_AUTO_BUSINESS_DIR.PATH_SEPARATOR
00442                 .ONPHP_META_AUTO_DAO_DIR.PATH_SEPARATOR
00443                 .ONPHP_META_AUTO_PROTO_DIR.PATH_SEPARATOR
00444             );
00445             
00446             $out->info("\t");
00447             
00448             $formErrors = array();
00449             
00450             foreach ($this->classes as $name => $class) {
00451                 if (
00452                     !(
00453                         $class->getPattern() instanceof SpookedClassPattern
00454                         || $class->getPattern() instanceof SpookedEnumerationPattern
00455                         || $class->getPattern() instanceof InternalClassPattern
00456                     ) && (
00457                         class_exists($class->getName(), true)
00458                     )
00459                 ) {
00460                     $out->info($name, true);
00461                     
00462                     $info = new ReflectionClass($name);
00463                     
00464                     $this->
00465                         checkClassSanity($class, $info);
00466                     
00467                     if ($info->implementsInterface('Prototyped'))
00468                         $this->checkClassSanity(
00469                             $class,
00470                             new ReflectionClass('Proto'.$name)
00471                         );
00472                     
00473                     if ($info->implementsInterface('DAOConnected'))
00474                         $this->checkClassSanity(
00475                             $class,
00476                             new ReflectionClass($name.'DAO')
00477                         );
00478                     
00479                     foreach ($class->getInterfaces() as $interface)
00480                         Assert::isTrue(
00481                             $info->implementsInterface($interface),
00482                             
00483                             'class '.$class->getName()
00484                             .' expected to implement interface '.$interface
00485                         );
00486                     
00487                     // special handling for Enumeration instances
00488                     if ($class->getPattern() instanceof EnumerationClassPattern) {
00489                         $object = new $name(call_user_func(array($name, 'getAnyId')));
00490                         
00491                         Assert::isTrue(
00492                             unserialize(serialize($object)) == $object
00493                         );
00494                         
00495                         $out->info(', ');
00496                         
00497                         if ($this->checkEnumerationRefIntegrity)
00498                             $this->checkEnumerationReferentialIntegrity(
00499                                 $object,
00500                                 $class->getTableName()
00501                             );
00502                         
00503                         continue;
00504                     }
00505                     
00506                     if ($class->getPattern() instanceof AbstractClassPattern) {
00507                         $out->info(', ');
00508                         continue;
00509                     }
00510                     
00511                     $object = new $name;
00512                     $proto = $object->proto();
00513                     $form = $proto->makeForm();
00514                     
00515                     foreach ($class->getProperties() as $name => $property) {
00516                         Assert::isTrue(
00517                             $property->toLightProperty($class)
00518                             == $proto->getPropertyByName($name),
00519                             
00520                             'defined property does not match autogenerated one - '
00521                             .$class->getName().'::'.$property->getName()
00522                         );
00523                     }
00524                     
00525                     if (!$object instanceof DAOConnected) {
00526                         $out->info(', ');
00527                         continue;
00528                     }
00529                     
00530                     $dao = $object->dao();
00531                     
00532                     Assert::isEqual(
00533                         $dao->getIdName(),
00534                         $class->getIdentifier()->getColumnName(),
00535                         'identifier name mismatch in '.$class->getName().' class'
00536                     );
00537                     
00538                     try {
00539                         DBPool::getByDao($dao);
00540                     } catch (MissingElementException $e) {
00541                         // skipping
00542                         $out->info(', ');
00543                         continue;
00544                     }
00545                     
00546                     $query =
00547                         Criteria::create($dao)->
00548                         setLimit(1)->
00549                         add(Expression::notNull($class->getIdentifier()->getName()))->
00550                         addOrder($class->getIdentifier()->getName())->
00551                         toSelectQuery();
00552                     
00553                     $out->warning(
00554                         ' ('
00555                         .$query->getFieldsCount()
00556                         .'/'
00557                         .$query->getTablesCount()
00558                         .'/'
00559                     );
00560                     
00561                     $clone = clone $object;
00562                     
00563                     if (serialize($clone) == serialize($object))
00564                         $out->info('C', true);
00565                     else {
00566                         $out->error('C', true);
00567                     }
00568                     
00569                     $out->warning('/');
00570                     
00571                     try {
00572                         $object = $dao->getByQuery($query);
00573                         $form = $object->proto()->makeForm();
00574                         FormUtils::object2form($object, $form);
00575                         
00576                         if ($errors = $form->getErrors()) {
00577                             $formErrors[$class->getName()] = $errors;
00578                             
00579                             $out->error('F', true);
00580                         } else
00581                             $out->info('F', true);
00582                     } catch (ObjectNotFoundException $e) {
00583                         $out->warning('F');
00584                     }
00585                     
00586                     $out->warning('/');
00587                     
00588                     if (
00589                         Criteria::create($dao)->
00590                         setFetchStrategy(FetchStrategy::cascade())->
00591                         toSelectQuery()
00592                         == $dao->makeSelectHead()
00593                     ) {
00594                         $out->info('H', true);
00595                     } else {
00596                         $out->error('H', true);
00597                     }
00598                     
00599                     $out->warning('/');
00600                     
00601                     // cloning once again
00602                     $clone = clone $object;
00603                     
00604                     FormUtils::object2form($object, $form);
00605                     FormUtils::form2object($form, $object);
00606                     
00607                     if ($object != $clone) {
00608                         $out->error('T', true);
00609                     } else {
00610                         $out->info('T', true);
00611                     }
00612                     
00613                     $out->warning(')')->info(', ');
00614                 }
00615             }
00616             
00617             $out->infoLine('done.');
00618             
00619             if ($formErrors) {
00620                 $out->newLine()->errorLine('Errors found:')->newLine();
00621                 
00622                 foreach ($formErrors as $class => $errors) {
00623                     $out->errorLine("\t".$class.':', true);
00624                     
00625                     foreach ($errors as $name => $error) {
00626                         $out->errorLine(
00627                             "\t\t".$name.' - '
00628                             .(
00629                                 $error == Form::WRONG
00630                                     ? ' wrong'
00631                                     : ' missing'
00632                             )
00633                         );
00634                     }
00635                     
00636                     $out->newLine();
00637                 }
00638             }
00639             
00640             return $this;
00641         }
00642         
00646         public function checkForStaleFiles($drop = false)
00647         {
00648             $this->getOutput()->
00649                 newLine()->
00650                 infoLine('Checking for stale files: ');
00651             
00652             return $this->
00653                 checkDirectory(ONPHP_META_AUTO_BUSINESS_DIR, 'Auto', null, $drop)->
00654                 checkDirectory(ONPHP_META_AUTO_DAO_DIR, 'Auto', 'DAO', $drop)->
00655                 checkDirectory(ONPHP_META_AUTO_PROTO_DIR, 'AutoProto', null, $drop);
00656         }
00657         
00662         public function getClassByName($name)
00663         {
00664             if (isset($this->classes[$name]))
00665                 return $this->classes[$name];
00666             
00667             throw new MissingElementException(
00668                 "knows nothing about '{$name}' class"
00669             );
00670         }
00671         
00672         public function getClassList()
00673         {
00674             return $this->classes;
00675         }
00676         
00680         public function setOutput(MetaOutput $out)
00681         {
00682             $this->out = $out;
00683             
00684             return $this;
00685         }
00686         
00690         public function getOutput()
00691         {
00692             return $this->out;
00693         }
00694         
00698         private function checkDirectory(
00699             $directory, $preStrip, $postStrip, $drop = false
00700         )
00701         {
00702             $out = $this->getOutput();
00703             
00704             foreach (
00705                 glob($directory.'*.class.php', GLOB_NOSORT)
00706                 as $filename
00707             ) {
00708                 $name =
00709                     substr(
00710                         basename($filename, $postStrip.EXT_CLASS),
00711                         strlen($preStrip)
00712                     );
00713                 
00714                 if (!isset($this->classes[$name])) {
00715                     $out->warning(
00716                         "\t"
00717                         .str_replace(
00718                             getcwd().DIRECTORY_SEPARATOR,
00719                             null,
00720                             $filename
00721                         )
00722                     );
00723                     
00724                     if ($drop) {
00725                         try {
00726                             unlink($filename);
00727                             $out->infoLine(' removed.');
00728                         } catch (BaseException $e) {
00729                             $out->errorLine(' failed to remove.');
00730                         }
00731                     } else {
00732                         $out->newLine();
00733                     }
00734                 }
00735             }
00736             
00737             return $this;
00738         }
00739         
00743         private function addSource(SimpleXMLElement $source)
00744         {
00745             $name = (string) $source['name'];
00746             
00747             $default =
00748                 isset($source['default']) && (string) $source['default'] == 'true'
00749                     ? true
00750                     : false;
00751             
00752             Assert::isFalse(
00753                 isset($this->sources[$name]),
00754                 "duplicate source - '{$name}'"
00755             );
00756             
00757             Assert::isFalse(
00758                 $default && $this->defaultSource !== null,
00759                 'too many default sources'
00760             );
00761             
00762             $this->sources[$name] = $default;
00763             
00764             if ($default)
00765                 $this->defaultSource = $name;
00766             
00767             return $this;
00768         }
00769         
00773         private function makeProperty($name, $type, MetaClass $class, $size)
00774         {
00775             Assert::isFalse(
00776                 strpos($name, '_'),
00777                 'naming convention violation spotted'
00778             );
00779             
00780             if (!$name || !$type)
00781                 throw new WrongArgumentException(
00782                     'strange name or type given: "'.$name.'" - "'.$type.'"'
00783                 );
00784             
00785             if (is_readable(ONPHP_META_TYPES.$type.'Type'.EXT_CLASS))
00786                 $typeClass = $type.'Type';
00787             else
00788                 $typeClass = 'ObjectType';
00789             
00790             $property = new MetaClassProperty($name, new $typeClass($type), $class);
00791             
00792             if ($size)
00793                 $property->setSize($size);
00794             else {
00795                 Assert::isTrue(
00796                     (
00797                         !$property->getType()
00798                             instanceof FixedLengthStringType
00799                     ) && (
00800                         !$property->getType()
00801                             instanceof NumericType
00802                     ) && (
00803                         !$property->getType()
00804                             instanceof HttpUrlType
00805                     ),
00806                     
00807                     'size is required for "'.$property->getName().'"'
00808                 );
00809             }
00810             
00811             return $property;
00812         }
00813         
00818         private function guessPattern($name)
00819         {
00820             $class = $name.'Pattern';
00821             
00822             if (is_readable(ONPHP_META_PATTERNS.$class.EXT_CLASS))
00823                 return Singleton::getInstance($class);
00824             
00825             throw new MissingElementException(
00826                 "unknown pattern '{$name}'"
00827             );
00828         }
00829         
00833         private function checkSanity(MetaClass $class)
00834         {
00835             if (
00836                 (
00837                     !$class->getParent()
00838                     || (
00839                         $class->getFinalParent()->getPattern()
00840                             instanceof InternalClassPattern
00841                     )
00842                 )
00843                 && (!$class->getPattern() instanceof ValueObjectPattern)
00844                 && (!$class->getPattern() instanceof InternalClassPattern)
00845             ) {
00846                 Assert::isTrue(
00847                     $class->getIdentifier() !== null,
00848                     
00849                     'only value objects can live without identifiers. '
00850                     .'do not use them anyway'
00851                 );
00852             }
00853             
00854             if (
00855                 $class->getType()
00856                 && $class->getTypeId()
00857                     == MetaClassType::CLASS_SPOOKED
00858             ) {
00859                 Assert::isFalse(
00860                     count($class->getProperties()) > 1,
00861                     'spooked classes must have only identifier'
00862                 );
00863                 
00864                 Assert::isTrue(
00865                     ($class->getPattern() instanceof SpookedClassPattern
00866                     || $class->getPattern() instanceof SpookedEnumerationPattern),
00867                     'spooked classes must use spooked patterns only'
00868                 );
00869             }
00870             
00871             foreach ($class->getProperties() as $property) {
00872                 if (
00873                     !$property->getType()->isGeneric()
00874                     && $property->getType() instanceof ObjectType
00875                     &&
00876                         $property->getType()->getClass()->getPattern()
00877                             instanceof ValueObjectPattern
00878                 ) {
00879                     Assert::isTrue(
00880                         $property->isRequired(),
00881                         'optional value object is not supported'
00882                     );
00883                     
00884                     Assert::isTrue(
00885                         $property->getRelationId() == MetaRelation::ONE_TO_ONE,
00886                         'value objects must have OneToOne relation'
00887                     );
00888                 } elseif (
00889                     ($property->getFetchStrategyId() == FetchStrategy::LAZY)
00890                     && $property->getType()->isGeneric()
00891                 ) {
00892                     throw new WrongArgumentException(
00893                         'lazy one-to-one is supported only for '
00894                         .'non-generic object types '
00895                         .'('.$property->getName()
00896                         .' @ '.$class->getName()
00897                         .')'
00898                     );
00899                 }
00900             }
00901             
00902             return $this;
00903         }
00904         
00905         private function checkRecursion(
00906             MetaClassProperty $property,
00907             MetaClass $holder,
00908             $paths = array()
00909         ) {
00910             Assert::isTrue(
00911                 $property->getRelationId()
00912                 == MetaRelation::ONE_TO_ONE
00913             );
00914             
00915             if (
00916                 $property->getFetchStrategy()
00917                 && $property->getFetchStrategy()->getId() != FetchStrategy::JOIN
00918             ) {
00919                 return false;
00920             }
00921 
00922             $remote = $property->getType()->getClass();
00923             
00924             if (isset($paths[$holder->getName()][$remote->getName()]))
00925                 return true;
00926             else {
00927                 $paths[$holder->getName()][$remote->getName()] = true;
00928                 
00929                 foreach ($remote->getProperties() as $remoteProperty) {
00930                     if (
00931                         $remoteProperty->getRelationId()
00932                         == MetaRelation::ONE_TO_ONE
00933                     ) {
00934                         if (
00935                             $this->checkRecursion(
00936                                 $remoteProperty,
00937                                 $holder,
00938                                 $paths
00939                             )
00940                         ) {
00941                             $remoteProperty->setFetchStrategy(
00942                                 FetchStrategy::cascade()
00943                             );
00944                         }
00945                     }
00946                 }
00947             }
00948             
00949             return false;
00950         }
00951         
00955         private function processIncludes(SimpleXMLElement $xml, $metafile)
00956         {
00957             foreach ($xml->include as $include) {
00958                 $file = (string) $include['file'];
00959                 $path = dirname($metafile).'/'.$file;
00960                 
00961                 Assert::isTrue(
00962                     is_readable($path),
00963                     'can not include '.$file
00964                 );
00965                 
00966                 $this->getOutput()->
00967                     infoLine('Including "'.$path.'".')->
00968                     newLine();
00969                 
00970                 $this->loadXml($path, !((string) $include['generate'] == 'false'));
00971             }
00972             
00973             return $this;
00974         }
00975         
00979         private function processClasses(SimpleXMLElement $xml, $metafile, $generate)
00980         {
00981             foreach ($xml->classes[0] as $xmlClass) {
00982                 $name = (string) $xmlClass['name'];
00983                 
00984                 Assert::isFalse(
00985                     isset($this->classes[$name]),
00986                     'class name collision found for '.$name
00987                 );
00988                 
00989                 $class = new MetaClass($name);
00990                 
00991                 if (isset($xmlClass['source']))
00992                     $class->setSourceLink((string) $xmlClass['source']);
00993                 
00994                 if (isset($xmlClass['table']))
00995                     $class->setTableName((string) $xmlClass['table']);
00996                 
00997                 if (isset($xmlClass['type'])) {
00998                     $type = (string) $xmlClass['type'];
00999                     
01000                     if ($type == 'spooked') {
01001                         $this->getOutput()->
01002                             warning($class->getName(), true)->
01003                             warningLine(': uses obsoleted "spooked" type.')->
01004                             newLine();
01005                     }
01006                     
01007                     $class->setType(
01008                         new MetaClassType(
01009                             (string) $xmlClass['type']
01010                         )
01011                     );
01012                 }
01013                 
01014                 // lazy existence checking
01015                 if (isset($xmlClass['extends']))
01016                     $this->liaisons[$class->getName()] = (string) $xmlClass['extends'];
01017                 
01018                 // populate implemented interfaces
01019                 foreach ($xmlClass->implement as $xmlImplement)
01020                     $class->addInterface((string) $xmlImplement['interface']);
01021                 
01022                 if (isset($xmlClass->properties[0]->identifier)) {
01023                     
01024                     $id = $xmlClass->properties[0]->identifier;
01025                     
01026                     if (!isset($id['name']))
01027                         $name = 'id';
01028                     else
01029                         $name = (string) $id['name'];
01030                     
01031                     if (!isset($id['type']))
01032                         $type = 'BigInteger';
01033                     else
01034                         $type = (string) $id['type'];
01035                     
01036                     $property = $this->makeProperty(
01037                         $name,
01038                         $type,
01039                         $class,
01040                         // not casting to int because of Numeric possible size
01041                         (string) $id['size']
01042                     );
01043                     
01044                     if (isset($id['column'])) {
01045                         $property->setColumnName(
01046                             (string) $id['column']
01047                         );
01048                     } elseif (
01049                         $property->getType() instanceof ObjectType
01050                         && !$property->getType()->isGeneric()
01051                     ) {
01052                         $property->setColumnName($property->getConvertedName().'_id');
01053                     } else {
01054                         $property->setColumnName($property->getConvertedName());
01055                     }
01056                     
01057                     $property->
01058                         setIdentifier(true)->
01059                         required();
01060                     
01061                     $class->addProperty($property);
01062                     
01063                     unset($xmlClass->properties[0]->identifier);
01064                 }
01065                 
01066                 $class->setPattern(
01067                     $this->guessPattern((string) $xmlClass->pattern['name'])
01068                 );
01069                 
01070                 if ((string) $xmlClass->pattern['fetch'] == 'cascade')
01071                     $class->setFetchStrategy(FetchStrategy::cascade());
01072                 
01073                 if ($class->getPattern() instanceof InternalClassPattern) {
01074                     Assert::isTrue(
01075                         $metafile === ONPHP_META_PATH.'internal.xml',
01076                         'internal classes can be defined only in onPHP, sorry'
01077                     );
01078                 } elseif (
01079                     (
01080                         $class->getPattern() instanceof SpookedClassPattern
01081                     ) || (
01082                         $class->getPattern() instanceof SpookedEnumerationPattern
01083                     )
01084                 ) {
01085                     $class->setType(
01086                         new MetaClassType(
01087                             MetaClassType::CLASS_SPOOKED
01088                         )
01089                     );
01090                 }
01091                 
01092                 // populate properties
01093                 foreach ($xmlClass->properties[0] as $xmlProperty) {
01094                     
01095                     $property = $this->makeProperty(
01096                         (string) $xmlProperty['name'],
01097                         (string) $xmlProperty['type'],
01098                         $class,
01099                         (string) $xmlProperty['size']
01100                     );
01101                     
01102                     if (isset($xmlProperty['column'])) {
01103                         $property->setColumnName(
01104                             (string) $xmlProperty['column']
01105                         );
01106                     } elseif (
01107                         $property->getType() instanceof ObjectType
01108                         && !$property->getType()->isGeneric()
01109                     ) {
01110                         if (
01111                             isset(
01112                                 $this->classes[
01113                                     $property->getType()->getClassName()
01114                                 ]
01115                             ) && (
01116                                 $property->getType()->getClass()->getPattern()
01117                                     instanceof InternalClassPattern
01118                             )
01119                         ) {
01120                             throw new UnimplementedFeatureException(
01121                                 'you can not use internal classes directly atm'
01122                             );
01123                         }
01124                         
01125                         $property->setColumnName($property->getConvertedName().'_id');
01126                     } else {
01127                         $property->setColumnName($property->getConvertedName());
01128                     }
01129                     
01130                     if ((string) $xmlProperty['required'] == 'true')
01131                         $property->required();
01132                     
01133                     if (isset($xmlProperty['identifier'])) {
01134                         throw new WrongArgumentException(
01135                             'obsoleted identifier description found in '
01136                             ."{$class->getName()} class;\n"
01137                             .'you must use <identifier /> instead.'
01138                         );
01139                     }
01140                     
01141                     if (!$property->getType()->isGeneric()) {
01142                         
01143                         if (!isset($xmlProperty['relation']))
01144                             throw new MissingElementException(
01145                                 'relation should be set for non-generic '
01146                                 ."property '{$property->getName()}' type '"
01147                                 .get_class($property->getType())."'"
01148                                 ." of '{$class->getName()}' class"
01149                             );
01150                         else {
01151                             $property->setRelation(
01152                                 MetaRelation::makeFromName(
01153                                     (string) $xmlProperty['relation']
01154                                 )
01155                             );
01156                             
01157                             if ($fetch = (string) $xmlProperty['fetch']) {
01158                                 Assert::isTrue(
01159                                     $property->getRelationId()
01160                                     == MetaRelation::ONE_TO_ONE,
01161                                     
01162                                     'fetch mode can be specified
01163                                     only for OneToOne relations'
01164                                 );
01165                                 
01166                                 if ($fetch == 'lazy')
01167                                     $property->setFetchStrategy(
01168                                         FetchStrategy::lazy()
01169                                     );
01170                                 elseif ($fetch == 'cascade')
01171                                     $property->setFetchStrategy(
01172                                         FetchStrategy::cascade()
01173                                     );
01174                                 else
01175                                     throw new WrongArgumentException(
01176                                         'strange fetch mode found - '.$fetch
01177                                     );
01178                             }
01179                             
01180                             if (
01181                                 (
01182                                     (
01183                                         $property->getRelationId()
01184                                             == MetaRelation::ONE_TO_ONE
01185                                     ) && (
01186                                         $property->getFetchStrategyId()
01187                                         != FetchStrategy::LAZY
01188                                     )
01189                                 ) && (
01190                                     $property->getType()->getClassName()
01191                                     <> $class->getName()
01192                                 )
01193                             ) {
01194                                 $this->references[$property->getType()->getClassName()][]
01195                                     = $class->getName();
01196                             }
01197                         }
01198                     }
01199                     
01200                     if (isset($xmlProperty['default'])) {
01201                         // will be correctly autocasted further down the code
01202                         $property->getType()->setDefault(
01203                             (string) $xmlProperty['default']
01204                         );
01205                     }
01206                     
01207                     $class->addProperty($property);
01208                 }
01209                 
01210                 $class->setBuild($generate);
01211                 
01212                 $this->classes[$class->getName()] = $class;
01213             }
01214             
01215             return $this;
01216         }
01217         
01218         private function loadXml($metafile, $generate)
01219         {
01220             $contents = file_get_contents($metafile);
01221             
01222             $contents = str_replace(
01223                 '"meta.dtd"',
01224                 '"'.ONPHP_META_PATH.'dtd/meta.dtd"',
01225                 $contents
01226             );
01227             
01228             $doc = new DOMDocument('1.0');
01229             $doc->loadXML($contents);
01230             
01231             try {
01232                 $doc->validate();
01233             } catch (BaseException $e) {
01234                 $error = libxml_get_last_error();
01235                 throw new WrongArgumentException(
01236                     $error->message.' in node placed on line '
01237                     .$error->line.' in file '.$metafile
01238                 );
01239             }
01240             
01241             $xml = simplexml_import_dom($doc);
01242             
01243             // populate sources (if any)
01244             if (isset($xml->sources[0])) {
01245                 foreach ($xml->sources[0] as $source) {
01246                     $this->addSource($source);
01247                 }
01248             }
01249             
01250             if (isset($xml->include['file'])) {
01251                 $this->processIncludes($xml, $metafile);
01252             }
01253             
01254             // otherwise it's an includes-only config
01255             if (isset($xml->classes[0])) {
01256                 return $this->processClasses($xml, $metafile, $generate);
01257             }
01258             
01259             return $this;
01260         }
01261         
01265         private function checkClassSanity(
01266             MetaClass $class,
01267             ReflectionClass $info
01268         )
01269         {
01270             switch ($class->getTypeId()) {
01271                 case null:
01272                     break;
01273                 
01274                 case MetaClassType::CLASS_ABSTRACT:
01275                     Assert::isTrue(
01276                         $info->isAbstract(),
01277                         'class '.$info->getName().' expected to be abstract'
01278                     );
01279                     Assert::isTrue(
01280                         $class->getPattern() instanceof AbstractClassPattern,
01281                         'class '.$info->getName().' must use AbstractClassPattern'
01282                     );
01283                     break;
01284                 
01285                 case MetaClassType::CLASS_FINAL:
01286                     Assert::isTrue(
01287                         $info->isFinal(),
01288                         'class '.$info->getName().' expected to be final'
01289                     );
01290                     break;
01291                 
01292                 case MetaClassType::CLASS_SPOOKED:
01293                 default:
01294                     Assert::isUnreachable();
01295                     break;
01296             }
01297             
01298             if ($public = $info->getProperties(ReflectionProperty::IS_PUBLIC)) {
01299                 Assert::isUnreachable(
01300                     $class->getName()
01301                     .' contains properties with evil visibility:'
01302                     ."\n"
01303                     .print_r($public, true)
01304                 );
01305             }
01306             
01307             return $this;
01308         }
01309         
01310         private function checkEnumerationReferentialIntegrity(
01311             Enumeration $enumeration, $tableName
01312         )
01313         {
01314             $updateQueries = null;
01315             
01316             $db = DBPool::me()->getLink();
01317             
01318             $class = get_class($enumeration);
01319             
01320             $ids = array();
01321             
01322             $list = $enumeration->getObjectList();
01323             
01324             foreach ($list as $enumerationObject)
01325                 $ids[$enumerationObject->getId()] = $enumerationObject->getName();
01326             
01327             $rows =
01328                 $db->querySet(
01329                     OSQL::select()->from($tableName)->
01330                     multiGet('id', 'name')
01331                 );
01332             
01333             echo "\n";
01334             
01335             foreach ($rows as $row) {
01336                 if (!isset($ids[$row['id']]))
01337                     echo "Class '{$class}', strange id: {$row['id']} found. \n";
01338                 else {
01339                     if ($ids[$row['id']] != $row['name']) {
01340                         echo "Class '{$class}',id: {$row['id']} sync names. \n";
01341                         
01342                         $updateQueries .=
01343                             OSQL::update($tableName)->
01344                             set('name', $ids[$row['id']])->
01345                             where(Expression::eq('id', $row['id']))->
01346                             toDialectString($db->getDialect()) . ";\n";
01347                     }
01348                     
01349                     unset($ids[$row['id']]);
01350                 }
01351             }
01352             
01353             foreach ($ids as $id => $name)
01354                 echo "Class '{$class}', id: {$id} not present in database. \n";
01355             
01356             echo $updateQueries;
01357             
01358             return $this;
01359         }
01360     }
01361 ?>