| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 | 
							- <?php
 
- namespace mindplay\demo;
 
- use Composer\Autoload\ClassLoader;
 
- use mindplay\annotations\AnnotationCache;
 
- use mindplay\annotations\Annotations;
 
- use mindplay\demo\annotations\Package;
 
- ## Configure a simple auto-loader
 
- $vendor_path = dirname(__DIR__) . '/vendor';
 
- if (!is_dir($vendor_path)) {
 
-     echo 'Install dependencies first' . PHP_EOL;
 
-     exit(1);
 
- }
 
- require_once($vendor_path . '/autoload.php');
 
- $auto_loader = new ClassLoader();
 
- $auto_loader->addPsr4("mindplay\\demo\\", __DIR__);
 
- $auto_loader->register();
 
- ## Configure the cache-path. The static `Annotations` class will configure any public
 
- ## properties of `AnnotationManager` when it creates it. The `AnnotationManager::$cachePath`
 
- ## property is a path to a writable folder, where the `AnnotationManager` caches parsed
 
- ## annotations from individual source code files.
 
- Annotations::$config['cache'] = new AnnotationCache(__DIR__ . '/runtime');
 
- ## Register demo annotations.
 
- Package::register(Annotations::getManager());
 
- ## For this example, we're going to generate a simple form that allows us to edit a `Person`
 
- ## object. We'll define a few public properties and annotate them with some useful metadata,
 
- ## which will enable us to make decisions (at run-time) about how to display each field,
 
- ## how to parse the values posted back from the form, and how to validate the input.
 
- ##
 
- ## Note the use of standard PHP-DOC annotations, such as `@var string` - this metadata is
 
- ## traditionally useful both as documentation to developers, and as hints for an IDE. In
 
- ## this example, we're going to use that same information as advice to our components, at
 
- ## run-time, to help them establish defaults and make sensible decisions about how to
 
- ## handle the value of each property.
 
- class Person
 
- {
 
-     /**
 
-      * @var string
 
-      * @required
 
-      * @length(50)
 
-      * @text('label' => 'Full Name')
 
-      */
 
-     public $name;
 
-     /**
 
-      * @var string
 
-      * @length(50)
 
-      * @text('label' => 'Street Address')
 
-      */
 
-     public $address;
 
-     /**
 
-      * @var int
 
-      * @range(0, 100)
 
-      */
 
-     public $age;
 
- }
 
- ## To build a simple form abstraction that can manage the state of an object being edited,
 
- ## we start with a simple, abstract base class for input widgets.
 
- abstract class Widget
 
