00001 <?php
00002
00003
00004
00005
00006
00007
00008
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
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
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
00177 foreach ($this->classes as $name => $class) {
00178 $this->checkSanity($class);
00179 }
00180
00181
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
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
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
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
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
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
01015 if (isset($xmlClass['extends']))
01016 $this->liaisons[$class->getName()] = (string) $xmlClass['extends'];
01017
01018
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
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
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
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
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
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 ?>