Page 1 of 1

Simple Class API Generator

Posted: Sat Sep 15, 2007 4:12 pm
by jeffery
PHP 5 comes with a complete reflection API that adds the ability to reverse-engineer classes, interfaces, functions and methods as well as extensions. Additionally, the reflection API also offers ways of retrieving doc comments for functions, classes and methods. This is extremely handy when you want to generate an API for the code you are writing.

Now I know there are already projects dedicated for creating API's from documentation. Projects such as phpDocumentor do the job very well. But what I have here is a simple API Generator without all the bells and whistles.

This code was developed so that I can show the Class functions and its params which were exposed through Webservices. Nowadays I tend to use its functionality to just observe what functions are available within a class and its params. Example if I want to view the functions available in the SPL DirectoryIterator class, I don't have to head to the php.net manual, instead I just do the following:

Code: Select all

<?php
$api = new SCA_Generator('DirectoryIterator');
$api->create_class_api(true, true);
?>

Code: Select all

<?php

/**
 * This is an API generator Class.
 *
 * It will generate documentation for a given class which has already been
 * loaded with an include or require call.
 *
 * @author Jeffery Fernandez <developer@jefferyfernandez.id.au>
 * @copyright Free to use as long as you keep the Authors name intact
 * @link http://jefferyfernandez.id.au/2007/09/0 ... generator/
 *
 * @todo Get the constants of the class
 *
 * Simple Class API Generator
 * @since 1st September, 2007
 * @version 0.3 Updated on 15th September, 2007
 */
Class SCA_Generator
{
	/**
	 * To store the Class name
	 *
	 * @var $class_name
	 */
	private $class_name;


	/**
	 * The constructor for the API Generator
	 *
	 * @param string $class_name The name of the Class for which to generate the API
	 * @return void
	 */
	public function __construct($class_name)
	{
		if (class_exists($class_name))
		{
			$this->class_name = $class_name;
		}
		else
		{
			echo "Class not loaded. Please include/require the Class ($class_name) you have specified";
			exit;
		}
	}


	/**
	 * A Test function with bogus params
	 *
	 * @param SCA_Generator $blah
	 * @param array $test
	 * @return void
	 */
	public static function test_function(SCA_Generator $blah, $test = array())
	{
		echo "This is just a test function to show you how the API is generated";
	}

	/**
	 * Generates the API for the Class
	 *
	 * @param boolean $show_private Decide to show private variables/functions
	 * @param boolean $show_protected Decide to show protected variables/functions
	 * @return void
	 */
	public function create_class_api($show_private = false, $show_protected = false)
	{
		$r_class = new reflectionClass($this->class_name);


		###################### Class & its Definition ######################
		// Get the Comments
		$comments = $r_class->getDocComment();

		// check if the class extends any other classes
		$parent_class = $r_class->getParentClass();
		$extends = (isset($parent_class->name)) ? " extends {$parent_class->name}" : '';

		// Get the Class type
		$class_type = $r_class->isInterface()
			? 'Interface '
			: (
				($r_class->isAbstract())
				? 'Abstract Class'
				: (
					($r_class->isFinal())
					? 'Final Class '
					: 'Class '
					)
				);

		// Get the Class interfaces
		$interfaces = $r_class->getInterfaces();
		$implements = '';
		if (count($interfaces))
		{
			foreach ($interfaces as $name => $reflection_object)
			{
				$implements .= ($implements == '')
					? " implements {$reflection_object->name}"
					: ", {$reflection_object->name}";
			}
		}

		echo "<pre>$comments\n{$class_type}{$this->class_name}{$extends}{$implements}\n{</pre>";
		###################### Class & its Definition ######################


		###################### Class Properties & Comments #################
		$class_constants = $r_class->getConstants();
		//@todo print_r($class_constants);

		$class_attributes = $r_class->getProperties();
		$attributes = array();
		foreach($class_attributes as $property)
		{
			// If we are allowed to see private/protected properties
			if(
				($show_private && $property->isPrivate()) ||
				($show_protected && $property->isProtected()) ||
				((!$property->isPrivate()) && (!$property->isProtected()))
			)
			{
				$r_property = new reflectionProperty($this->class_name, $property->getName());

				// Get the Comments for the property
				$attributes[$property->name]['comment'] = $r_property->getDocComment();

				// Get the Visibility
				$property_visibility[$property->name] = ($property->isPrivate())
					? 'private'
					: ($property->isProtected() ? 'protected' : 'public');
			}
		}

		ksort($attributes);
		foreach ($attributes as $index => $attribute)
		{
			echo "<pre>\t{$attribute['comment']}\n\t{$property_visibility[$index]} \${$index};</pre>";
		}
		###################### Class Properties & Comments #################

		###################### Methods & Parameters ########################
		$class_methods = $r_class->getMethods();
		foreach($class_methods as $method)
		{
			$r_method = new reflectionMethod($this->class_name, $method->name);
			if (
				($show_private && $r_method->isPrivate()) ||
				($show_protected && $r_method->isProtected()) ||
				((!$r_method->isPrivate()) && (!$r_method->isProtected()))
			)
			{
				// Check if its a static method
				$static = ($r_method->isStatic()) ? ' static' : '';

				// check the visibility of the method
				$visibility = ($r_method->isPrivate())
					? 'private'
					: ($r_method->isProtected() ? 'protected' : 'public');

				// Get the comments for the method
				$method_comments = $r_method->getDocComment();

				$parameters = '';
				$method->params = $method->getParameters();
				$all_optional = false;
				foreach ($method->params as $position => $parameter)
				{
					// Check if the first parameter is optional.
					if ($position == 0)
					{
						$all_optional = ($parameter->isOptional()) ? true : false;
					}

					// Get the Default values for  the parameter
					$default_value = '';
					if ($parameter->isDefaultValueAvailable())
					{
						$value = var_export($parameter->getDefaultValue(), true);
						$default_value = ' = ' . str_replace("\n", '', $value);
					}

					// Check if the parameter is of a Class Type
					$class_type = ($parameter->getClass()) ? $parameter->getClass()->name . ' ' : '';

					// check if the parameter is passed by reference
					$reference = ($parameter->isPassedByReference()) ? '&' : '';

					// Build the parameter
					$prmtr = (
					   ($parameters != '')
					       ? ", "
					       : ''
					) .
					"{$class_type}{$reference}\${$parameter->name}{$default_value}";

					// Check if it is an optional parameter and add the square brackets
					$prmtr = (
					           $parameter->isOptional() &&
					           (!
					               (
					                   ($position == 0) &&
					                   ($all_optional)
					               )
					            )
					         )
					         ? "[{$prmtr}]"
					         : $prmtr;
					$parameters .= $prmtr;
				}
				$parameters = ($all_optional) ? "[{$parameters}]" : $parameters;

				echo "<pre>\t{$method_comments}\n\t{$visibility}{$static} function {$method->name}({$parameters})</pre>";
				echo "<br />";
			}
		}
		###################### Methods & Parameters ########################
		echo "}";
	}
}

