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