- {
 
-     protected $object;
 
-     protected $property;
 
-     public $value;
 
-     ## Each widget will maintain a list of error messages.
 
-     public $errors = array();
 
-     ## A widget needs to know which property of what object is being edited.
 
-     public function __construct($object, $property)
 
-     {
 
-         $this->object = $object;
 
-         $this->property = $property;
 
-         $this->value = $object->$property;
 
-     }
 
-     ## Widget classes will use this method to add an error-message.
 
-     public function addError($message)
 
-     {
 
-         $this->errors[] = $message;
 
-     }
 
-     ## This helper function provides a shortcut to get a named property from a
 
-     ## particular type of annotation - if no annotation is found, the `$default`
 
-     ## value is returned instead.
 
-     protected function getMetadata($type, $name, $default = null)
 
-     {
 
-         $a = Annotations::ofProperty($this->object, $this->property, $type);
 
-         if (!count($a)) {
 
-             return $default;
 
-         }
 
-         return $a[0]->$name;
 
-     }
 
-     ## Each type of widget will need to implement this interface, which takes a raw
 
-     ## POST value from the form, and attempts to bind it to the object's property.
 
-     abstract public function update($input);
 
-     ## After a widget successfully updates a property, we may need to perform additional
 
-     ## validation - this method will perform some basic validations, and if errors are
 
-     ## found, it will add them to the `$errors` collection.
 
-     public function validate()
 
-     {
 
-         if (empty($this->value)) {
 
-             if ($this->isRequired()) {
 
-                 $this->addError("Please complete this field");
 
-             } else {
 
-                 return;
 
-             }
 
-         }
 
-         if (is_string($this->value)) {
 
-             $min = $this->getMetadata('@length', 'min');
 
-             $max = $this->getMetadata('@length', 'max');
 
-             if ($min !== null && strlen($this->value) < $min) {
 
-                 $this->addError("Minimum length is {$min} characters");
 
-             } else {
 
-                 if ($max !== null && strlen($this->value) > $max) {
 
-                     $this->addError("Maximum length is {$max} characters");
 
-                 }
 
-             }
 
-         }
 
-         if (is_int($this->value)) {
 
-             $min = $this->getMetadata('@range', 'min');
 
-             $max = $this->getMetadata('@range', 'max');
 
-             if (($min !== null && $this->value < $min) || ($max !== null && $this->value > $max)) {
 
-                 $this->addError("Please enter a value in the range {$min} to {$max}");
 
-             }
 
-         }
 
-     }
 
-     ## Each type of widget will need to implement this interface, which renders an
 
-     ## HTML input representing the widget's current value.
 
-     abstract public function display();
 
-     ## This helper function returns a descriptive label for the input.
 
-     public function getLabel()
 
-     {
 
-         return $this->getMetadata('@text', 'label', ucfirst($this->property));
 
-     }
 
-     ## Finally, this little helper function will tell us if the field is required -
 
-     ## if a property is annotated with `@required`, the field must be filled in.
 
-     public function isRequired()
 
-     {
 
-         return count(Annotations::ofProperty($this->object, $this->property, '@required')) > 0;
 
-     }
 
- }
 
- ## The first and most basic kind of widget, is this simple string widget.
 
- class StringWidget extends Widget
 
- {
 
-     ## On update, take into account the min/max string length, and provide error
 
-     ## messages if the constraints are violated.
 
-     public function update($input)
 
-     {
 
-         $this->value = $input;
 
-         $this->validate();
 
-     }
 
-     ## On display, render out a simple `<input type="text"/>` field, taking into account
 
-     ## the maximum string-length.
 
-     public function display()
 
-     {
 
-         $length = $this->getMetadata('@length', 'max', 255);
 
-         echo '<input type="text" name="' . get_class($this->object) . '[' . $this->property . ']"'
 
-             . ' maxlength="' . $length . '" value="' . htmlspecialchars($this->value) . '"/>';
 
-     }
 
- }
 
- ## For the age input, we'll need a specialized `StringWidget` that also checks the input type.
 
- class IntWidget extends StringWidget
 
- {
 
-     ## On update, take into account the min/max numerical range, and provide error
 
-     ## messages if the constraints are violated.
 
-     public function update($input)
 
-     {
 
-         if (strval(intval($input)) === $input) {
 
-             $this->value = intval($input);
 
-             $this->validate();
 
-         } else {
 
-             $this->value = $input;
 
-             if (!empty($input)) {
 
-                 $this->addError("Please enter a whole number value");
 
-             }
 
-         }
 
-     }
 
- }
 
- ## Next, we can build a simple form abstraction - this will hold and object and manage
 
- ## the widgets required to edit the object.
 
- class Form
 
