Organizing classes with __autoload()

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Organizing classes with __autoload()

Post by Benjamin »

I'm using an autoloader to load classes. I wrote this small class to display certain results. My question is this. If I want to run validation on the $answer variable, should I:

1. Create a new class in a different file and use the autoloader?
2. Put the validation class in the exampleClass file?
3. Just put a function in the exampleClass class to do the validation?
4. Perform the validation in the class that calls this class?
5. Other?

Code: Select all

<?php
class exampleClass
{
    private $cache = null;
    private $lf = "\n";

    function __construct()
    {
        $this->tableHead();
    }

    public function addAnswer($question, $answer)
    {
        $this->cache .= '<tr>' . $this->lf
                     .  '  <th>' . $question . '</th>' . $this->lf
                     .  '  <td>' . $answer . '</td>' . $this->lf
                     .  '</tr>';
    }

    public function getAnswers()
    {
        $this->tableFooter();
        return $this->cache;
    }

    private function tableHead()
    {
        $this->cache .= '<table cellpadding="0" cellspacing="0" border="0">' . $this->lf;
    }

    private function tableFooter()
    {
        $this->cache .= '</table>' . $this->lf;
    }
}

?>
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

That looks like a display/presentation class -- what often gets called the View. Usually you don't do validation there, but instead do it in you in your Model or up front in the Controller
(#10850)
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

Well I've gotten myself to this point by teaching myself. I have 12 pounds of books that should arrive tomorrow though :)

I'm trying to minimize the number of files I create. If I call the validation from the controller, where would I put the class? In a seperate file? It's only relevent to this class.

I'm just being nit-picky so that I build a good foundation to improve my skills from.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

astions wrote:I'm trying to minimize the number of files I create.
That may be a goal for data, but not for code. More smaller and simpler classes is generally a better direction to go than fewer bigger and more complex classes. Seek a good design and don't count files. The number "several dozen" was recently mentioned as pretty standard for the number of files just for the base framework.
astions wrote:If I call the validation from the controller, where would I put the class? In a seperate file? It's only relevent to this class.

I'm just being nit-picky so that I build a good foundation to improve my skills from.
If it is a generic Validator then put it in its own file. If it is domain specific then add methods to your Model class.
(#10850)
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

Ok, sounds good. Thank you for your knowledge.
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

For myself, I use the __autoload() to generate stub classes and a ServiceLocator to actually load and procure real classes. The stubs created by __autoload() log how and where they are used so I can make corrections.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

feyd wrote:For myself, I use the __autoload() to generate stub classes and a ServiceLocator to actually load and procure real classes. The stubs created by __autoload() log how and where they are used so I can make corrections.
Very, very cool idea feyd ... thanks for sharing. :D
(#10850)
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

feyd wrote:For myself, I use the __autoload() to generate stub classes and a ServiceLocator to actually load and procure real classes. The stubs created by __autoload() log how and where they are used so I can make corrections.
Doesn't that create a lot of overhead? The reason I like __autoload() is because I don't have to include "everything" that might be required, and each page can dynamically call what it needs. If your having each stub log an event and then include other files, that sounds like a lot of disk i/o for every page request.
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

astions wrote:Doesn't that create a lot of overhead? The reason I like __autoload() is because I don't have to include "everything" that might be required, and each page can dynamically call what it needs. If your having each stub log an event and then include other files, that sounds like a lot of disk i/o for every page request.
The overhead is most often only during development as I cannot guarantee whether another __autoload() is already defined or not, nor do I want to dictate that people use my __autoload(). When my __autoload() is active it accesses the ServiceLocator checking for the existance of the class. If not found, it generates the stub and logs the class not being defined. When found, it activates the ServiceLocator to do the loading and logs that X file used Y class.

This gives me documentation of the coupling, but also gives me information of where I could place ServiceLocator calls to load a class giving me a more optimal loading pattern. My ServiceLocator supports loading whole trees of classes too though when its more straight forward to load all storage classes, for instance.
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

K I'll have to learn more about that.
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

feyd wrote:For myself, I use the __autoload() to generate stub classes and a ServiceLocator to actually load and procure real classes. The stubs created by __autoload() log how and where they are used so I can make corrections.
Can you explain that in more depth. With some example code perhaps. What is a stub anyway?
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

ole wrote:Can you explain that in more depth. With some example code perhaps. What is a stub anyway?
I generally consider __autoload() more of an error handling functionality than something magical.

A stub, in this case, is a dynamically created class that fills the requirement for the namespace requested to be filled. My stubs overload a few of the magic methods supported by PHP 5.1. In this way it can handle property and method requests similar to the expected class. It allows PHP to continue execution without breaking fatally because I or a third party forgot to include the proper file or call the ServiceLocator.

I can't show actual code right now as my ServiceLocator is in a massive state of flux, but I'll show you the basics of what my __autoload() does.

Code: Select all

function flash($var)
{
  if($var === null)
  {
    return 'null';
  }
  elseif(is_array($var))
  {
    $c = 0;
    $o = 'array(';
    foreach($var as $k => $v)
    {
      $o .= ($c > 0 ? ',' : '') . ($k == $c ? '' : flash($k) . '=>') . flash($v);
      $c++;
    }
    $o .= ')';
    return $o;
  }
  elseif(is_scalar($var))
  {
    return var_export($var,true);
  }
  elseif(is_object($var))
  {
    return 'object ' . get_class($var);
  }
  elseif(is_resource($var))
  {
    return 'resource ' . get_resource_type($var);
  }
  else
  {
    return 'unknown';
  }
}

function flashOver($aForetext = null, $aArgs = null)
{
  $trace = debug_backtrace();
  //var_dump($trace);
  if (isset($trace[1]))
  {
    $line = (isset($trace[1]['line']) ? intval($trace[1]['line']) : '??');
	$file = (isset($trace[1]['file']) ? $trace[1]['file'] : 'Unknown file');
	$prefix = '(' . $line . ')' . $file . ': ';
  }
  else
  {
    $prefix = '';
  }

  if (func_num_args() == 2 and is_string($aForetext) and is_array($aArgs))
  {
    $call = $aForetext;
	$args = $aArgs;
  }
  else
  {
    $call = (!empty($trace[1]['class']) ? $trace[1]['class'] . $trace[1]['type'] : '') . $trace[1]['function'];
    $args = $trace[1]['args'];
  }
  
  $args = '(' . implode(', ', array_map('flash', $args)) . ')';

  return $prefix . $call . $args;
}

if (!function_exists('__autoload'))
{
  function __autoload($aClassName)
  {
    $code =<<<STOP
class {$aClassName}
{
  private \$store;
  public function __construct()
  {
    \$this->store = array();
    echo flashOver(), PHP_EOL;
  }
  public function __destruct()
  {
    echo flashOver(), PHP_EOL;
  }
  private function __set(\$aVar, \$aValue)
  {
	echo __CLASS__, (isset(\$this) ? '->' : '::'), \$aVar, ' = ' . flash(\$aValue), PHP_EOL;
	\$this->store[\$aVar] = \$aValue;
  }
  private function __get(\$aVar)
  {
    echo __CLASS__, (isset(\$this) ? '->' : '::'), \$aVar, PHP_EOL;
	return \$this->store[\$aVar];
  }
  private function __call(\$aFunction, \$aArgs)
  {
    echo flashOver(__CLASS__ . (isset(\$this) ? '->' : '::') . \$aFunction, \$aArgs), PHP_EOL;
	return null;
  }
}
STOP;
    echo flashOver(), PHP_EOL;
    eval($code);
  }
}


$a = new UnknownClassName('arg1','arg2');
could possibly kick out:

Code: Select all

(133)/tests/stubTest.php: __autoload('UnknownClassName')
(133)/tests/stubTest.php: UnknownClassName->__construct('arg1', 'arg2')
(??)Unknown file: UnknownClassName::__destruct()
Now, this is just a rough example, but it's a working one. Image
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

I guess I have a few more questions related to this so I'll just add to this thread.

If I have a class, which requires subclasses, and I put them all in the same folder, using an autoloader is easy. But what if I don't want all my classes in the same folder. On a large site I could end up with thousands of them. What if I create a class in a folder, and then put all the classes that it requires in a subfolder, with the exception of universal classes such as the database class. Now that makes it more organized, but then how do I use an autoloader with it?

Since the different classes are in different folders, I can't just use 1 autoloader. Can I declare an autoloader as a private function inside of the main class? Or do I just write a little bit of code that automatically includes all the .php files in a certain directory?
jmut
Forum Regular
Posts: 945
Joined: Tue Jul 05, 2005 3:54 am
Location: Sofia, Bulgaria
Contact:

Post by jmut »

astions wrote:I guess I have a few more questions related to this so I'll just add to this thread.

If I have a class, which requires subclasses, and I put them all in the same folder, using an autoloader is easy. But what if I don't want all my classes in the same folder. On a large site I could end up with thousands of them. What if I create a class in a folder, and then put all the classes that it requires in a subfolder, with the exception of universal classes such as the database class. Now that makes it more organized, but then how do I use an autoloader with it?

Since the different classes are in different folders, I can't just use 1 autoloader. Can I declare an autoloader as a private function inside of the main class? Or do I just write a little bit of code that automatically includes all the .php files in a certain directory?
yes, you can use one autoloader to load all your classes. One way is to use naming convention for your classes though and appropriate dir structure.
For example


library/
...........Controller/
........................Action.php (declares class Controller_Action)
........................Index.php (declares class Controller_Index)
...........Controller.php (declares class Controller)
...etc....

Code: Select all

define('CLASS_PATH',dirname(__FILE__)."/library");
set_include_path(get_include_path() . PATH_SEPARATOR .CLASS_PATH );
function __autoload($classname) {

    if (class_exists($classname, false)) {
        return;
    }
    $path = str_replace('_',DIRECTORY_SEPARATOR, $classname);
    include_once($path.".php");

}

Edit:
Also take a look at spl_autoload_* family functions
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

feyd wrote:Now, this is just a rough example, but it's a working one.
Well its very clever, and I understand what its doing but now I don't understand why.
feyd wrote:This gives me documentation of the coupling, but also gives me information of where I could place ServiceLocator calls to load a class giving me a more optimal loading pattern.
It does?
jmut wrote:One way is to use naming convention for your classes though and appropriate dir structure.
Yep that's almost exactly what happens in the ZF.
Post Reply