Thursday, January 15, 2015

Resolved - Apache Alias or Symlinks Not Working with Unexpected 403 Forbidden Error

Apache Forbidden Access Issues

Linking files to Apache on a new install of Cent OS 7 with Apache 2.4 was not quite as smooth as I imagined.

The Setup


I'm working a new server and I want to link to another location so I can use Dropbox to work on files locally and have them automatically updated to my development system.  Since it is easiest to install in the root directory, the Dropbox files automatically get setup in the root user's home directory.  That is not a good place to link the document root to in Apache.  The most logical thing to do was to copy those files out to another better location where they can be served by Apache.  These files were still owned by root and I didn't want to change that, so I creating a symlink to get it to work.

As root (note: my document root was /var/html)
cd /var/html
ln -s /usr/demo/html/ demo

When trying to run in Apache, I would still get the Forbidden error message.





NOTE: When Apache follows symlinks, the path must be accessible all the way down by the calling user (this means you need execute access in the folder you are linking and the parent folders above it).  To make sure this folder is accessible by others, I would use the following command:
chmod o+x /usr /usr/demo /usr/demo/html 

That didn't work for me, but it should work.  I just didn't realize the underlying problem I was experiencing which I will get to in a minute.  So now, I'm thinking I'll try to use an alias and edited and saved the new config file.  

Opening Apache config, I edited it as follows:

sudo nano /etc/httpd/conf/httpd.conf

Alias /demo /usr/demo/html

<Directory "/usr/demo/html">
    Options FollowSymLinks
    AllowOverride None
    Order allow,deny
    allow from all

</Directory>

All the online literature was pointing me in this direction.
Since I am using the new CentOS7 I need to restart the service using the system control program.
sudo systemctl restart httpd.service
#but, on most servers this is: 
#sudo /etc/init.d/httpd restart

The server restarted properly but I am still not able to access the page and still the Forbidden error pops up in my browser.  Looking in the /var/log/httpd/error_log was somewhat helpful:

[Thu Jan 15 14:37:07.549412 2015] [authz_core:error] [pid 30582] [client x.x.x.x:yyyy] AH01630: client denied by server configuration: /usr/demo/html/test.php


This was telling me that I didn't have a linux permission error accessing the file, but that I had an Apache configuration file error.  Back into the httpd.conf file.


The Solution


After a little digging, I found that Apache 2.4 (that I had on the new server) handles permissions differently that the previous version 2.2 that I was used to using.

Finally modifying my httpd.conf file resulted in:

Alias /demo /usr/demo/html

<Directory "/usr/demo/html">
    Options FollowSymLinks
    AllowOverride None
    Require all granted

</Directory>

Now everything works.  I just hadn't been aware that setting aliases in new Apache 2.4 installtion requires a couple changes in the httpd.conf file to get things working properly.  All this time it was just I was using: 
Order allow,deny
allow from all

when I should have been using:
Require all granted

Hopefully this helps someone else save some time.

Sunday, January 4, 2015

Don't Look Bad - Test Your Website

Don't be caught looking like a fool when your website fails unexpectedly.  Find out errors ahead of time using automatic testing techniques.  In this article we discuss automatic regression testing.  Use PHP to setup your own continuous integration server.

When you are responsible for a updating a website that is in active use,  you want to feel confident that the changes you make to the code do not break any currently working pages.  Nothing is worse that making a small change to fix one problem and find out later through a customer that the unintended consequence causes another page to stop working.  Prevent yourself from experiencing that embarrassment and setup automated webpage checking.  It's not too difficult and you will thank yourself later when you identify errors before pushing changes made on your development server to the production server.

Types of Testing

  • Unit Testing - Using a tool like PHPUnit, tests the individual components of your programs and be run each time code changes are made.  
  • Regression Testing - The subject of this article are tests help verify that the webpage works as a whole.  It is designed to supplement unit testing and provide early notification when any page or web service breaks.  Scheduling this test to run repeatedly is the next best thing to having your own continuous integration service, but is much easier to setup.
  • Integration/UI Testing - Using a UI simulator like Selenium tests the end-user's experience on the website.  While useful, this is time consuming to setup and is easily defeated when site layout is changed.  This testing is beyond the scope of this article. 

 

