Why doesn't PHP catch a "Class not found" error? - php

In the following example, if the class does not exist, I want to catch the error and create a Null class instead.
But in spite of my try/catch statements, PHP simply tells me Class 'SmartFormasdfasdf' not found.
How can I get PHP to catch the 'class not found' error?
<?php
class SmartFormLogin extends SmartForm {
public function render() {
echo '<p>this is the login form</p>';
}
}
class SmartFormCodeWrapper extends SmartForm {
public function render() {
echo '<p>this is the code wrapper form</p>';
}
}
class SmartFormNull extends SmartForm {
public function render() {
echo '<p>the form "' . htmlentities($this->idCode) . '" does not exist</p>';
}
}
class SmartForm {
protected $idCode;
public function __construct($idCode) {
$this->idCode = $idCode;
}
public static function create($smartFormIdCode) {
$className = 'SmartForm' . $smartFormIdCode;
try {
return new $className($smartFormIdCode);
} catch (Exception $ex) {
return new SmartFormNull($smartformIdCode);
}
}
}
$formLogin = SmartForm::create('Login');
$formLogin->render();
$formLogin = SmartForm::create('CodeWrapper');
$formLogin->render();
$formLogin = SmartForm::create('asdfasdf');
$formLogin->render();
?>
Solution:
Thanks #Mchl, this is how I solved it then:
public static function create($smartFormIdCode) {
$className = 'SmartForm' . $smartFormIdCode;
if(class_exists($className)) {
return new $className($smartFormIdCode);
} else {
return new SmartFormNull($smartFormIdCode);
}
}

Because it's a fatal error. Use class_exists() function to check if class exist.
Also: PHP is not Java - unless you redefined default error handler, it will raise errors and not throw exceptions.

Old question, but in PHP7 this is a catchable exception. Though I still think the class_exists($class) is a more explicit way to do it. However, you could do a try/catch block using the new \Throwable exception type:
$className = 'SmartForm' . $smartFormIdCode;
try {
return new $className($smartFormIdCode);
} catch (\Throwable $ex) {
return new SmartFormNull($smartformIdCode);
}

php >= 7.0
php can catch 'class not found' as Throwable
try {
return new $className($smartFormIdCode);
} catch (\Throwable $ex) {
return new SmartFormNull($smartformIdCode);
}

You need to use class_exists to see if the class exists before you try and instantiate it.
Incidentally, if you're using a class autoloader, be sure to set the second arg to true.

Because php emits fatal error when you ty to create new object of non existing class. To make it work you will need php >= 5.3 and autoload function, where you should try to look for file with class definition or throw your custom exception.

Related

Uncaught exception when passing exception up through class

I have a class named User that calls one of its own methods, setUsername(), upon construction, within a try/catch block. If setUsername() fails, it will throw an exception:
class User {
private $username;
public function __construct($input_username) {
try {
$this->setUsername($input_username);
} catch(Exception $e) {
throw $e;
}
}
private function setUsername($username) {
if(1 != 0)
throw new Exception("1 does not equal 0!!!");
$this->username = $username;
}
}
I then create a new User in an external function, in a separate file, within its own try/catch block. It's supposed to catch the exception passed through from the User class constructor:
namespace UserController;
function createUser(){
try {
$user = new \User('sample-user');
} catch(Exception $e) {
echo $e->getMessage();
}
}
Why, then, am I still getting an "Uncaught Exception" error?
It seems I was missing a statement at the top of the file that instantiates the class, since it is namespaced. After the namespace declaration, it needs to say:
use \Exception;

Laravel is throwing exception event when I have try, catch

I am using laravel 5.4. I have a method in a class that gets a new instance of a model class. The class's full name is computed at run time, so there is a chance the computed class name does not exists. In the case the class does not exists, I want to ignore any exception and I want to return null instead.
But, when an exception takes place, Laravel still throw the exception below even thought I believe it should not
[Symfony\Component\Debug\Exception\FatalThrowableError]
Class 'App\Models\CreatedBy' not found
Note that the string App\Models\CreatedBy was computed at run-time.
Here is my code
private function getForeignModelInstance()
{
try {
if (!$this->foreignModel) {
$model = $this->getFullForeignModel();
if ($model) {
$this->foreignModel = new $model();
}
}
return $this->foreignModel;
} catch (\Exception $e) {
return null;
}
}
How can ignore any error thrown and settle by returning null?
I think the best way is to prevent exception happen instead of hiding it. So before make a new class instance, check if its exists:
private function getForeignModelInstance()
{
try {
if (!$this->foreignModel) {
$model = $this->getFullForeignModel();
if ($model && class_exists($model)) {
$this->foreignModel = new $model();
}
return null;
}
return $this->foreignModel;
} catch (\Exception $e) {
return null;
}
}
Note: class_exists will not work using the short, aliased class name.

