# 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 ?>