First Things First, the Overall Process


  1. Separate development servers from production.  It is recommended to have a separate development server and production server.  Development servers can be low power and low memory machines which are not only inexpensive to maintain, but have the added benefit of exposing any performance issues early.  
  2. Perform unit testing on the development server each time you make code changes.
  3. Perform automatic regression tests from a different computer to the one you are checking.  If the computer you are testing goes down, you want to be notified.  If your testing is done on the same computer, then if that computer fails, you run the risk of not being notified when there is a serious problem.  Run an automated task on the production server that checks the development server.  
  4. Test in both directions.  Run an automated task on the development server that checks the production server.  
  5. Confidently push code changes to the production server after testing successfully passes.

 

Writing a Regression Test - Get what you expect, not what you don't 


Get the first page you want to check and identify three things, the URL of the page, the content that is expected to be on the page (ie. page title), and content that should never be on the page (error messages, etc).  The test will verify that content you expect to be there is present and that content you don't want is not present.  This could be any page you want and I am using checkliststogo.com's homepage (a site I develop and maintain).  The error messages I use are geared towards a Linux, PHP, and Apache server, but the same basics apply to other systems.  Make sure error reporting is on in your programming language (for PHP this can be done by setting error_reporting = E_ALL in /etc/php.ini).
URL - "www.checkliststogo.com"
Expected content - "Checklists ToGo", "Popular Checklists", " WorxForUs &copy;"
Error content -"Parse error: syntax error", "{local filesystem root path to your site}", in my case: "/home/ec2-user/www/htdocs/"

Using the local filesystem page path in the check for error content combined with the web server language displaying errors is an extremely powerful and easy way to check for site errors.  Syntax errors, database errors, run-time errors, and all kinds of problems are all easily detected since errors report a filesystem trace including the root path when errors occur.  NOTE: The relative path below the filesystem root should not be used in the error detection since those strings will be found in page links.

Running the Regression Test - Main Code


Now that we know the site and what strings to check for we can build the program to run the actual test in a file called sample_index_test.php.

<?php
    include_once("validate_site_helper.php");

    $url = "http://www.checkliststogo.com";
    //These strings must not be in the page content to pass testing
    $err_arr = array();
    $err_arr[] = "Parse error: syntax error";
    $err_arr[] = "/home/ec2-user/www/htdocs/"; //This is probably the best detector in this group

    //These strings are required to be in the page content to pass testing
    $pass_arr = array();
    $pass_arr[] = "Checklists ToGo"; //check page title is on page
    $pass_arr[] = "Popular Checklists"; //check sample header
    $pass_arr[] = "WorxForUs &copy;"; //check copyright

    //Check the site
    $result = validate_site_helper::check_site($url, $err_arr, $pass_arr, basename(__FILE__));

    //Report the test result
    if (!$result->success) {
        $message = "Page {$url} testing failed - {$result->error}";
        handle_error_notification($message);
    } else {
        //(optional) let developer know the site was ok
        echo ("Site {$url} is ok");
    }

    //This is your custom module to send the display to the administrator or developer
    function handle_error_notification($message) {
        //Notify admin of failure - email, print to screen, etc.
        //Please see other blog entries on sending emails which are a great notifier
        echo ("ERROR: {$message}");
    }
?>


The validate site helper encapsulates all this checking and returns a result object that lets you know how the testing went.

The error notification is going to be different for each system and is beyond the scope of this article.  In my case, I like to use Amazon Simple Email Service (tutorial here) to send emails to myself when errors are detected and find that works very well.  


Validate Site Helper - Code


The validate_site_helper does all the hard work of getting the URL page content, parsing the text for the expected and error strings and then returns the result.

<?php

class validation_result {
        public $success = true;
        public $error = ""; //for passing errors
        public $subject = ""; //for providing a quick summary to email
}

/**
 *  validate_site_helper - this is a tool to capture and parse a specific web site page
 * @author sbossen
 */
class validate_site_helper {

