MetaConfiguration.class.php

Go to the documentation of this file.
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 ?>

Generated on Sun Dec 9 21:56:24 2007 for onPHP by  doxygen 1.5.4