$api = new SCA_Generator('SCA_Generator');
$api->create_class_api(true, true);

?>
cheers,
Jeffery

Posted: Sat Sep 15, 2007 6:17 pm
by Christopher
Can you show and example of the output?

I think my only suggestion would be to separate it into a data gathering class and a HTML writer class. Then could modify the output or do different writers, such as an XML stats feed or emailing text stats, etc.

Posted: Sat Sep 15, 2007 6:23 pm
by jeffery
arborint wrote:Can you show and example of the output?
sure, the output of the above will result in the following html:

Code: Select all

/**
 * This is an API generator Class.
 *
 * It will generate documentation for a given class which has already been
 * loaded with an include or require call.
 *
 * @author Jeffery Fernandez 
 * @copyright Free to use as long as you keep the Authors name intact
 * @link http://jefferyfernandez.id.au/2007/09/01/simple-class-api-generator/
 *
 * @todo Get the constants of the class
 *
 * Simple Class API Generator
 * @since 1st September, 2007
 * @version 0.3 Updated on 15th September, 2007
 */
Class SCA_Generator
{

	/**
	 * To store the Class name
	 *
	 * @var $class_name
	 */
	private $class_name;

	/**
	 * The constructor for the API Generator
	 *
	 * @param string $class_name The name of the Class for which to generate the API
	 * @return void
	 */
	public function __construct($class_name)


	/**
	 * A Test function with bogus params
	 *
	 * @param SCA_Generator $blah
	 * @param array $test
	 * @return void
	 */
	public static function test_function(SCA_Generator $blah[, $test = array ()])


	/**
	 * Generates the API for the Class
	 *
	 * @param boolean $show_private Decide to show private variables/functions
	 * @param boolean $show_protected Decide to show protected variables/functions
	 * @return void
	 */
	public function create_class_api([$show_private = false[, $show_protected = false]])


}
arborint wrote: I think my only suggestion would be to separate it into a data gathering class and a HTML writer class. Then could modify the output or do different writers, such as an XML stats feed or emailing text stats, etc.
In theory thats a good idea. But as I said in my previous post, the idea was to keep it simple.

cheers,
Jeffery