        protected static function check_site_helper($site_content, $host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file) {
                $result = new validation_result();
                //using try here so any parse errors will be caught by this script
                try {
                        $ctg_content = $site_content;
                        //check for the errors
                        foreach ($err_indicators_arr as $err_str) {
                                if (stristr($ctg_content, $err_str)) {
                                        $result->success = false;
                                        $result->error .= "Suspected error indication: '$err_str' found in generated page content.\r\n";
                                }
                        }
                        //check for the required items
                        foreach ($pass_indicators_arr as $pass_str) {
                                if (!stristr($ctg_content, $pass_str)) {
                                        $result->success = false;
                                        $result->error .= "Validation indication: '$pass_str' was not found in generated page content.\r\n";
                                }
                        }

                        if (!$result->success) {
                                $result->subject = "$host_url - Warning - $calling_file";
                        }
                } catch (Exception $e) {
                        $result->success = false;
                        //email user
                        $body = $e->getMessage()."\r\n".$e->getTraceAsString();
                        $result->error .= $body;
                        $result->subject = "$host_url - Execution Error - $calling_file";
                }
                return $result;
        }

        public static function check_site($host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file) {
                $result = new validation_result();
                //using try here so any network errors will be caught by this script
                try {
                        $ctg_content = file_get_contents($host_url);
                        $result = validate_site_helper::check_site_helper($ctg_content, $host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file);
                } catch (Exception $e) {
                        $result->success = false;
                        //email user
                        $body = $e->getMessage()."\r\n".$e->getTraceAsString();
                        $result->error .= $body;
                        $result->subject = "$host_url - Execution Error - $calling_file";
                }
                return $result;
        }

        public static function check_site_with_post($host_url, $post_params_array, $err_indicators_arr, $pass_indicators_arr, $calling_file) {
                $result = new validation_result();
                //using try here so any errors will be caught by this script and emailed
                try {
                        // use key 'http' even if you send the request to https://...
                        $options = array(
                                'http' => array(
                                        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
                                        'method'  => 'POST',
                                        'content' => http_build_query($post_params_array),
                                ),
                        );
                        $context  = stream_context_create($options);
                        $ctg_content = file_get_contents($host_url, false, $context);

                        $result = validate_site_helper::check_site_helper($ctg_content, $host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file);
                } catch (Exception $e) {
                        $result->success = false;
                        //email user
                        $body = $e->getMessage()."\r\n".$e->getTraceAsString();
                        $result->error .= $body;
                        $result->subject = "$host_url - Execution Error - $calling_file";
                }
                return $result;
        }

}

?>

Validation Results

 

This works by using PHP's built in file_get_contents function which grabs the contents of the URL from the server.  That is handled beneath a try function that captures errors such as the page not being found and allows the script to continue and report the error back to the user.  Otherwise, if the page could not be retrieved the notification code would not execute which would be a big problem.


The returned validation_result object is just a holder to pass along the results of the validation.  When you get the results, you'll want to pass them on somewhere to let the developer know that an error has occurred.  In the sample code here we are just outputting to the screen.  

This code was tested under multiple failure scenarios, including:
  • Server is offline (IP address could not be resolved)
  • Page is not authorized
  • Page does not exist
  • Page is blank

    ERROR: Page http://
    www.checkliststogo.com/ctg/app testing failed - Validation indication: 'Checklists ToGo' was not found in generated page content. Validation indication: 'Popular Checklists' was not found in generated page content. Validation indication: 'WorxForUs ©' was not found in generated page content.
  • Page is OK

    Site http://www.checkliststogo.com/ctg/app is ok

Automating the Testing


When the validation code is ready, you'll want to continually run it.  An easy way to do this in Linux is using the cron tool or Task Scheduler for Windows.

For me, I just run this test every hour on the 14th minute:
    sudo crontab -e
    14 * * * * php {path to file}/sample_index_test.php
To finalize the change and write the updated task to the system
    :w

Of course, you will need to have added the email notification (or other system) since cron will only output to the console and you will not see it directly.

If you find this code useful, please let me know in the comments, give a +1, or send a smiley cat picture.

Tuesday, December 30, 2014

Pass a Verified edX.org Entrepreneur Course, Get $1000 AWS Credit

Take a $50 class and get $1000 AWS credit



