diff --git a/extensions/yii/apidoc/apidoc b/extensions/yii/apidoc/apidoc
new file mode 100755
index 0000000..91f7981
--- /dev/null
+++ b/extensions/yii/apidoc/apidoc
@@ -0,0 +1,49 @@
+#!/usr/bin/env php
+<?php
+/**
+ * Yii Framework 2.0 API documentation generator
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+defined('YII_DEBUG') or define('YII_DEBUG', false);
+
+$composerAutoload = [
+	__DIR__ . '/vendor/autoload.php', // standalone with "composer install" run
+	__DIR__ . '/../../../../autoload.php', // script is installed as a composer binary
+];
+foreach($composerAutoload as $autoload) {
+	if (file_exists($autoload)) {
+		require($autoload);
+		break;
+	}
+}
+$yiiDirs = [
+	__DIR__ . '/../../../framework', // in yii2-dev repo
+	__DIR__ . '/vendor/yiisoft/yii2', // standalone with "composer install" run
+	__DIR__ . '/../../../../yiisoft/yii2', // script is installed as a composer binary
+];
+foreach($yiiDirs as $dir) {
+	if (file_exists($dir . '/yii/Yii.php')) {
+		require($dir . '/yii/Yii.php');
+		break;
+	}
+}
+if (!class_exists('Yii')) {
+	echo "\nThe Yii Framework 2.0 does not seem to be installed. Try running composer install.\n\n";
+	exit(1);
+}
+
+Yii::setAlias('@yii/apidoc', __DIR__);
+
+$application = new yii\console\Application([
+	'id' => 'yii2-apidoc',
+	'basePath' => __DIR__,
+	'enableCoreCommands' => false,
+	'controllerNamespace' => 'yii\\apidoc\\commands',
+	'controllerPath' => '@yii/apidoc/commands',
+]);
+$exitCode = $application->run();
+exit($exitCode);
diff --git a/extensions/yii/apidoc/apidoc.bat b/extensions/yii/apidoc/apidoc.bat
new file mode 100644
index 0000000..ae00407
--- /dev/null
+++ b/extensions/yii/apidoc/apidoc.bat
@@ -0,0 +1,20 @@
+@echo off
+
+rem -------------------------------------------------------------
+rem  Yii command line bootstrap script for Windows.
+rem
+rem  @author Qiang Xue <qiang.xue@gmail.com>
+rem  @link http://www.yiiframework.com/
+rem  @copyright Copyright &copy; 2012 Yii Software LLC
+rem  @license http://www.yiiframework.com/license/
+rem -------------------------------------------------------------
+
+@setlocal
+
+set YII_PATH=%~dp0
+
+if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
+
+"%PHP_COMMAND%" "%YII_PATH%apidoc" %*
+
+@endlocal
diff --git a/extensions/yii/apidoc/commands/PhpdocController.php b/extensions/yii/apidoc/commands/RenderController.php
similarity index 74%
rename from extensions/yii/apidoc/commands/PhpdocController.php
rename to extensions/yii/apidoc/commands/RenderController.php
index 9236580..33aee50 100644
--- a/extensions/yii/apidoc/commands/PhpdocController.php
+++ b/extensions/yii/apidoc/commands/RenderController.php
@@ -16,30 +16,59 @@ use yii\apidoc\models\Context;
 use Yii;
 
 /**
+ * Command to render API Documentation files
  *
  * @author Carsten Brandt <mail@cebe.cc>
  * @since 2.0
  */