Can one call an instance's method inside a class without recreating the instance within class' scope?

I'm relatively new to PHP, I'm trying to make a script to log errors from try/catch blocks. I've run into a scope problem trying to do so.
First, I attempted to make the class instance a global variable but that didn't work.
I know it is possible to make a new instance each time 'AnotherClass' is called; however, that would clear the '$errors' array in 'errorhandler'.
I've been stuck on this issue for a few hours and any help would be greatly appreciated!
<?php
class errorhandler
{
private $errors = [];
function log($e = '')
{
print "Opps! An error occured: " . $e;
array_push($this->errors, $e);
}
}
# global $errorhandler; # Doesn't work...
$errorhandler = new errorhandler();
class AnotherClass
{
function __construct()
{
try {
$not_possible = 1/0;
} catch (Exception $e) {
$errorhandler->log($e); # Doesn't work
}
}
}
new AnotherClass();
?>
Thanks :)
You have to import the global $errorhandler variable into your local scope:
class AnotherClass
{
function __construct()
{
global $errorhandler;
try {
$not_possible = 1/0;
} catch (Exception $e) {
$errorhandler->log($e); # Doesn't work
}
}
}
P.S. 1/0 is not an exception, it's a runtime error. You can't catch those with a try/catch block.

PHPUnit - Throwing, capturing and handling custom exceptions