Right now, and for a limited time (though I'm not sure how limited) you can take the edX course Entrepreneurship 101 or Entrepreneurship 102 by MITx and you can get $1000 in Amazon Web Services credit.  

My Thoughts


The course costs only $50, so stop being a cheap ass and sign up.  If you are already using AWS, this is a no-brainer.

Take advantage of this offer from edX.

 

 

 

 

Test that New Startup Idea

I'm already a fan of AWS, so this is an easy decision for me.  If you are not already a customer of AWS, then for a $50 investment and some time in the class, you can use that $1000 to run a decent Linux M3 server for about 18 months.  That's a decent amount of time to get a demo up and running for a new startup.

The classes are by MITx, so expect this class to be of high quality and be ready to put some time in it to pass the class.  I've taken other classes on edX (CS169.1 & 2 - the Berkeley Software as a Service classes) and found it to be an extremely good learning experience and highly recommend it.

Monday, December 15, 2014

Game Design Bundle - Hours of Training for only $8

Get Hours of Training in Unity and 3D Modeling for an Extremely Low Price


This is such a good deal I figured others may be interested in it.  Over at Slashdot Deals there is a 'name your own price' bundle for some pretty extensive game development courses.  The courses are hosted by Udemy.  If you want to learn about Unity or want to improve your artistic ability this may be the bundle for you - or a good gift for that developer you know.  Make sure to pay the average price (or above) to get the best deal by getting all four courses.  It's not really worth it unless you pay the extra to unlock all the courses.

https://deals.slashdot.org/sales/the-name-your-own-price-game-design-bundle

Get the Game Design Bundle on Slashdot Deals.

Why to the Courses


Professional Video Game Art School - This is the real meat of what I am interested in and if this is the only course I use in the group I will have easily gotten my money's worth.  This is a course on getting 3D fundamentals with 3DS Max.   These skills will be useful, not only for gaming, but to build 3D modeling skills which can be applied to other areas such as 3D printing.  Getting started with anything 3D is a real challenge.  For the eight dollars I paid for this package, this price is a steal and it is highly rated by people who have taken the course.  I'm in.

Unity3D Course - I've taken a lot of Unity tutorials, and most seem to cover the same old ground.  I'm not particularly excited about this course and the reviews haven't been good, but sometimes you can pick up new things that another instructor missed.  I'll probably spend some time going through the second half of this course.

Gamification Course - This class could be interesting and is geared towards taking an existing business (non-game) and making it more fun for the the users by adding game elements.  Not my primary focus, but could be useful on some other projects I am working on.

Photoshop Hand Painted Textures Course - I've always wondered how to be able to make great looking textures to apply to gaming models so I'm looking forward to taking this short class.


Tuesday, December 9, 2014

Get Licensed - Tips for preparing for the PE Exam in Computer Engineering

What is this NCEES exam and why would I want to take it?


The NCEES is a governing body that is allowed to license people and companies to practice engineering.  Any company with "Engineer" in the title needs to have a professional engineer (PE) or they may get sued by the professional engineering society for the state it is in.  The Principal and Practice exam is one requirement  prior to becoming licensed as an engineer.  This is an eight hour test that once you pass it, you are on your way to be licensed to officially perform the practice of engineering and run you own engineering company.  

People with a PE license are highly regarded in their field of expertise as it is no small feat to getting licensed.


My Tips


This applies to the Electrical and Computer : Computer Engineering exam, but the same methods and most books should work for the Software Exam.  Although the book list will need to be adjusted somewhat.

I spent many long hours studying for both this FE exam and then the PE exam, and now I have an engineering license to practice software engineering.  I wish I had some help along the way and someone to clue me in to what I needed to do.  Maybe my study methods can help you prepare for this exam.

To prepare for the PE test in October, I started studying five months in advance.  So, to take the test in October, I began studying in May a couple hours a night for most nights.  I figure I put about 300 hours of studying to get ready for this test.  Your needs may be different than mine as I wanted to make sure I did not have to take this eight hour test again.  Really, who wants to take this exam again.


Tips for passing the PE Exam


  1. Start studying five to six months in advance.
  2. Try for at least 300 hours of studying.
  3. Get to know your reference materials well.  Get everything you need early.  You will need to use them under stress and finding what you need quickly is key.  
  4. Use a calculator that you are allowed to take to the test.  Learn it well.  I used the Casio Fx-115ES.
  5. Spend the time to mark the Table of Contents, and where the Index is in all your books with sticky tabs.  You will be thankful later when you can just grab a book and start looking through the index quickly instead of fumbling for it. 
  6. Take all the sample tests you can until you feel comfortable with the material.



Here you can see the yellow tabs.  It is a good idea to mark the table of contents,
the start and end of the index for quick access.

The Books (Computer Engineering version)

To get the booklist, I started with the exam specifications from NCEES and made sure the books I selected covered most of those topics.  If you are taking the software only exam, you will not find as much use for the Electronics Engineers' Handbook.
  1. The Computer Science and Engineering Handbook, Tucker - This book was the best single resource for the test, it is a hefty beast, though.
  2. Electronics Engineers' Handbook, Christiansen - This book also was a great resource for the test.  It's big, but covers a lot of ground.
  3. Operating Systems: Internals and Design Principle, William Stallings  
  4. The Camara Books - Some people don't like his books.  I didn't either at first, but it turned out they were actually useful for me - especially the sample problems:
    1. Electrical Engineering Practice Problems for the Power, Electrical/Electronics, and Computer PE Exams, Camara
    2. Electrical Engineering Quick Reference for the Power, Electrical and Electronics, and Computer PE Exams - This one is expensive and not as good as the Electronic or Computer Science Engineering Handbook.
    3. Electrical Engineering Sample Examinations for the Power, Electrical and Electronics, and Computer PE Exams - The sample problems are a must.  You should be able to solve these using just your reference materials.
  5. PE Electrical and Computer: Computer Engineering Practice Exam - The sample test from NCEES.
  6. Guide to the Software Engineering Body of Knowledge (SWEBOK) - Absolutely necessary.  I used the 2004 version and this book was quite handy for the project management details.  A newer version is available.
  7. Schaum's Outlines - I got these books but didn't really need them for the test. Your mileage may vary.  If you need to target specific holes in your knowledge, they may be a good resource.
    1. Schaum's Outline of Digital Principles
    2. Schaum's Outline of Computer Architecture
    3. Schaum's Outline of Software Engineering
    4. Schaum's Outline of Operating Systems
  8. Microelectronic Circuit Design, Jaeger
  9. Introduction to Computer Engineering: Hardware and Software Design, Booth
  10. Microelectronics (Mcgraw Hill Series in Electrical and Computer Engineering), Jacob Millman


Good luck!

If this list does help you, please consider clicking on +1, post a LOL cat photo, or whatever.

Friday, November 28, 2014

Migrating from PHP 5.3 to PHP 5.4 or 5.5 - Watch Out For a Dangerous iconv Bug

If you are using iconv to filter out invalid characters for strings and you migrate to PHP 5.5, you may experience the nasty bug that bit me.


Currently I am converting all my web data from a nice UTF-8 format to ISO-8859-1 (otherwise known as ISO Latin-1) for use for inserting into PDF reports using the fantastic FPDF library.

The code looks something like this:
$clean = @iconv("UTF-8", "ISO-8859-1//IGNORE//TRANSLIT", $text);

I used the error suppression here so no errors get output to the screen when an invalid character needs to be stripped.  If the error displays on the screen, then it interrupts the creation of the PDF file and the user does not get a file.  Not so great, right?

Here is the error that is returned when the error is not suppressed:
Notice: iconv(): Detected an illegal character in input string in {...}

I had been using the //IGNORE directive to direct the function to ignore characters that have errors in them.   I also use //TRANSLIT so that if a character doesn't match exactly to the specific character set, the closest approximation is used.



$text = "Equipment List – Projéct [2014-Nov-28]";
$clean = @iconv("UTF-8", "ISO-8859-1//IGNORE//TRANSLIT", $text);
print_ln($clean);



It may be hard to see, but the first dash '
–' is an em dash and is actually a different character and longer than the '-' en dash (or minus symbol).  Also, I placed a 'e' on project just for good measure.  That doesn't have a representation in the ISO-8859-1 character set according to iconv.

In my PHP version 5.3.29 (with iconv library version 2.17) I get the output:
Equipment List - Proj�ct [2014-Nov-28]

However in PHP version 5.5.19 (also with iconv library version 2.17) with the same code, I get no output at all.


Temporary Solution


So how to correct for that behavior?  Well, I'm not quite sure what's going on in the code for iconv, but I found that if I remove the //IGNORE directive and just leave //TRANSLIT then I am ok.

$text = "Equipment List – Projéct [2014-Nov-28]";
$clean = @iconv("UTF-8", "ISO-8859-1//TRANSLIT", $text);
print_ln($clean);
Output:
Equipment List - Proj�ct [2014-Nov-28]

That's what I wanted, so we should be good for now.  At least until I can examine the iconv source code from GNU Libc and see what is going on.


Update:

After reviewing the source code to the PHP implementation of iconv (from PHP 5.3 iconv to PHP 5.4 iconv) I think the culprit lies in how PHP is calling the iconv library, not the iconv library itself.  There is a line that calls the PHP return value after a check for errors and handles it differently in the later version (line 2390 of PHP5.4+.iconv.c).
    if (err == PHP_ICONV_ERR_SUCCESS && out_buffer != NULL) {
        RETVAL_STRINGL(out_buffer, out_len, 0);
    } else {
        if (out_buffer != NULL) {
            efree(out_buffer);
        }
        RETURN_FALSE;
    }
In the original 5.3 version it just returned what was found (line 2330 of PHP5.3.iconv.c)

    if (out_buffer != NULL) {
        RETVAL_STRINGL(out_buffer, out_len, 0);
    } else {
        RETURN_FALSE;
    }

It looks like that extra check (bolded above) is causing any failure to return FALSE which will give you an empty string ''.

Tuesday, November 18, 2014

What are static bindings in PHP - Using self:: versus static::

[1] Why an abstract horse?  See below.

What are static bindings?

Static bindings are functions or variables that can be called on a class that don't need an object created to use them first.  For example, you don't need to use the new operator to call a static method or access a static variable.  These methods exist the first time the class is loaded by the php process and are accessed using the '::' accessor.

How and when to use late static binding in PHP

Late static bindings should be used anytime you are likely to want to redefine or override static objects in children class.  In those cases you will use the static keyword which is a signal to php to check the child class for the appropriate overridden function or variable before it goes up the chain in checking the parent classes.

If you are sure the static function in the base class isn't going to change, or you don't want it to change, then you should consider using the self keyword to access the item.

<?php

abstract class booger {
    abstract function b();
    public static function a() { echo("base class function<br />"); }
    public function c() { self::a();}
    public function d() { static::a();}
}

class goober extends booger{
    public function b() { echo("extended class function<br />"); }
    public static function a() { echo("late static binding<br />"); }
}

$goob = new goober();
$goob::a();
$goob->b();
$goob->c();
$goob->d(); //this is the line that actually uses the late static binding
goober::a();

?>


OUTPUT:

late static binding
extended class function
base class function
late static binding
late static binding



The first line $goob::a() is just a regular overridden function call and does not use the static keyword for the access.  Similarly, $goob->b() is regular method call to a function that was required to be created because of the abstract keyword.

$goob->c() makes use of the self keyword.  This means that the whichever class the keyword is found in is the one that is searched for the requested method.  That is why it shows the output from the base class function.
 

$goob->d() makes use of the static keyword which shows how although the same function a() is called, that since the static keyword was used php knows to check not the base class but the calling class first for the requested function.  That was the desired behavior we were looking for in this case.

Summary

Generally, in most cases static will be the correct keyword to use since the programmer often expects the class to prefer to use the functions in the children classes over those in the parent class.  There are exceptions in situations where self is more appropriate such as when you don't ever plan on calling a particular function from a child class and only the parent class.

Some Gotchas

Please note incorrect order or positioning of the classes in your code can affect the interpreter and can cause a Fatal error:
Class 'YourClass' not found 

This can happen when there are multiple levels of abstraction and the base classes are out of order in the source code.  

For example:
 
<?php

abstract class horse extends animal {
    public function get_breed() { return "Jersey"; }
}

class cart extends horse {
    public function get_breed() { return "Wood"; }
}
 
abstract class animal {
    public abstract function get_breed();
}

$cart = new cart();
print($cart->get_breed());
?>

this outputs:
Wood

 
However, if you put the cart before the abstract horse (literally):
 
<?php
//same code, just in a different order
class cart extends horse {
    public function get_breed() { return "Wood"; }
}

abstract class horse extends animal {
    public function get_breed() { return "Jersey"; }
}
 
abstract class animal {
    public abstract function get_breed();
}

$cart = new cart();
print($cart->get_breed());

?>

this throws an error:
Fatal error: Class 'horse' not found
So, when using multiple levels of abstraction, be careful of the positioning of the classes within the source code - and don't put the cart before the abstract horse.

[1] The abstract horse image was provided by pptbackgroundstemplates.