Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SOLID - It's Not Just a State of Matter, It's Principles for OO Propriety

SOLID - It's Not Just a State of Matter, It's Principles for OO Propriety

Principles abound our lives. Software and engineering is no exception. The more you develop software, the more patterns and principles you master. Yet, solutions remain difficult to maintain and developers are frequently frustrated by impediments they introduce and the waste generated to get around them. But why do we run into these roadblocks? It could be that you're not using the principles of object-oriented development to build better solutions. The SOLID principles are an important foundation for how to leverage OO languages to produce high value, low overhead, maintainable solutions. Whether you've just starting to sling code or have been a veteran in the industry, the principles taught in this presentation are invaluable.

neraath

July 01, 2012
Tweet

More Decks by neraath

Other Decks in Programming

Transcript

  1. SOLID
    Not Just a State of Matter, It’s Principles for OO Propriety
    Chris Weldon
    Saturday, June 30, 12

    View Slide

  2. Me
    • Fightin’ Texas Aggie
    • PHP Developer since 2003
    • Senior Consultant at Improving Enterprises
    • mojolive.com/profile/neraath
    • Contact Me: [email protected]
    Saturday, June 30, 12

    View Slide

  3. apihackday.com
    Saturday, June 30, 12

    View Slide

  4. TOMORROW
    • Free!
    • Food, Drinks, and Beer Provided
    • $750 Amazon Cert, $250 Apple Cert, Other Prizes
    • Register NOW: http://apihackday.com
    Saturday, June 30, 12

    View Slide

  5. What is OOD?
    Saturday, June 30, 12

    View Slide

  6. What is OOD?
    Abstraction
    Saturday, June 30, 12

    View Slide

  7. What is OOD?
    Inheritance
    Abstraction
    Saturday, June 30, 12

    View Slide

  8. What is OOD?
    Encapsulation
    Inheritance
    Abstraction
    Saturday, June 30, 12

    View Slide

  9. What is OOD?
    Encapsulation Polymorphism
    Inheritance
    Abstraction
    Saturday, June 30, 12

    View Slide

  10. ?
    Saturday, June 30, 12

    View Slide

  11. Saturday, June 30, 12

    View Slide

  12. An Order
    Saturday, June 30, 12

    View Slide

  13. class Order
    {
    private $_id;
    private $_userId;
    private $_products;
    public function getId() {
    return $this->_id;
    }
    public function setId($id) {
    $this->_id = $id;
    }
    public function getUserId() {
    return $this->_userId;
    }
    public function setUserId($userId) {
    $this->_userId = $userId;
    }
    Saturday, June 30, 12

    View Slide

  14. public function getProducts() {
    return $this->_products;
    }
    public function addProduct($product) {
    $this->_products->add($product);
    }
    public function setProducts(Product[] $products) {
    $this->_products = $products;
    }
    Saturday, June 30, 12

    View Slide

  15. New Requirement!
    Calculate Cost of Order
    Saturday, June 30, 12

    View Slide

  16. class OrderUtilities
    {
    public static function calculateCostOfOrder($order)
    {
    $products = $order->getProducts();
    $cost = 0.0;
    foreach ($products as $product) {
    $cost += $product->getCost();
    }
    return $cost;
    }
    Saturday, June 30, 12

    View Slide

  17. New Requirements!
    Fetch Orders from Database
    Fetch User for Order
    Saturday, June 30, 12

    View Slide

  18. public static function getOrderById($orderId)
    {
    if ($orderId == null || $orderId < 0)
    {
    $command = $this->_db->exec("SELECT * FROM orders");
    $results = $this->_db->getResults($command);
    // Get the first result.
    return $results[0];
    }
    $command = $this->_db->exec("SELECT * FROM orders WHERE id = ?", $orderId);
    $order = $this->_db->getSingle($command);
    return $order;
    }
    Saturday, June 30, 12

    View Slide

  19. public static function getUserForOrder($order)
    {
    $command = $this->_db->exec("SELECT * FROM users WHERE id = ?", $order->getUserId());
    $user = $this->_db->getSingle($command);
    return $user;
    }
    Saturday, June 30, 12

    View Slide

  20. New Requirements!
    Deliver Order to Customer
    Notify Customer of Delivery
    Saturday, June 30, 12

    View Slide

  21. public static function deliverOrder()
    {
    try {
    $networkConnection = new WebServiceConnection("http://shpt1/shipctrl.svc");
    $networkConnection->open();
    $shippingManager = new ShippingManager($networkConnection);
    $shippingManager->setOrder($this);
    $shippingManager->setDeliveryType(DeliveryType::EXPRESS);
    $shippingManager->shipViaWebService();
    $this->notifyCustomer();
    } catch (\Exception $e) {
    $logFile = fopen("/tmp/logfile.log", "a");
    fwrite("An error occurred while delivering the order.", $logFile);
    fclose($logFile);
    }
    }
    Saturday, June 30, 12

    View Slide

  22. public static function notifyCustomer()
    {
    try {
    $mailer = new \Zend_Mail();
    $mailer->setFrom('[email protected]', 'Grumpy Baby Orders');
    $mailer->setSubject('Order #' . $this->getId() . ' out for Delivery');
    $mailer->setBodyText('Your order is being shipped!');
    $mailer->send();
    } catch (\Exception $e) {
    self::logNotificationError($errorMessage);
    }
    }
    Saturday, June 30, 12

    View Slide

  23. public static function logDeliveryError($errorMessage)
    {
    $logFile = fopen("/tmp/logfile.log", "a");
    fwrite("An error occurred while delivering the order.", $logFile);
    fwrite($errorMessage, $logFile);
    fclose($logFile);
    }
    public static function logNotificationError($errorMessage)
    {
    $logFile = fopen("/tmp/logfile.log", "a");
    fwrite("An error occurred while emailing the notice.", $logFile);
    fwrite($errorMessage, $logFile);
    fclose($logFile);
    }
    }
    Saturday, June 30, 12

    View Slide

  24. Saturday, June 30, 12

    View Slide

  25. OMG
    Saturday, June 30, 12

    View Slide

  26. OMG
    Who vomited in my codebase?
    Saturday, June 30, 12

    View Slide

  27. Saturday, June 30, 12

    View Slide

  28. “There should never be more than one reason
    for a class to change”
    Saturday, June 30, 12

    View Slide

  29. Improve
    Saturday, June 30, 12

    View Slide

  30. Improve
    class Order
    {
    // I’m just a model
    }
    Saturday, June 30, 12

    View Slide

  31. Improve
    class Order
    {
    // I’m just a model
    }
    class OrderDao
    {
    // I just talk to the database
    public function CreateOrder($userId,
    array $products);
    public function getOrderById($orderId);
    }
    Saturday, June 30, 12

    View Slide

  32. Improve
    class Order
    {
    // I’m just a model
    }
    class OrderDao
    {
    // I just talk to the database
    public function CreateOrder($userId,
    array $products);
    public function getOrderById($orderId);
    }
    class OrderDeliverer
    {
    // I just deliver orders
    public function deliverOrder();
    }
    Saturday, June 30, 12

    View Slide

  33. Improve
    class Order
    {
    // I’m just a model
    }
    class OrderDao
    {
    // I just talk to the database
    public function CreateOrder($userId,
    array $products);
    public function getOrderById($orderId);
    }
    class OrderDeliverer
    {
    // I just deliver orders
    public function deliverOrder();
    }
    class CustomerNotifier
    {
    // I just deliver orders
    public function notifyCustomer();
    }
    Saturday, June 30, 12

    View Slide

  34. Improve
    class Order
    {
    // I’m just a model
    }
    class OrderDao
    {
    // I just talk to the database
    public function CreateOrder($userId,
    array $products);
    public function getOrderById($orderId);
    }
    class OrderDeliverer
    {
    // I just deliver orders
    public function deliverOrder();
    }
    class CustomerNotifier
    {
    // I just deliver orders
    public function notifyCustomer();
    }
    it’s what we do
    Saturday, June 30, 12

    View Slide

  35. Zend_Log
    kLogger
    log4php
    log5php
    pear/Log
    Saturday, June 30, 12

    View Slide

  36. Next Sample
    Saturday, June 30, 12

    View Slide

  37. public function ActivateDrillBit($customerOption) {
    if ($customerOption == "small") {
    $drillBit = new SmallBit();
    $drillBit->activate();
    } else if ($customerOption == "medium") {
    $drillBit = new MediumBit();
    $drillBit->activate("120hz");
    } else if ($customerOption == "large") {
    $drillBit = new LargeBit();
    $drillBit->activate("240hz", Options::Water);
    }
    }
    Saturday, June 30, 12

    View Slide

  38. Customer Needs to Specify Options
    Requirements Change!
    Saturday, June 30, 12

    View Slide

  39. public function ActivateDrillBit($customerOption, $freq, Options $options)
    {
    if ($freq == "") $freq = "240hz";
    if ($options == null) $options = Options::NoWater;
    if ($customerOption == "small") {
    $drillBit = new SmallBit();
    $drillBit.activate($freq, $options);
    } else if (customerOption == "medium") {
    $drillBit = new MediumBit();
    $drillBit.activate($freq, $options);
    } else if (customerOption == "large") {
    $drillBit = new LargeBit();
    $drillBit.activate($freq, $options);
    }
    }
    Saturday, June 30, 12

    View Slide

  40. Saturday, June 30, 12

    View Slide

  41. You
    Broke
    My
    App
    !@#!@
    Saturday, June 30, 12

    View Slide

  42. Saturday, June 30, 12

    View Slide

  43. class DrillBitActivator {
    public function ActivateDrillBit($customerOption) {
    // ...
    }
    }
    class DrillBitConfigurableActivator implements DrillBitActivator {
    public __construct($freqency, Options $options) {
    // Configurable!
    }
    public function ActivateDrillBit($customerOption) {
    // ...
    }
    }
    Saturday, June 30, 12

    View Slide

  44. public function ActivateDrillBit($customerOption)
    /** @var IDrillBit $drillBit */
    $drillBit = DrillBitFactory::CreateDrillBit($customerOption);
    $drillBit.activate($this->_freq, $this->_options);
    }
    Saturday, June 30, 12

    View Slide

  45. Next
    Saturday, June 30, 12

    View Slide

  46. Saturday, June 30, 12

    View Slide

  47. Saturday, June 30, 12

    View Slide

  48. A Familiar Example
    Saturday, June 30, 12

    View Slide

  49. interface IManageCustomers {
    function TakeSpecifications(Specs $specs);
    function ReviewSpecifications(Specs $specs);
    function GiveToEngineers(Specs $specs);
    function DealWithTheGoshDarnCustomers();
    function IsPeoplePerson();
    }
    Saturday, June 30, 12

    View Slide

  50. class GoodManager implements IManageCustomers {
    public function TakeSpecifications(Specs $specs) {
    $this->ReviewSpecifications($specs);
    }
    public function ReviewSpecifications(Specs $specs) {
    // If specs seem good.
    $this->GiveToEngineers($specs);
    }
    public function GiveToEngineers(Specs $specs) {
    // E-mails specs to engineers.
    }
    public function DealWithTheGoshDarnCustomers() {
    // You better believe he does.
    }
    public function IsPeoplePerson() {
    return true; // Absolutely!
    }
    }
    Saturday, June 30, 12

    View Slide

  51. Saturday, June 30, 12

    View Slide

  52. class Tom implements IManageCustomers {
    public function TakeSpecifications(Specs $specs) {
    throw new DelegateToSecretary();
    }
    public function ReviewSpecifications(Specs $specs) {
    throw new DelegateToSecretary();
    }
    public function GiveToEngineers(Specs $specs) {
    throw new DelegateToSecretary();
    }
    public function DealWithTheGoshDarnCustomers() {
    // Your gosh darn right I do!
    }
    public function IsPeoplePerson() {
    return true; // I AM a people person, dammit!
    }
    }
    Saturday, June 30, 12

    View Slide

  53. Our consultants have a look at Tom
    Saturday, June 30, 12

    View Slide

  54. Saturday, June 30, 12

    View Slide

  55. interface ITakeSpecifications {
    function TakeSpecifications(Specs $specs);
    }
    interface IReviewSpecifications {
    function ReviewSpecifications(Specs $specs);
    }
    interface IGiveToEngineers {
    function GiveToEngineers(Specs $specs);
    }
    interface IManageCustomers {
    function DealWithTheGoshDarnCustomers();
    function IsPeoplePerson();
    }
    Saturday, June 30, 12

    View Slide

  56. class GoodManager implements
    IManageCustomers, ITakeSpecifications, IReviewSpecifications, IGiveToEngineers
    {
    public function TakeSpecifications(Specs $specs) {
    $this->ReviewSpecifications($specs);
    }
    public function ReviewSpecifications(Specs $specs) {
    // If specs seem good.
    $this->GiveToEngineers($specs);
    }
    public function GiveToEngineers(Specs $specs) {
    // E-mails specs to engineers.
    }
    public function DealWithTheGoshDarnCustomers() {
    // You better believe he does.
    }
    public function IsPeoplePerson() {
    return true; // Absolutely!
    }
    }
    Saturday, June 30, 12

    View Slide

  57. class Tom implements IManageCustomers {
    public function DealWithTheGoshDarnCustomers() {
    // Your gosh darn right I do!
    }
    public function IsPeoplePerson() {
    return true; // I AM a people person, dammit!
    }
    }
    Saturday, June 30, 12

    View Slide

  58. Next Up
    Saturday, June 30, 12

    View Slide

  59. Saturday, June 30, 12

    View Slide

  60. interface IDataResource
    {
    function Load();
    function Save();
    }
    Saturday, June 30, 12

    View Slide

  61. class AppSettings implements IDataResource {
    public function Load() {
    // Load application settings.
    }
    public function Save() {
    // Save application settings.
    }
    }
    Saturday, June 30, 12

    View Slide

  62. class UserSettings implements IDataResource {
    public function Load() {
    // Load user settings.
    }
    public function Save() {
    // Save user settings.
    }
    }
    Saturday, June 30, 12

    View Slide

  63. static function LoadAll() {
    $resources = array(
    new AppSettings(),
    new UserSettings()
    );
    foreach ($resources as $resource) {
    $resource->Load();
    }
    return $resources;
    }
    static function SaveAll(array $resources) {
    foreach ($resources as $resource) {
    $resource->Save();
    }
    }
    Saturday, June 30, 12

    View Slide

  64. Our “Duck”
    Saturday, June 30, 12

    View Slide

  65. class UnsaveableSettings implements AppSettings {
    public function Load() {
    // Loads settings.
    }
    public function Save() {
    throw new CannotSaveException();
    }
    }
    Saturday, June 30, 12

    View Slide

  66. static function LoadAll() {
    $resources = array(
    new AppSettings(),
    new UserSettings(),
    new UnsaveableSettings()
    );
    foreach ($resources as $resource) {
    $resource->Load();
    }
    return $resources;
    }
    static function SaveAll(array resources) {
    foreach ($resources as $resource) {
    $resource->Save();
    }
    }
    Saturday, June 30, 12

    View Slide

  67. static function LoadAll() {
    $resources = array(
    new AppSettings(),
    new UserSettings(),
    new UnsaveableSettings()
    );
    foreach ($resources as $resource) {
    $resource->Load();
    }
    return $resources;
    }
    static function SaveAll(array resources) {
    foreach ($resources as $resource) {
    $resource->Save();
    }
    }
    Saturday, June 30, 12

    View Slide

  68. static function LoadAll() {
    $resources = array(
    new AppSettings(),
    new UserSettings(),
    new UnsaveableSettings()
    );
    foreach ($resources as $resource) {
    $resource->Load();
    }
    return $resources;
    }
    static function SaveAll(array resources) {
    foreach ($resources as $resource) {
    $resource->Save();
    }
    }
    What happens with UnsaveableSettings?
    Saturday, June 30, 12

    View Slide

  69. Saturday, June 30, 12

    View Slide

  70. static function SaveAll(array $resources) {
    foreach ($resources as $resource) {
    if ($resource instanceof UnsaveableSettings) continue;
    $resource->Save();
    }
    }
    teh fix!
    Saturday, June 30, 12

    View Slide

  71. static function SaveAll(array $resources) {
    foreach ($resources as $resource) {
    if ($resource instanceof UnsaveableSettings) continue;
    $resource->Save();
    }
    }
    teh fix!
    Saturday, June 30, 12

    View Slide

  72. static function SaveAll(array $resources) {
    foreach ($resources as $resource) {
    if ($resource instanceof UnsaveableSettings) continue;
    $resource->Save();
    }
    }
    teh fix!
    omg shoot me
    good job reducing abstraction
    Saturday, June 30, 12

    View Slide

  73. The Real Fix
    Saturday, June 30, 12

    View Slide

  74. The Real Fix
    Interface Segregation +
    Polymorphism
    Saturday, June 30, 12

    View Slide

  75. interface IDataResource
    {
    function Load();
    }
    interface ISaveResource
    {
    function Save();
    }
    Saturday, June 30, 12

    View Slide

  76. public class AppSettingsLoaderBase implements IDataResource {
    public void Load() {
    // Load application settings.
    }
    }
    Saturday, June 30, 12

    View Slide

  77. public class AppSettings extends AppSettingsLoaderBase
    implements ISaveResource {
    public function Save() {
    // Save application settings.
    }
    }
    Saturday, June 30, 12

    View Slide

  78. public class UnsaveableSettings extends AppSettingsLoaderBase {
    public void Load() {
    // Loads settings.
    }
    }
    Saturday, June 30, 12

    View Slide

  79. static function LoadAll() {
    $resources = array(
    new AppSettings(),
    new UserSettings(),
    new UnsaveableSettings()
    );
    foreach ($resources as $resources) {
    $resource->Load();
    }
    return $resources;
    }
    static function SaveAll(SaveResourcesCollection $resources) {
    foreach ($resources as $resource) {
    $resource->Save();
    }
    }
    Saturday, June 30, 12

    View Slide

  80. Final Problem
    Saturday, June 30, 12

    View Slide

  81. class Authenticator {
    /** @var DataAccessLayer $_repository */
    private $_repository;
    public __construct() {
    $this->_repository = new DataAccessLayer();
    }
    public function authenticate($username, $password) {
    $hashedPassword = md5($password);
    $user = $this->_repository->findByUsernameAndPassword(
    $username, $hashedPassword);
    return $user == null;
    }
    }
    Saturday, June 30, 12

    View Slide

  82. Problems?
    Saturday, June 30, 12

    View Slide

  83. Problems?
    authenticate() : bool
    Authenticator
    findByUsernameAndPassword : array
    DataAccessLayer
    Saturday, June 30, 12

    View Slide

  84. Problems?
    authenticate() : bool
    Authenticator
    findByUsernameAndPassword : array
    DataAccessLayer
    Strongly coupled to DataAccessLayer
    Saturday, June 30, 12

    View Slide

  85. Saturday, June 30, 12

    View Slide

  86. Dependency Inversion
    • “High-level modules should not depend upon low level modules. They
    should depend upon abstractions.
    • “Abstractions should not depend upon details. Details should depend
    upon abstractions.”
    Robert Martin
    Saturday, June 30, 12

    View Slide

  87. Step 1
    Invert Dependency
    Saturday, June 30, 12

    View Slide

  88. class Authenticator {
    private $_repository;
    public __construct(DataAccessLayer $repository) {
    $this->_repository = $repository;
    }
    public function authenticate($username, $password) {
    $hashedPassword = md5($password);
    $user = $this->_repository->findByUsernameAndPassword(
    $username, $hashedPassword);
    return $user == null;
    }
    }
    Saturday, June 30, 12

    View Slide

  89. Coupling = Bad
    Saturday, June 30, 12

    View Slide

  90. Step 2
    Depend on Abstractions
    Saturday, June 30, 12

    View Slide

  91. interface IUserRepository {
    function findByUsernameAndPassword($username, $password);
    }
    class DataAccessLayer implements IUserRepository {
    public function findByUsernameAndPassword($username, $password) {
    // Do some database stuff.
    }
    }
    Saturday, June 30, 12

    View Slide

  92. class Authenticator {
    /** @var IUserRepository $_repository
    private $_repository;
    public __construct(IUserRepository $repository) {
    $this->_repository = $repository;
    }
    public function authenticate($username, $password) {
    $hashedPassword = md5($password);
    $user = $this->_repository->findByUsernameAndPassword(
    $username, $hashedPassword);
    return $user == null;
    }
    }
    Saturday, June 30, 12

    View Slide

  93. Comparison
    Saturday, June 30, 12

    View Slide

  94. Comparison
    authenticate() : bool
    Authenticator
    findByUsernameAndPassword : array
    DataAccessLayer
    Saturday, June 30, 12

    View Slide

  95. Comparison
    authenticate() : bool
    Authenticator
    findByUsernameAndPassword : array
    DataAccessLayer
    Saturday, June 30, 12

    View Slide

  96. Comparison
    authenticate() : bool
    Authenticator
    findByUsernameAndPassword : array
    DataAccessLayer
    authenticate() : bool
    Authenticator
    findByUsernameAndPassword : array
    IUserRepository
    findByUsernameAndPassword : array
    DataAccessLayer
    Saturday, June 30, 12

    View Slide

  97. Benefit: Flexibility
    class WebServiceUserRepository implements IUserRepository {
    public function findByUsernameAndPassword($username, $password) {
    // Fetch our user through JSON or SOAP
    }
    }
    public class OAuthRepository implements IUserRepository {
    public function findByUsernameAndPassword($username, $password) {
    // Connect to your favorite OAuth provider
    }
    }
    Saturday, June 30, 12

    View Slide

  98. Recap
    Saturday, June 30, 12

    View Slide

  99. Saturday, June 30, 12

    View Slide

  100. S = SRP - Single Responsibility Principle
    Saturday, June 30, 12

    View Slide

  101. S = SRP - Single Responsibility Principle
    O = OCP - Open/Closed Principle
    Saturday, June 30, 12

    View Slide

  102. S = SRP - Single Responsibility Principle
    O = OCP - Open/Closed Principle
    L = LSP - Liskov Substitution Principle
    Saturday, June 30, 12

    View Slide

  103. S = SRP - Single Responsibility Principle
    O = OCP - Open/Closed Principle
    L = LSP - Liskov Substitution Principle
    I = ISP - Interface Segregation Principle
    Saturday, June 30, 12

    View Slide

  104. S = SRP - Single Responsibility Principle
    O = OCP - Open/Closed Principle
    L = LSP - Liskov Substitution Principle
    I = ISP - Interface Segregation Principle
    D = DIP - Dependency Inversion Principle
    Saturday, June 30, 12

    View Slide

  105. ?
    Saturday, June 30, 12

    View Slide

  106. Is it law?
    Saturday, June 30, 12

    View Slide

  107. They be more like
    “guidelines”
    Saturday, June 30, 12

    View Slide

  108. Questions?
    Saturday, June 30, 12

    View Slide

  109. http://joind.in/talk/view/6345
    Thanks!
    Saturday, June 30, 12

    View Slide

  110. Dependency Injection
    Saturday, June 30, 12

    View Slide

  111. What is it?
    Saturday, June 30, 12

    View Slide

  112. Saturday, June 30, 12

    View Slide

  113. Involves a Service Container
    Involves a Service
    Container
    Saturday, June 30, 12

    View Slide

  114. sfServiceContainer Subclass
    class UserRepositoryContainer extends sfServiceContainer {
    protected function getUserRepositoryService() {
    $container = new DataAccessLayer($this['repository.config']);
    return $container;
    }
    }
    Saturday, June 30, 12

    View Slide

  115. Just Ask The Container for a
    Service
    Saturday, June 30, 12

    View Slide

  116. Consuming the Container
    class LoginController {
    public function login($username, $password) {
    $container = new UserRepositoryContainer();
    $repository = $container->userRepository;
    $authenticator = new Authenticator($repository);
    if ($authenticator->authenticate($username, $password)) {
    // Do something to log the user in.
    }
    }
    }
    Saturday, June 30, 12

    View Slide

  117. Registers Chains of
    Dependencies
    Saturday, June 30, 12

    View Slide

  118. Code-Based Description
    // Imagine this is a bootstrap file.
    $configuration = Zend_Registry::get('dbconfig');
    $builder = new sfServiceContainerBuilder();
    $builder->register('user_repository', 'DataAccessLayer')
    ->addArgument($configuration)
    // OR ->addArgument('%repository.config%') from earlier
    ->setShared(false);
    $builder->register('authenticator', 'Authenticator')
    ->addArgument(new sfServiceReference('user_repository'));
    Zend_Registry::set('di_container', $builder);
    Saturday, June 30, 12

    View Slide

  119. Config-Based Description



    mysql://localhost/database
    username
    password




    %user_repository.dsn%
    %user_repository.username%
    %user_repository.password%







    Saturday, June 30, 12

    View Slide

  120. Get the Object You Want
    Saturday, June 30, 12

    View Slide

  121. Using the Container
    class LoginController {
    public function login($username, $password) {
    $container = Zend_Registry::get('di_container');
    $authenticator = $container->authenticator;
    if ($authenticator->authenticate($username, $password)) {
    // Do something to log the user in.
    }
    }
    }
    Saturday, June 30, 12

    View Slide

  122. Deep Topic
    Saturday, June 30, 12

    View Slide