I'm trying to test a capturing and handling a custom exception in PHP.
I've extended the base exception type with some extra properties and methods.
One of the classes I'm stubbing can throw an exception, I want to be able to test that I'm correctly capturing and handling that exception (which in this case means building a response object to return from the call).
e.g.
try {
$objectBeingStubbed->doSomething();
} catch (\Exception $ex) {
if ($ex instanceof CustomExceptionType) {
$this->_errorResponse->error->message = $exception->getMessage();
$this->_errorResponse->error->code = $exception->getCode();
$this->_errorResponse->error->data = $exception->getData();
} else {
throw $ex;
}
}
I'm attempted to simulate the exception being thrown with:
$objectStub->expects($this->any())
->method('doSomething')
->will($this->throwException(new CustomExceptionType()));
But when the exception arrives in the class I'm testing it's now an instance of "Mock_ErrorResponse_????" which doesn't extend my custom exception. My exception is instead contained in a "$exception" property on the Mock_ErrorResponse.
Is there any way of handling this without being forced to do something horrible like:
if ($ex instanceof PHPUnit_Framework_MockObject_Stub_Exception) {
$ex = $ex->exception;
}
if ($ex instanceof CustomExceptionType) {
...
Inside the class I'm testing?
First of all, instead:
} catch (\Exception $ex) {
if ($ex instanceof CustomExceptionType) {
you should use try/catch structure:
// (...)
} catch (CustomExceptionType $e) {
// (...)
} catch (\Exception $e) {
// (...)
}
So, answering your question, basically probably you're doing sth wrong. Because when the stubbed method throws an exception, it should throw exactly exception that you've set with throwException method.
I don't know how you build your stub (maybe there something is broken, maybe namespaces) but please consider an example below which works fine.
class Unit
{
public function foo()
{
throw new \InvalidArgumentException();
}
public function bar()
{
try {
$this->foo();
} catch (\InvalidArgumentException $e) {
return true;
} catch (\Exception $e) {
return false;
}
return false;
}
}
class UnitTest extends \PHPUnit_Framework_TestCase
{
public function testBar()
{
$sut = $this->getMock('Unit', array('foo'));
$sut->expects($this->any())
->method('foo')
->will($this->throwException(new \InvalidArgumentException()));
$this->assertTrue($sut->bar());
}
}
Of course you can replace InvalidArgumentException with your own implementation exception and this still should work. If you'll still have problems with figure out what is wrong with your code please post more complete example (eg. how you build your stub). Maybe then I can help more.
Nowadays you can use the #expectedException php-doc annotation built-in in PHPUnit: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.exceptions
/**
* #expectedException InvalidArgumentException
*/
public function testBar()
{
$sut = $this->getMock('Unit', array('foo'));
$sut->expects($this->any())
->method('foo')
->will($this->throwException(new \InvalidArgumentException()));
}

Throwing Exceptions in an SPL autoloader?

Is there a way to throw exceptions from an SPL Autoloader in PHP in case it fails? It doesn't seem to work under PHP 5.2.11.
class SPLAutoLoader{
public static function autoloadDomain($className) {
if(file_exists('test/'.$className.'.class.php')){
require_once('test/'.$className.'.class.php');
return true;
}
throw new Exception('File not found');
}
} //end class
//start
spl_autoload_register( array('SPLAutoLoader', 'autoloadDomain') );
try{
$domain = new foobarDomain();
}catch(Exception $c){
echo 'File not found';
}
When the above code is called, there is no sign of an exception, instead I get a standard "Fatal error: Class 'foobarDomain' not found in bla". And the execution of the script terminates.
This is not a bug, it's a design decision:
Note: Exceptions thrown in __autoload function cannot be caught in the catch block and results in a fatal error.
The reason is that there may be more than one autoload handlers, in which case, you don't want the first handler to throw an Exception and bypass the second handler. You want your second handler to have a chance at autoloading its classes. If you use a library which makes use of the autoloading feature, you don't want it bypassing your autoload handler because they throw Exceptions inside their autoloader.
If you want to check whether or not you can instantiate a class, then use class_exists and pass true as the second argument (or leave it out, true is the default):
if (class_exists('foobarDomain', $autoload = true)) {
$domain = new foobarDomain();
} else {
echo 'Class not found';
}
According to the comments in the documentation for spl_autoload_register, it's possible to call another function from the autoloader, which in turn would throw the exception.
class SPLAutoLoader{
public static function autoloadDomain($className) {
if(file_exists('test/'.$className.'.class.php')){
require_once('test/'.$className.'.class.php');
return true;
}
self::throwFileNotFoundException();
}
public static function throwFileNotFoundException()
{
throw new Exception('File not found');
}
} //end class
//start
spl_autoload_register( array('SPLAutoLoader', 'autoloadDomain') );
try{
$domain = new foobarDomain();
}catch(Exception $c){
echo 'File not found';
}
Here's a full-fledged factory object which demonstrates auto-loading, namespaces support, callables from non-static instances (with variable paths), handling of loading errors and custom exceptions.
abstract class AbstractFactory implements \ArrayAccess
{
protected $manifest;
function __construct($manifest)
{
$this->manifest = $manifest;
}
abstract function produce($name);
public function offsetExists($offset)
{
return isset($this->manifest[$offset]);
}
public function offsetGet($offset)
{
return $this->produce($offset);
}
//implement stubs for other ArrayAccess funcs
}
abstract class SimpleFactory extends AbstractFactory {
protected $description;
protected $path;
protected $namespace;
function __construct($manifest, $path, $namespace = "jj\\") {
parent::__construct($manifest);
$this->path = $path;
$this->namespace = $namespace;
if (! spl_autoload_register(array($this, 'autoload'), false)) //throws exceptions on its own, but we want a custom one
throw new \RuntimeException(get_class($this)." failed to register autoload.");
}
function __destruct()
{
spl_autoload_unregister(array($this, 'autoload'));
}
public function autoload($class_name) {
$file = str_replace($this->namespace, '', $class_name);
$filename = $this->path.$file.'.php';
if (file_exists($filename))
try {
require $filename; //TODO add global set_error_handler and try clause to catch parse errors
} catch (Exception $e) {} //autoload exceptions are not passed by design, nothing to do
}
function produce($name) {
if (isset($this->manifest[$name])) {
$class = $this->namespace.$this->manifest[$name];
if (class_exists($class, $autoload = true)) {
return new $class();
} else throw new \jj\SystemConfigurationException('Factory '.get_class($this)." was unable to produce a new class {$class}", 'SYSTEM_ERROR', $this);
//an example of a custom exception with a string code and data container
} else throw new LogicException("Unknown {$this->description} {$name}.");
}
function __toString() //description function if custom exception class wants a string explanation for its container
{
return $this->description." factory ".get_class($this)."(path={$this->path}, namespace={$this->namespace}, map: ".json_encode($this->manifest).")";
}
}
and finally an example:
namespace jj;
require_once('lib/AbstractFactory.php');
require_once('lib/CurrenciesProvider.php'); //base abstract class for all banking objects that are created
class CurrencyProviders extends SimpleFactory
{
function __construct()
{
$manifest = array(
'Germany' => 'GermanBankCurrencies',
'Switzerland' => 'SwissBankCurrencies'
);
parent::__construct($manifest, __DIR__.'/CurrencyProviders/', //you have total control over relative or absolute paths here
'banks\');
$this->description = 'currency provider country name';
}
}
now do
$currencies_cache = (new \jj\CurrencyProviders())['Germany'];
or
$currencies_cache = (new \jj\CurrencyProviders())['Ukraine'];
LogicException("Unknown currency provider country name Ukraine")
If there is no SwissCurrencies.php file in /CurrencyProviders/,
\jj\SystemConfigurationException('Factory jj\CurrencyProviders was unable to produce a new class banks\SwissCurrencies. Debug data: currency provider country name factory jj\CurrencyProviders(path=/var/www/hosted/site/.../CurrencyProviders/, namespace=banks\, map: {"Germany": "GermanBankCurrencies", "Switzerland":"SwissBankCurrencies"}')
With enough effort this factory can be extended to catch parse errors (How to catch error of require() or include() in PHP?) and pass arguments to constructors.

Categories