-class PhpdocController extends Controller
+class RenderController extends Controller
 {
-	public function actionIndex($targetDir)
+	/**
+	 * Renders API documentation files
+	 * @param array $sourceDirs
+	 * @param string $targetDir
+	 * @return int
+	 */
+	public function actionIndex(array $sourceDirs, $targetDir)
 	{
-		echo "hi\n";
-
 		$targetDir = Yii::getAlias($targetDir);
 		if (is_dir($targetDir) && !$this->confirm('TargetDirectory already exists. Overwrite?')) {
 			return 2;
 		}
+		if (!is_dir($targetDir)) {
+			mkdir($targetDir);
+		}
 
-		// TODO determine files to analyze
 		$this->stdout('Searching files to process... ');
-		$files = $this->findFiles(YII_PATH);
-//		$files = array_slice($files, 0, 42); // TODO remove this line
+		$files = [];
+		foreach($sourceDirs as $source) {
+			foreach($this->findFiles($source) as $fileName) {
+				$files[$fileName] = $fileName;
+			}
+		}
+
 		$this->stdout('done.' . PHP_EOL, Console::FG_GREEN);
 
+		$context = new Context();
+
+		$cacheFile = $targetDir . '/cache/' . md5(serialize($files)) . '.tmp';
+		if (file_exists($cacheFile)) {
+			$this->stdout('Loading processed data from cache... ');
+			$context = unserialize(file_get_contents($cacheFile));
+			$this->stdout('done.' . PHP_EOL, Console::FG_GREEN);
+
+			$this->stdout('Checking for updated files... ');
+			foreach($context->files as $file => $sha) {
+				if (sha1_file($file) === $sha) {
+					unset($files[$file]);
+				}
+			}
+			$this->stdout('done.' . PHP_EOL, Console::FG_GREEN);
+		}
+
 		$fileCount = count($files);
+		$this->stdout($fileCount . ' file' . ($fileCount == 1 ? '' : 's') . ' to update.' . PHP_EOL);
 		Console::startProgress(0, $fileCount, 'Processing files... ', false);
-		$context = new Context();
 		$done = 0;
 		foreach($files as $file) {
 			$context->addFile($file);
@@ -48,20 +77,22 @@ class PhpdocController extends Controller
 		Console::endProgress(true);
 		$this->stdout('done.' . PHP_EOL, Console::FG_GREEN);
 
+		// save processed data to cache
+		if (!is_dir(dirname($cacheFile))) {
+			mkdir(dirname($cacheFile));
+		}
+		file_put_contents($cacheFile, serialize($context));
+
 		$this->stdout('Updating cross references and backlinks... ');
 		$context->updateReferences();
 		$this->stdout('done.' . PHP_EOL, Console::FG_GREEN);
 
-
-		// TODO LATER analyze for dead links and similar stuff
-
-		// TODO render models
+		// render models
 		$renderer = new OfflineRenderer();
 		$renderer->targetDir = $targetDir;
 		$renderer->render($context, $this);
 	}
 
-
 	protected function findFiles($path, $except = [])
 	{
 		$path = FileHelper::normalizePath($path);
@@ -84,5 +115,4 @@ class PhpdocController extends Controller
 		];
 		return FileHelper::findFiles($path, $options);
 	}
-
 }
\ No newline at end of file
diff --git a/extensions/yii/apidoc/composer.json b/extensions/yii/apidoc/composer.json
index 30608a1..bf1c4b5 100644
--- a/extensions/yii/apidoc/composer.json
+++ b/extensions/yii/apidoc/composer.json
@@ -25,5 +25,6 @@
 	"autoload": {
 		"psr-0": { "yii\\apidoc\\": "" }
 	},
-	"target-dir": "yii/apidoc"
+	"target-dir": "yii/apidoc",
+	"bin": ["apidoc"]
 }
diff --git a/extensions/yii/apidoc/models/Context.php b/extensions/yii/apidoc/models/Context.php
index 89bfcfd..549c318 100644
--- a/extensions/yii/apidoc/models/Context.php
+++ b/extensions/yii/apidoc/models/Context.php
@@ -47,10 +47,7 @@ class Context extends Component
 
 	public function addFile($fileName)
 	{
-		if (isset($this->files[$fileName])) {
-			return;
-		}
-		$this->files[$fileName] = $fileName;
+		$this->files[$fileName] = sha1_file($fileName);
 
 		$reflection = new FileReflector($fileName, true);
 		$reflection->process();
@@ -58,54 +55,18 @@ class Context extends Component
 		foreach($reflection->getClasses() as $class) {
 			$class = new ClassDoc($class);
 			$class->sourceFile = $fileName;
-			$this->addClass($class);
+			$this->classes[$class->name] = $class;
 		}
 		foreach($reflection->getInterfaces() as $interface) {
 			$interface = new InterfaceDoc($interface);
 			$interface->sourceFile = $fileName;
-			$this->addInterface($interface);
+			$this->interfaces[$interface->name] = $interface;
 		}
 		foreach($reflection->getTraits() as $trait) {
 			$trait = new TraitDoc($trait);
 			$trait->sourceFile = $fileName;
-			$this->addTrait($trait);
-		}
-	}
-
-	/**
-	 * @param ClassDoc $class
-	 * @throws \yii\base\Exception when class is already part of this context
-	 */
-	public function addClass($class)
-	{
-		if (isset($this->classes[$class->name])) {
-			throw new Exception('Duplicate class definition: ' . $class->name . ' in file ' . $class->sourceFile . '.');
-		}
-		$this->classes[$class->name] = $class;
-	}
-
-	/**
-	 * @param InterfaceDoc $interface
-	 * @throws \yii\base\Exception when interface is already part of this context
-	 */
-	public function addInterface($interface)
-	{
-		if (isset($this->interfaces[$interface->name])) {
-			throw new Exception('Duplicate interface definition: ' . $interface->name . ' in file ' . $interface->sourceFile);
-		}
-		$this->interfaces[$interface->name] = $interface;
-	}
-
-	/**
-	 * @param TraitDoc $trait
-	 * @throws \yii\base\Exception when trait is already part of this context
-	 */
-	public function addTrait($trait)
-	{
-		if (isset($this->traits[$trait->name])) {
-			throw new Exception('Duplicate trait definition: ' . $trait->name . ' in file ' . $trait->sourceFile);
+			$this->traits[$trait->name] = $trait;
 		}
-		$this->traits[$trait->name] = $trait;
 	}
 
 	public function updateReferences()