public function run(App $previousApp = null) { //Make sure all correct feature aliases are registered $this->_registerFeatureAliases(); //We will sort the features by dependency on other features //and initialize them in the correct order //First we create an instance of all features. /** * @var \Tale\App\FeatureBase[] $features */ $features = []; foreach ($this->getOption('features') as $name => $options) { //Aliases are resolved. $className = $this->_featureFactory->resolveClassName($name); //We don't want duplicate instances. Rather create a new feature //It's better for unique identification if (isset($features[$className])) { throw new RuntimeException("Failed to add feature {$name}({$className}): Feature does already exist"); } //This creates the actual instance via Tale\Factory $feature = $this->_featureFactory->createInstance($className, [$this]); //We can pass a string as the options to use a config file if (is_string($options)) { $feature->appendOptionFile($options, true); } else { if (is_array($options)) { $feature->appendOptions($options, true); } else { throw new RuntimeException("Failed to initialize feature {$name}({$className}): " . "Config needs to be a path to a file or an option array"); } } $features[] = $feature; } //All features are instanced and got all the options they will get //at this point. //Now we sort the features by dependencies. //TODO: Circular dependencies probably f**k up. ArrayUtil::mergeSort($features, [__CLASS__, 'compareFeatures']); $classes = array_map('get_class', $features); $features = array_combine($classes, $features); //Now we can initialize the features //Since our deps are ordered now, we can just iterate foreach ($features as $feature) { //First append our dependencies foreach ($feature->getDependencies() as $name => $className) { if (isset($features[$className])) { $feature->setDependency($name, $features[$className]); } } //Now initialize the feature //This is the possibility to add events etc. $feature->init(); } //This is only sorting! We dont ensure, that the feature exists. //The feature has to do that by itself (through an easy way, look at it) //Put together our event args $args = ['app' => $this, 'previousApp' => $previousApp]; //First, we prepare (and let features prepare) if ($this->emit('beforeRun', new Event\Args($args))) { $this->_setPhpOptions(); //Then we run if ($this->emit('run', new Event\Args($args))) { //Clear our features $this->_features = null; //Allow clean-up at the end (Notice that the "true" //makes it reverse $this->emit('afterRun', new Event\Args($args), true); } } return $this; }