Hi!
I think I found a bug but before posing it to bugs.php.net I would like
to ask your opinion. I think the it is not a planed behavior that some
errors doesn't "bubble up" from autoload, but at least the error message
is misleading.
foo.php:
<?php
throw new Exception();
class foo {}
?>
bar.php:
<?php
class bar extends foo {}
?>
bootstrap.php
<?php
function __autoload($className)
{
include $className.'.php';
}
new bar();
?>
Result: "Fatal error: Class 'bar' not found".
The code above seems not to be too realistic but the next one is:
foo.php:
<?php
define('error', oops_i_left_the_quotes);
class foo {}
?>
bar.php:
<?php
class bar extends foo {}
?>
bootstrap.php
<?php
function __autoload($className)
{
include $className.'.php';
}
function error_handler()
{
throw new Exception();
}
set_error_handler("error_handler");
new bar();
?>
Result: "Fatal error: Class 'bar' not found".
And for example if define a class level constant without quotes I get
the exception generated by the Notice.
Best Regards,
Felhő
Hi Gergely:
foo.php:
<?php
throw new Exception();
class foo {}bar.php:
<?php
class bar extends foo {}<?php
function __autoload($className)
{
include $className.'.php';
}
function error_handler()
{
throw new Exception();
}
set_error_handler("error_handler");
new bar();Result: "Fatal error: Class 'bar' not found".
The fact that the class "bar" is not found indicates that your include in
the autoload is either totally failing or gathering some other files.
Make your life better by specifying the path in the include statement
rather than relying on the include_path.
Also, you would have known this on your own if you better utilized the
error handler:
function error_handler($errno, $errstr, $errfile, $errline) {
echo "survey says... $errstr <br />\n";
}
--Dan
--
T H E A N A L Y S I S A N D S O L U T I O N S C O M P A N Y
data intensive web and database programming
http://www.AnalysisAndSolutions.com/
4015 7th Ave #4, Brooklyn NY 11232 v: 718-854-0335 f: 718-854-0409
Hi Dan,
The fact that the class "bar" is not found indicates that your include in
the autoload is either totally failing or gathering some other files.
Make your life better by specifying the path in the include statement
rather than relying on the include_path.
You totally missed the point. Please try the code first. I didn't write
but I thought that it is obvious that the files are in the same directory.
Also, you would have known this on your own if you better utilized the
error handler:
function error_handler($errno, $errstr, $errfile, $errline) {
echo "survey says... $errstr <br />\n";
}
This is a SKELETON (use case) to present the error! Do you think
somebody write to the internal list with a dummy error?
Best Regards,
Felhő
This is a SKELETON (use case) to present the error! Do you think somebody
write to the internal list with a dummy error?
YOu'd be surprised sometimes ;-) Anyway, exceptions thrown during
autoload are ignored. Autoloading is the last resort for the engine to
find a class. If the class can not be loaded (file isn't there,
exception, autoloader doesn't load a file), then you get the fatal
error. This is correct behavior.
regards,
Derick
--
Derick Rethans
http://derickrethans.nl | http://ezcomponents.org | http://xdebug.org
YOu'd be surprised sometimes ;-) Anyway, exceptions thrown during
autoload are ignored. Autoloading is the last resort for the engine to
find a class. If the class can not be loaded (file isn't there,
exception, autoloader doesn't load a file), then you get the fatal
error. This is correct behavior.
Yes, I read that exception can not be catched from autoload, but one
thing was which confused me in this case:
<?php
define('error', oops_i_left_the_quotes);
class foo {
}
?>
In this case I got bar not found.
<?php
class foo {
const error = oops_i_left_the_quotes;
}
?>
In this case I got the exception.
So the difference is that class level constants are creating after the
autoload?
Best Regards,
Felhő
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the
catch block and results in a fatal error."
I think your explanation makes much more clear what happens, maybe it
would worth to upgrade the manual. While the quoted text suggests that
that if throw an exception I just can't catch it and will bubble up to
top level and this cause the fatal error.
Best Regards,
Felhő
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the catch
block and results in a fatal error."I think your explanation makes much more clear what happens, maybe it would
worth to upgrade the manual. While the quoted text suggests that that if throw
an exception I just can't catch it and will bubble up to top level and this
cause the fatal error.
You can actually catch it in the autoload method, it just wouldn't
bubble out of it.
regards,
Derick
--
Derick Rethans
http://derickrethans.nl | http://ezcomponents.org | http://xdebug.org
Derick Rethans schreef:
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the catch
block and results in a fatal error."I think your explanation makes much more clear what happens, maybe it would
worth to upgrade the manual. While the quoted text suggests that that if throw
an exception I just can't catch it and will bubble up to top level and this
cause the fatal error.You can actually catch it in the autoload method, it just wouldn't
bubble out of it.
the manual could do with that tidbit, maybe also the hack for 'getting the
exception out' of __autoload() ...
function __autoload($class)
{
try {
throw new Exception('foo');
} catch (Exception $e) {
self::handleDebug($e);
if (!class_exists($class, false))
eval(sprintf('
class %1$s
{
public function __construct() { throw new AL_Exception("Class %1$s not found: %2$s"); }
public function __call($m, $a) { throw new AL_Exception("Class %1$s not found: %2$s"); }
public static function __callStatic($m, $a) { throw new AL_Exception("Class %1$s not found: %2$s"); }
}', $class, $e->__toString()));
}
}
which works best when __autoload() isn't triggered by class_exists("Foo", true)
regards,
Derick
Hi Jochem,
not that you really want it, but you could pass the exception as a
static property to fully preserve and throw it later again. Something
like this:
...
sprintf('class %s {
public static $e;
public function __construct()
{
throw self::$e;
}
public function __call($method, array $args)
{
throw self::$e;
}
public function __callStatic($method, array $args)
{
throw self::$e;
}
}', $class);
$class::$e = $e;
...
Am Donnerstag, den 10.07.2008, 19:14 +0200 schrieb Jochem Maas:
[...]
function __autoload($class)
{
try {
throw new Exception('foo');
} catch (Exception $e) {
self::handleDebug($e);if (!class_exists($class, false)) eval(sprintf(' class %1$s { public function __construct() { throw new AL_Exception("Class %1$s not found: %2$s"); } public function __call($m, $a) { throw new AL_Exception("Class %1$s not found: %2$s"); } public static function __callStatic($m, $a) { throw new AL_Exception("Class %1$s not found: %2$s"); } }', $class, $e->__toString())); }
}
which works best when __autoload() isn't triggered by class_exists("Foo", true)
Hello Jochem,
seems like we have quite some nice additions to the manual here in this
thread. Now to the real issue, Exceptions don't bubble up. That is they
are simply ignored. And once all __autoload work is done and the class
still doesn't exist an E_ERROR
is issued. The work around for cases where
the class must not exist (e.g. when no E_ERROR
is needed would be
collecting the exceptions. That is the base Exception class would get an
additional property and getter:
private $previous_exception;
function public getPreviousException();
That way we could let the exceptions bubble up (though some smaller engine
changes are necessary).
marcus
Thursday, July 10, 2008, 7:14:33 PM, you wrote:
Derick Rethans schreef:
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the catch
block and results in a fatal error."I think your explanation makes much more clear what happens, maybe it would
worth to upgrade the manual. While the quoted text suggests that that if throw
an exception I just can't catch it and will bubble up to top level and this
cause the fatal error.You can actually catch it in the autoload method, it just wouldn't
bubble out of it.
the manual could do with that tidbit, maybe also the hack for 'getting the
exception out' of __autoload() ...
function __autoload($class) { try { throw new Exception('foo'); } catch (Exception $e) { self::handleDebug($e); if (!class_exists($class, false)) eval(sprintf(' class %1$s { public function __construct() {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public function __call($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public static function __callStatic($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
}', $class, $e->__toString()));
}
}
which works best when __autoload() isn't triggered by class_exists("Foo", true)
regards,
Derick
Best regards,
Marcus
Hi Marcus,
Marcus Boerger schreef:
Hello Jochem,
seems like we have quite some nice additions to the manual here in this
thread. Now to the real issue, Exceptions don't bubble up.
The thing is under quite a few circumstances the Exceptions do bubble up,
and there is another issue with __autoload() causing [other] errors not
to trigger the user defined error handler ... behaviour or php5.2.6 and php5.3
are the same in this regard.
I just posted another mail to internals in which I attempt to detail the
__autoload behaviour 'issues' ... it includes something that wants to be a
test suite when it grows up ... it might help someone to investage/determine
what __autoload() does/should do:
http://iamjochem.com/autoload_behaviour_tests.zip
That is they
are simply ignored.
and so I thought :-) which is why some of my test's output "wtf!" :-)
as always, thanks for your feedback,
regards,
Jochem
And once all __autoload work is done and the class
still doesn't exist anE_ERROR
is issued. The work around for cases where
the class must not exist (e.g. when noE_ERROR
is needed would be
collecting the exceptions. That is the base Exception class would get an
additional property and getter:
private $previous_exception;
function public getPreviousException();
That way we could let the exceptions bubble up (though some smaller engine
changes are necessary).
currently you can throw an exception out of __autoload() unless __autload()
is triggered by the 'new' syntax or by a static method call (e.g. MyClass::myMethod()),
with other 'triggers' (e.g. call_user_func()
) the exception comes through ...
in cases that it doesn't you can get an exception out of __autoload() via the
eval() trick [see below] ... I don't suppose there is anyway of asking the engine
which way __autoload() was triggered from inside the __autoload() code?
marcus
Thursday, July 10, 2008, 7:14:33 PM, you wrote:
Derick Rethans schreef:
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the catch
block and results in a fatal error."I think your explanation makes much more clear what happens, maybe it would
worth to upgrade the manual. While the quoted text suggests that that if throw
an exception I just can't catch it and will bubble up to top level and this
cause the fatal error.
You can actually catch it in the autoload method, it just wouldn't
bubble out of it.the manual could do with that tidbit, maybe also the hack for 'getting the
exception out' of __autoload() ...function __autoload($class) { try { throw new Exception('foo'); } catch (Exception $e) { self::handleDebug($e); if (!class_exists($class, false)) eval(sprintf(' class %1$s { public function __construct() {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public function __call($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public static function __callStatic($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
}', $class, $e->__toString()));
}
}which works best when __autoload() isn't triggered by class_exists("Foo", true)
regards,
DerickBest regards,
Marcus
Hello Jochem,
Thursday, July 17, 2008, 12:03:42 PM, you wrote:
Hi Marcus,
Marcus Boerger schreef:
Hello Jochem,
seems like we have quite some nice additions to the manual here in this
thread. Now to the real issue, Exceptions don't bubble up.
The thing is under quite a few circumstances the Exceptions do bubble up,
and there is another issue with __autoload() causing [other] errors not
to trigger the user defined error handler ... behaviour or php5.2.6 and php5.3
are the same in this regard.
In the other thread you identified rightly that when the class is not used
to instanciate an instance or for reflection purpose immediately, the
exceptions do bubble up.
I just posted another mail to internals in which I attempt to detail the
__autoload behaviour 'issues' ... it includes something that wants to be a
test suite when it grows up ... it might help someone to investage/determine
what __autoload() does/should do:
Great! More tests :-)
It is quite a lot of stuff and I'd like to see this turned into .phpt's.
marcus
That is they
are simply ignored.
and so I thought :-) which is why some of my test's output "wtf!" :-)
as always, thanks for your feedback,
regards,
Jochem
And once all __autoload work is done and the class
still doesn't exist anE_ERROR
is issued. The work around for cases where
the class must not exist (e.g. when noE_ERROR
is needed would be
collecting the exceptions. That is the base Exception class would get an
additional property and getter:
private $previous_exception;
function public getPreviousException();
That way we could let the exceptions bubble up (though some smaller engine
changes are necessary).
currently you can throw an exception out of __autoload() unless __autload()
is triggered by the 'new' syntax or by a static method call (e.g. MyClass::myMethod()),
with other 'triggers' (e.g.call_user_func()
) the exception comes through ...
in cases that it doesn't you can get an exception out of __autoload() via the
eval() trick [see below] ... I don't suppose there is anyway of asking the engine
which way __autoload() was triggered from inside the __autoload() code?
marcus
Thursday, July 10, 2008, 7:14:33 PM, you wrote:
Derick Rethans schreef:
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the catch
block and results in a fatal error."I think your explanation makes much more clear what happens, maybe it would
worth to upgrade the manual. While the quoted text suggests that that if throw
an exception I just can't catch it and will bubble up to top level and this
cause the fatal error.
You can actually catch it in the autoload method, it just wouldn't
bubble out of it.the manual could do with that tidbit, maybe also the hack for 'getting the
exception out' of __autoload() ...function __autoload($class) { try { throw new Exception('foo'); } catch (Exception $e) { self::handleDebug($e); if (!class_exists($class, false)) eval(sprintf(' class %1$s { public function __construct() {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public function __call($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public static function __callStatic($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
}', $class, $e->__toString()));
}
}which works best when __autoload() isn't triggered by class_exists("Foo", true)
regards,
DerickBest regards,
Marcus
Best regards,
Marcus
Marcus Boerger schreef:
Hello Jochem,
...
I just posted another mail to internals in which I attempt to detail the
__autoload behaviour 'issues' ... it includes something that wants to be a
test suite when it grows up ... it might help someone to investage/determine
what __autoload() does/should do:Great! More tests :-)
It is quite a lot of stuff and I'd like to see this turned into .phpt's.
it looks more than it is, the runtests.sh script makes viewing the results
of the variations in settings/code easier.
if you unzip the file and execute ./runtests.sh with no args it goves you
all the options ... at this stage I wrote primarily to figure out what
actually happens under any number of conditions.
I'd love to take a shot at converting them to .phpt I see 3 problems
- I don't know .phpt (I can solve this)
- I have no idea what to expect and therefore test for
(this needs to be determined/clarified!) - In the case that I need to test for an uncaught Exception I wonder
how to determine a pass (ie uncaugh exception thrown as expected) given
that the exact output is dependent on the full path of the relevant script
any advice as to how to proceed, especially with regard to point 2.
rgds,
Jochem
marcus
That is they
are simply ignored.and so I thought :-) which is why some of my test's output "wtf!" :-)
as always, thanks for your feedback,
regards,
JochemAnd once all __autoload work is done and the class
still doesn't exist anE_ERROR
is issued. The work around for cases where
the class must not exist (e.g. when noE_ERROR
is needed would be
collecting the exceptions. That is the base Exception class would get an
additional property and getter:
private $previous_exception;
function public getPreviousException();
That way we could let the exceptions bubble up (though some smaller engine
changes are necessary).currently you can throw an exception out of __autoload() unless __autload()
is triggered by the 'new' syntax or by a static method call (e.g. MyClass::myMethod()),
with other 'triggers' (e.g.call_user_func()
) the exception comes through ...
in cases that it doesn't you can get an exception out of __autoload() via the
eval() trick [see below] ... I don't suppose there is anyway of asking the engine
which way __autoload() was triggered from inside the __autoload() code?marcus
Thursday, July 10, 2008, 7:14:33 PM, you wrote:
Derick Rethans schreef:
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the catch
block and results in a fatal error."I think your explanation makes much more clear what happens, maybe it would
worth to upgrade the manual. While the quoted text suggests that that if throw
an exception I just can't catch it and will bubble up to top level and this
cause the fatal error.
You can actually catch it in the autoload method, it just wouldn't
bubble out of it.
the manual could do with that tidbit, maybe also the hack for 'getting the
exception out' of __autoload() ...
function __autoload($class)
{
try {
throw new Exception('foo');
} catch (Exception $e) {
self::handleDebug($e);if (!class_exists($class, false)) eval(sprintf(' class %1$s { public function __construct() {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public function __call($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public static function __callStatic($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
}', $class, $e->__toString()));
}
}
which works best when __autoload() isn't triggered by class_exists("Foo", true)regards,
DerickBest regards,
MarcusBest regards,
Marcus
- I don't know .phpt (I can solve this)
its quite easy. if you have questions either send them to php-qa@lists.php.net
or join us in #php.pecl on efnet or #phptestfest on freenode
respectively.
- I have no idea what to expect and therefore test for
(this needs to be determined/clarified!)
i guess for now all you can do is expect what you expect. this can
then be tweaked later on.
- In the case that I need to test for an uncaught Exception I wonder
how to determine a pass (ie uncaugh exception thrown as expected)
given
that the exact output is dependent on the full path of the
relevant script
phpt provides a fair amount of ways to make the expected result dynamic.
regards,
Lukas Kahwe Smith
mls@pooteeweet.org
Hello Jochem,
Thursday, July 17, 2008, 7:38:01 PM, you wrote:
Marcus Boerger schreef:
Hello Jochem,
...
I just posted another mail to internals in which I attempt to detail the
__autoload behaviour 'issues' ... it includes something that wants to be a
test suite when it grows up ... it might help someone to investage/determine
what __autoload() does/should do:Great! More tests :-)
It is quite a lot of stuff and I'd like to see this turned into .phpt's.
it looks more than it is, the runtests.sh script makes viewing the results
of the variations in settings/code easier.
if you unzip the file and execute ./runtests.sh with no args it goves you
all the options ... at this stage I wrote primarily to figure out what
actually happens under any number of conditions.
I'd love to take a shot at converting them to .phpt I see 3 problems
- I don't know .phpt (I can solve this)
http://qa.php.net/write-test.php
http://somabo.de/talks/200703_montreal_need_for_testing.pdf or .pps
- I have no idea what to expect and therefore test for
(this needs to be determined/clarified!)
Write the tests as good as you can and if you are unsure about the result
mail.
- In the case that I need to test for an uncaught Exception I wonder
how to determine a pass (ie uncaugh exception thrown as expected) given
that the exact output is dependent on the full path of the relevant script
Zend/tests/exception_008.phpt is a test for an uncaught exception that is
made dynamic enough to work everywhere. In general we use EXPECTF as soon
as error messages or exceptions are expected. Then we replace:
- path's including the last path separator with '%s'
- line numbers with '%d'
- object identifiers '#<num>' with '#%d'
marcus
any advice as to how to proceed, especially with regard to point 2.
rgds,
Jochem
marcus
That is they
are simply ignored.and so I thought :-) which is why some of my test's output "wtf!" :-)
as always, thanks for your feedback,
regards,
JochemAnd once all __autoload work is done and the class
still doesn't exist anE_ERROR
is issued. The work around for cases where
the class must not exist (e.g. when noE_ERROR
is needed would be
collecting the exceptions. That is the base Exception class would get an
additional property and getter:
private $previous_exception;
function public getPreviousException();
That way we could let the exceptions bubble up (though some smaller engine
changes are necessary).currently you can throw an exception out of __autoload() unless __autload()
is triggered by the 'new' syntax or by a static method call (e.g. MyClass::myMethod()),
with other 'triggers' (e.g.call_user_func()
) the exception comes through ...
in cases that it doesn't you can get an exception out of __autoload() via the
eval() trick [see below] ... I don't suppose there is anyway of asking the engine
which way __autoload() was triggered from inside the __autoload() code?marcus
Thursday, July 10, 2008, 7:14:33 PM, you wrote:
Derick Rethans schreef:
exceptions thrown during autoload are ignored.
And one more thing, this is in the manual:
"Note: Exceptions thrown in __autoload function cannot be caught in the catch
block and results in a fatal error."I think your explanation makes much more clear what happens, maybe it would
worth to upgrade the manual. While the quoted text suggests that that if throw
an exception I just can't catch it and will bubble up to top level and this
cause the fatal error.
You can actually catch it in the autoload method, it just wouldn't
bubble out of it.
the manual could do with that tidbit, maybe also the hack for 'getting the
exception out' of __autoload() ...
function __autoload($class)
{
try {
throw new Exception('foo');
} catch (Exception $e) {
self::handleDebug($e);if (!class_exists($class, false)) eval(sprintf(' class %1$s { public function __construct() {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public function __call($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
public static function __callStatic($m, $a) {
throw new AL_Exception("Class %1$s not found: %2$s"); }
}', $class, $e->__toString()));
}
}
which works best when __autoload() isn't triggered by class_exists("Foo", true)regards,
DerickBest regards,
MarcusBest regards,
Marcus
Best regards,
Marcus
Hi Gergely:
You totally missed the point. Please try the code first. I didn't write
but I thought that it is obvious that the files are in the same directory.
The error was clear: the file containing the class foo was not found. But
before I made my post, I did run your test, just to make sure. It worked
fine[1] -- because my include_path is set to look at the current
directory first.
Just because something is in the same directory doesn't mean it gets
included first. If the path is not specified (either by fully qualified
path (/var/www/test) or by relative path (./) the include_path is used.
The include path can specify that locations other than the current
directory are searched first. If you add the following in your
bootstrap.php I suspect you'll find that to be the case.
echo '<p>' . ini_get('include_path') . "</p>\n";
Good luck,
--Dan
[1] error_handler: Use of undefined constant oops_i_left_the_quotes -
assumed 'oops_i_left_the_quotes'
--
T H E A N A L Y S I S A N D S O L U T I O N S C O M P A N Y
data intensive web and database programming
http://www.AnalysisAndSolutions.com/
4015 7th Ave #4, Brooklyn NY 11232 v: 718-854-0335 f: 718-854-0409
Hi Dan,
The error was clear: the file containing the class foo was not found. But
before I made my post, I did run your test, just to make sure. It worked
fine[1] -- because my include_path is set to look at the current
directory first.
If you used my sample correctly you should get the same result and the
cause is already explained by Derick.
Just because something is in the same directory doesn't mean it gets
included first. If the path is not specified (either by fully qualified
path (/var/www/test) or by relative path (./) the include_path is used.
Sorry but this is not true, check the manual:
"Files for including are first looked for in each include_path entry
relative to the current working directory, and then in the directory of
current script."
Best Regards,
Felhő
p.s.: I run into this problem using amfphp which overwrites the error
handler with a function which simply convert the error to an excepton.
I have "Fatal error: Class 'Foo' not found" which is expected.
Thanks. Dmitry.
Gergely Hodicska wrote:
Hi!
I think I found a bug but before posing it to bugs.php.net I would like
to ask your opinion. I think the it is not a planed behavior that some
errors doesn't "bubble up" from autoload, but at least the error message
is misleading.
foo.php:
<?php
throw new Exception();
class foo {}
?>bar.php:
<?php
class bar extends foo {}
?>bootstrap.php
<?php
function __autoload($className)
{
include $className.'.php';
}
new bar();
?>Result: "Fatal error: Class 'bar' not found".
The code above seems not to be too realistic but the next one is:
foo.php:
<?php
define('error', oops_i_left_the_quotes);
class foo {}
?>bar.php:
<?php
class bar extends foo {}
?>bootstrap.php
<?php
function __autoload($className)
{
include $className.'.php';
}
function error_handler()
{
throw new Exception();
}
set_error_handler("error_handler");
new bar();
?>Result: "Fatal error: Class 'bar' not found".
And for example if define a class level constant without quotes I get
the exception generated by the Notice.Best Regards,
Felhő