- {
 
-     private $object;
 
-     /**
 
-      * Widget list.
 
-      *
 
-      * @var Widget[]
 
-      */
 
-     private $widgets = array();
 
-     ## The constructor just needs to know which object we're editing.
 
-     ##
 
-     ## Using reflection, we enumerate the properties of the object's type, and using the
 
-     ## `@var` annotation, we decide which type of widget we're going to use.
 
-     public function __construct($object)
 
-     {
 
-         $this->object = $object;
 
-         $class = new \ReflectionClass($this->object);
 
-         foreach ($class->getProperties() as $property) {
 
-             $type = $this->getMetadata($property->name, '@var', 'type', 'string');
 
-             $wtype = 'mindplay\\demo\\' . ucfirst($type) . 'Widget';
 
-             $this->widgets[$property->name] = new $wtype($this->object, $property->name);
 
-         }
 
-     }
 
-     ## This helper-method is similar to the one we defined for the widget base
 
-     ## class, but fetches annotations for the specified property.
 
-     private function getMetadata($property, $type, $name, $default = null)
 
-     {
 
-         $a = Annotations::ofProperty(get_class($this->object), $property, $type);
 
-         if (!count($a)) {
 
-             return $default;
 
-         }
 
-         return $a[0]->$name;
 
-     }
 
-     ## When you post information back to the form, we'll need to update it's state,
 
-     ## validate each of the fields, and return a value indicating whether the form
 
-     ## update was successful.
 
-     public function update($post)
 
-     {
 
-         $data = $post[get_class($this->object)];
 
-         foreach ($this->widgets as $property => $widget) {
 
-             if (array_key_exists($property, $data)) {
 
-                 $this->widgets[$property]->update($data[$property]);
 
-             }
 
-         }
 
-         $valid = true;
 
-         foreach ($this->widgets as $widget) {
 
-             $valid = $valid && (count($widget->errors) === 0);
 
-         }
 
-         if ($valid) {
 
-             foreach ($this->widgets as $property => $widget) {
 
-                 $this->object->$property = $widget->value;
 
-             }
 
-         }
 
-         return $valid;
 
-     }
 
-     ## Finally, this method renders out the form, and each of the widgets inside, with
 
-     ## a `<label>` tag surrounding each input.
 
-     public function display()
 
-     {
 
-         foreach ($this->widgets as $widget) {
 
-             $star = $widget->isRequired() ? ' <span style="color:red">*</span>' : '';
 
-             echo '<label>' . htmlspecialchars($widget->getLabel()) . $star . '<br/>';
 
-             $widget->display();
 
-             echo '</label><br/>';
 
-             if (count($widget->errors)) {
 
-                 echo '<ul>';
 
-                 foreach ($widget->errors as $error) {
 
-                     echo '<li>' . htmlspecialchars($error) . '</li>';
 
-                 }
 
-                 echo '</ul>';
 
-             }
 
-         }
 
-     }
 
- }
 
- ## Now let's put the whole thing to work...
 
- ##
 
- ## We'll create a `Person` object, create a `Form` for the object, and render it!
 
- ##
 
- ## Try leaving the name field empty, or try to tell the form you're 120 years old -
 
- ## it won't pass validation.
 
- ##
 
- ## You can see the state of the object being displayed below the form - as you can
 
- ## see, unless all updates and validations succeed, the state of your object is
 
- ## left untouched.
 
- echo <<<HTML
 
- <html>
 
-   <head>
 
-     <title>Metaprogramming With Annotations!</title>
 
-   </head>
 
-   <body>
 
-     <h1>Edit a Person!</h1>
 
-     <h4>Declarative Metaprogramming in action!</h4>
 
-     <form method="post">
 
- HTML;
 
- $person = new Person;
 
- $form = new Form($person);
 
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 
-     if ($form->update($_POST)) {
 
-         echo '<h2 style="color:green">Person Accepted!</h2>';
 
-     } else {
 
-         echo '<h2 style="color:red">Oops! Try again.</h2>';
 
-     }
 
- }
 
- $form->display();
 
- echo <<<HTML
 
-     <br/>
 
-     <input type="submit" value="Go!"/>
 
-     </form>
 
- HTML;
 
- echo "<pre>\n\nHere's what your Person instance currently looks like:\n\n";
 
- var_dump($person);
 
- echo '</pre>';
 
- echo <<<HTML
 
-   </body>
 
- </html>
 
- HTML;
 
 
  |