PHP Version: 7.3.8
CakePHP Version: 4.0.1
Database Server: 10.4.6-MariaDB - mariadb.org binary distribution
Web server: Apache/2.4.39 (Win64) OpenSSL/1.1.1c
// WHAT I'M DOING
I created an error recording function to compliment the comprehensive error reporting functionality that comes out of the box with Cake. It's purpose in development is to assist me in reducing the amount of time it takes me to isolate an error when it occurs. And it's purpose when my app goes live is to do the same as in development but also assist me in monitoring any security vulnerabilities.
// WHAT'S HAPPENING:
Between 6 and 9 errors are being recorded in the database but the function that records the error is housed in the if part of the statement.
// USERS CONTROLLER
public function view(string $userID = null): void
{
// Initialise the variables.
$this->args = func_num_args();
$this->params = func_get_args();
if ($this->args !== 1) {
echo 'in FAILED ' . '<br /><br />';
// THIS IS THE FUNCTION THAT RECORDS THE ERROR.
$this->setErrorLocation(
$this->controller(). ' - Line ' . __LINE__);
if ($this->recordError(
$this->getErrorLocation()) === false) {
throw new UnauthorizedException();
}
throw new UnauthorizedException();
}
else {
// BUT THIS IS WHATS PRINTED TO THE SCREEN.
echo 'in PASSED ' . '<br /><br />';
}
// Rest of view code...
}
When the above code is executed the else part of the statement is executed and 'in PASSED' is printed to the screen. This is the expected behaviour.
And this is the unexpected behaviour:
Between 6 and 9 errors are recorded in the database?
'in FAILED' is NOT printed to the screen?
No exception is thrown?
======================================================================
// CODE TO REPLICATE
I've streamlined the code to the bare bones to hopefully enable replication of the behaviour.
// APP CONTROLLER
// The record error function:
The idea behind this function is best case scenerio I get a log in my database and Cake logs the error and throws an exception and worst case scenerio is Cake logs the error and throws an exception.
public function recordError(string $errorLocation): bool
{
// Initialise the variables.
$this->args = func_num_args();
$this->params = func_get_args();
// Check the number of arguments.
if ($this->args !== 1) {
return false;
}
// Check the argument type.
if (!is_string($this->params[0])) {
return false;
}
// Insert into the database.
$Errors = TableRegistry::getTableLocator()->get('Errors');
$error = $Errors->newEmptyEntity();
$error->ip = '::1';
$error->user_id = 1001; // NOTE: This must be a valid id so if $rules->existsIn is present in the user model the save does not fail.
$error->location = $this->params[0];
$error->date_occurred = '0000-00-00 00:00:00';
// Save the error.
if ($Errors->save($error)) {
return true;
}
return false;
}
// The error location setter and getter.
protected function setErrorLocation(string $errorLocation): object
{
$this->errorLocation = $errorLocation;
return $this;
}
protected function getErrorLocation(): string
{
return $this->errorLocation;
}
// USERS CONTROLLER
The controller name
private function controller(): string
{
$val = 'UsersController';
return $val;
}
// DATABASE
Table name: errors
Name Type Collation Attributes Null Default
id Primary int(10) No None AUTO_INCREMENT
ip varchar(100) utf8mb4_unicode_ci No None
user_id int(10) No None
location varchar(200) utf8mb4_unicode_ci No None
date_occured datetime No None
======================================================================
// RELEVANT INFO
The majority of the time this function works as expected but in the controllers view, edit and index methods multiple errors are being recorded.
I use this technique several times in view, edit and index and its only on a couple of instances does it not work as expected.
Also it's not just when I use PHP argument functions, it also records multiple errors in index when I use is_string.
I'm not sure if it's my environment, PHP, Cake or something I'm doing.
======================================================================
// MY QUESTION
Why are the errors recorded in the database?
(I mean how can the errors be recorded unyet no exception is thrown and in PASSED is printed to the screen?)
Thanks Zenzs.
======================================================================
#Jeto
I used the below backtrace function and it prints the following:
$e = new \Exception;
var_dump($e->getTraceAsString());
string(3679) "
0 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Controller\Controller.php(524): App\Controller\UsersController->view('1026')
1 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Controller\ControllerFactory.php(79): Cake\Controller\Controller->invokeAction(Object(Closure), Array)
2 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\BaseApplication.php(229): Cake\Controller\ControllerFactory->invoke(Object(App\Controller\UsersController))
3 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(77): Cake\Http\BaseApplication->handle(Object(Cake\Http\ServerRequest))
4 C:\xampp\htdocs\crm\vendor\cakephp\authentication\src\Middleware\AuthenticationMiddleware.php(122): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
5 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(73): Authentication\Middleware\AuthenticationMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
6 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\I18n\Middleware\LocaleSelectorMiddleware.php(70): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
7 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(73): Cake\I18n\Middleware\LocaleSelectorMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
8 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(77): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
9 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Middleware\CsrfProtectionMiddleware.php(132): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
10 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(73): Cake\Http\Middleware\CsrfProtectionMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
11 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(58): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
12 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Routing\Middleware\RoutingMiddleware.php(162): Cake\Http\Runner->run(Object(Cake\Http\MiddlewareQueue), Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
13 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(73): Cake\Routing\Middleware\RoutingMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
14 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Routing\Middleware\AssetMiddleware.php(68): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
15 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(73): Cake\Routing\Middleware\AssetMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
16 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Error\Middleware\ErrorHandlerMiddleware.php(118): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
17 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(73): Cake\Error\Middleware\ErrorHandlerMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
18 C:\xampp\htdocs\crm\vendor\cakephp\debug_kit\src\Middleware\DebugKitMiddleware.php(60): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
19 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(73): DebugKit\Middleware\DebugKitMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
20 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php(58): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest))
21 C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Server.php(90): Cake\Http\Runner->run(Object(Cake\Http\MiddlewareQueue), Object(Cake\Http\ServerRequest), Object(App\Application))
22 C:\xampp\htdocs\crm\webroot\index.php(40): Cake\Http\Server->run()
23 {main}"
XDebug is an excellent suggestion but as yet I have not installed it but it's on my list.
And this comment helps me understand a little more:
It could easily be that some of the calls fall in the error case and one of them passes.
Thanks.
======================================================================
#ndm
I'm using Chrome and it is making multiple requests but only to the ones in the head section of my app. I can't see any requests for any other than the ones it should be. IE: Requests to the following because all the following are stored locally:
<script src="bootstrap-sass/javascripts/jquery.min.js"></script>
<script src="bootstrap-sass/javascripts/bootstrap.min.js"></script>
<script src="bootstrap-sass/javascripts/crm-sys.js"></script>
<link rel="stylesheet "href="bootstrap-sass/stylesheets/all.css" type="text/css" media="screen"/>
<link rel="stylesheet" href="bootstrap-sass/stylesheets/sys-styles.css" media="screen" type="text/css"/>
Due to the fact that I installed Compass which complies my sass I have a slightly different webroot structure. IE:
webroot
->bootstrap-sass
->.sass-cache
->fonts
->javascripts
->sass
->stylesheets
->webfonts
config.rb
->css
->font
->img
->js
IE: I don't store my stylesheets in the Cake default css folder. I'm not sure if I can recompile it so it does but I thought I'd save that job for just before I go live.
A POSSIBLE ANSWER:
Chrome doesn't make requests to anything it shouldn't be but because I use compass I have to traverse the directory to ensure the files can be found on index, add, view and edit and Chrome confirms it makes multiple requests for the same file.
And I have to declare each of the above links 3 times like below and Chrome confirms it's making 3 requests for each file.
<!-- Welcome and index etc -->
<script src="bootstrap-sass/javascripts/jquery.min.js"></script>
<!-- Add -->
<script src="../bootstrap-sass/javascripts/jquery.min.js"></script>
<!-- View and edit -->
<script src="../../bootstrap-sass/javascripts/jquery.min.js"></script>
Could this be the cause of the behaviour I'm seeing?
I think that might be it because the multiple entries in the database are always either an extra 3, 6, 9. Ie: Always grouped in threes and it's a bit coincidental that I declare each file 3 times and the user agent confirms its making 3 requests.
Surely that's got to be it. Would you agree?
Also thanks for the heads up about not echoing data from the controller. Can I confirm you mean I should write to a flat file or even to the db.
Thanks.
Related
I get the following error:
Catchable fatal error: Argument 1 passed to CorenlpAdapter::getOutput() must be an instance of string, string given, called in /Library/WebServer/Documents/website/php-stanford-corenlp-adapter/index.php on line 22 and defined in /Library/WebServer/Documents/website/php-stanford-corenlp-adapter/src/CoreNLP/CorenlpAdapter.php on line 95
index.php 21 and 22 contain:
$text1 = 'I will meet Mary in New York at 10pm';
$coreNLP->getOutput($text1);
corenlpAdapter.php lines 95 and onwards contain:
public function getOutput(string $text){
if(ONLINE_API){
// run the text through the public API
$this->getServerOutputOnline($text);
} else{
// run the text through Java CoreNLP
$this->getServerOutput($text);
}
// cache result
$this->serverMemory[] = $this->serverOutput;
if(empty($this->serverOutput)){
echo '** ERROR: No output from the CoreNLP Server **<br />
- Check if the CoreNLP server is running. Start the CoreNLP server if necessary<br />
- Check if the port you are using (probably port 9000) is not blocked by another program<br />';
die;
}
/**
* create trees
*/
$sentences = $this->serverOutput['sentences'];
foreach($this->serverOutput['sentences'] as $sentence){
$tree = $this->getTreeWithTokens($sentence); // gets one tree
$this->trees[] = $tree; // collect all trees
}
/**
* add OpenIE data
*/
$this->addOpenIE();
// to get the trees just call $coreNLP->trees in the main program
return;
}
Why exactly am I getting this error when text1 is a string?
I am the original author of this class. As you can see, the function getOutput looks like this:
public function getOutput(string $text){
...
}
Change that to:
public function getOutput($text){
...
}
The function tries to enforce that the input is string. The original code should work. However, it seems that in your case, PHP thinks "string" is not actually a string. Maybe the coding environment (the IDE) you are using uses the wrong character set? Or maybe you copy-pasted the code from HTML into the IDE or something like that. Thus whilst it says "string" on the screen, it's not actually a string to PHP.
If you are sure the input is a string, you can safely change the code like above. The class should then work normally.
public function getOutput($text){
.
.
.
}
I am trying to use the output from a php file in a TemplaVoila FCE.
According to the articles, etc, I have found on the subject, I seem to be doing it right. But it does not work.
I have reduced my implementation to a very simple test, and I hope that someone here can tell me what I am doing wrong.
The php code is in fileadmin/php/test.php
The file contains this code:
<?php
function getBeechgroveTest($content, $conf)
{
return 'B';
}
//echo getBeechgroveTest(0,0);
?>
In the main template (template module - not TemplaVoila) I have added this line:
includeLibs.beechgroveTest = fileadmin/php/test.php
I have tried to put it at the root level and inside a PAGE object. Both gave the same result.
If I uncomment the 'echo' line I get a 'B' at the top of my HTML page, so the php must be read at some point.
My FCE has one field of type 'None (TypoScript only)' and contains this code:
10 = TEXT
10 {
value = A
}
20 = USER
20 {
userFunc = getBeechgroveTest
}
30 = TEXT
30 {
value = C
}
I was expecting the FCE to output 'ABC', but I only get 'AC'.
What am I doing wrong?
I use TYPO3 version 4.5.30 and TemplVoila 1.8.0
It must by problem in cache, try use USER_INT instead USER. If you create this object as USER_INT, it will be rendered non-cached, outside the main page-rendering.
20 = USER_INT
20 {
userFunc = getBeechgroveTest
}
I have slapped together a test PHP script. It would output some remote connection's geo ip based data. Nothing fancy, just a quick prototype.
But I am seeing an odd behavior, so I am asking here if somebody had any clues about it.
PHP is version 5.5.12 on Ubuntu 64 bit.
Here's some code from the geoip_test.php calling script:
require_once ('geoip_utils.php');
$server_geoip_record = geoip_record_by_name('php.net');
echo '<pre>PHP.net web server location: ' . print_r($server_geoip_record, 1);
echo '<br />PHP.net web server local time: ' . \df_library\getUserTime($server_geoip_record)->format('Y-m-d H:i:s');
Nothing fancy at all, isn't it?
Now the simple geoip_utils.php code:
<?php
namespace df_library;
require_once('timezone.php');
// Given an IP address, returns the language code (i.e. en)
function getLanguageCodeFromIP($input_ip)
{
};
// Given a geo_ip_record, it returns the local time for the location indicated
// by it. In case of errors, it will return the optionally provided fall back value
function getUserTime($geoip_record, $fall_back_time_zone = 'America/Los_Angeles') {
//Calculate the timezone and local time
try
{
//Create timezone
$timezone = #get_time_zone($geoip_record['country_code'], ($geoip_record['region'] != '') ? $geoip_record['region'] : 0);
if (!isset($timezone)) {
$timezone = $fall_back_time_zone;
}
$user_timezone = new \DateTimeZone($timezone);
//Create local time
$user_localtime = new \DateTime("now", $user_timezone);
}
//Timezone and/or local time detection failed
catch(Exception $e)
{
$user_localtime = new \DateTime("now");
}
return $user_localtime;
}
?>
When I run the calling script I get:
PHP Fatal error: Call to undefined function df_library\getUserTime() in /var/www/apps/res/geoip_test.php on line 5
The funny part is: if I add this debug code:
$x = get_defined_functions();
print_r($x["user"]);
I get this output:
Array
(
[0] => df_library\getlanguagecodefromip
[1] => df_library\gettimezone
[2] => df_library\getutcdatetime
[3] => df_library\getlocalizedtime
[4] => df_library\getutcdatetimeaslocalizeddatetime
[5] => df_library\getlocalizeddatetimeasutcdatetime
[6] => get_time_zone
)
First of all, I don't understand why the function names are converted to lower case.
But most of all, notice how index 0 shows the empty function function getLanguageCodeFromIP($input_ip) being defined, and that function is right above the one that the interpreter complains about as not being defined!
Why does PHP see the other function in that file but not the one I want to use?
Any ideas are welcome!
There is an extra semi-colon ; after the close bracket of function getLanguageCodeFromIP which causes PHP parser somehow unable to recognize the functions after getLanguageCodeFromIP.
As proven in OP's comment, removing the ; solved the problem.
I know that an E_WARNING is generated by PHP
PHP Warning: Unknown: Input variables exceeded 1000
But how can I detect this in my script?
A "close enough" method would be to check if( count($_POST, COUNT_RECURSIVE) == ini_get("max_input_vars"))
This will cause a false positive if the number of POST vars happens to be exactly on the limit, but considering the default limit is 1000 it's unlikely to ever be a concern.
count($_POST, COUNT_RECURSIVE) is not accurate because it counts all nodes in the array tree whereas input_vars are only the terminal nodes. For example, $_POST['a']['b'] = 'c' has 1 input_var but using COUNT_RECURSIVE will return 3.
php://input cannot be used with enctype="multipart/form-data". http://php.net/manual/en/wrappers.php.php
Since this issue only arises with PHP >= 5.3.9, we can use anonymous functions. The following recursively counts the terminals in an array.
function count_terminals($a) {
return is_array($a)
? array_reduce($a, function($carry, $item) {return $carry + count_terminals($item);}, 0)
: 1;
}
What works for me is this. Firstly, I put this at the top of my script/handler/front controller. This is where the error will be saved (or $e0 will be null, which is OK).
$e0 = error_get_last();
Then I run a bunch of other processing, bootstrapping my application, registering plugins, establishing sessions, checking database state - lots of things - that I can accomplish regardless of exceeding this condition.. Then I check this $e0 state. If it's not null, we have an error so I bail out (assume that App is a big class with lots of your magic in it)
if (null != $e0) {
ob_end_clean(); // Purge the outputted Warning
App::bail($e0); // Spew the warning in a friendly way
}
Tweak and tune error handlers for your own state.
Registering an error handler won't catch this condition because it exists before your error handler is registered.
Checking input var count to equal the maximum is not reliable.
The above $e0 will be an array, with type => 8, and line => 0; the message will explicitly mention input_vars so you could regex match to create a very narrow condition and ensure positive identification of the specific case.
Also note, according to the PHP specs this is a Warning not an Error.
function checkMaxInputVars()
{
$max_input_vars = ini_get('max_input_vars');
# Value of the configuration option as a string, or an empty string for null values, or FALSE if the configuration option doesn't exist
if($max_input_vars == FALSE)
return FALSE;
$php_input = substr_count(file_get_contents('php://input'), '&');
$post = count($_POST, COUNT_RECURSIVE);
echo $php_input, $post, $max_input_vars;
return $php_input > $post;
}
echo checkMaxInputVars() ? 'POST has been truncated.': 'POST is not truncated.';
Call error_get_last() as soon as possible in your script (before you have a chance to cause errors, as they will obscure this one.) In my testing, the max_input_vars warning will be there if applicable.
Here is my test script with max_input_vars set to 100:
<?php
if (($error = error_get_last()) !== null) {
echo 'got error:';
var_dump($error);
return;
}
unset($error);
if (isset($_POST['0'])) {
echo 'Got ',count($_POST),' vars';
return;
}
?>
<form method="post">
<?php
for ($i = 0; $i < 200; $i++) {
echo '<input name="',$i,'" value="foo" type="hidden">';
}
?>
<input type="submit">
</form>
Output when var limit is hit:
got error:
array
'type' => int 2
'message' => string 'Unknown: Input variables exceeded 100. To increase the limit change max_input_vars in php.ini.' (length=94)
'file' => string 'Unknown' (length=7)
'line' => int 0
Tested on Ubuntu with PHP 5.3.10 and Apache 2.2.22.
I would be hesitant to check explicitly for this error string, for stability (they could change it) and general PHP good practice. I prefer to turn all PHP errors into exceptions, like this (separate subclasses may be overkill, but I like this example because it allows # error suppression.) It would be a little different coming from error_get_last() but should be pretty easy to adapt.
I don't know if there are other pre-execution errors that could get caught by this method.
What about something like that:
$num_vars = count( explode( '###', http_build_query($array, '', '###') ) );
You can repeat it both for $_POST, $_GET, $_COOKIE, whatever.
Still cant be considered 100% accurate, but I guess it get pretty close to it.
I hope the title isn't too confusing, I'll try to explain better below.
Suppose I have a function in a separate file, functions.php:
function divide($num1, $num2) {
if ($num1 == 0 || $num2 == 0) {
trigger_error("Cannot divide by 0", E_USER_ERROR);
} else {
return ($num1 / $num2);
}
}
And another file that calls it:
include "functions.php";
echo divide(10, 0);
My error is
Fatal error: Cannot divide by 0 in
C:\Users\Derek\Desktop\projects\functions.php on line 5
My question is, how do I make that error instead point to the location of the error in the main code, so I instead get:
Fatal error: Cannot divide by 0 in
C:\Users\Derek\Desktop\projects\main.php on line 3
The particular reason I want this is because I have a function called load_class that simply finds a PHP file and instantiates the object inside, but if given an incorrect file name, it reports an error from inside load_class, which is technically true, but it's not particularly helpful if I don't remember where I called load_class in the first place. I would like the error to point to the file that called load_class incorrectly.
Also, I would like to write a function error() (something like below) that when given a message as a parameter would throw more "meaningful" error messages, but when done that way, the error always says it comes from error(), not from where the error actually came from!
For example, in an error.php:
/**
* error()
*
* Throws an error of a certain type
*
* #param string $type The type of error. "Fatal", "warning," or "notice"
* #param string $message A description of the error
* #return void
*/
function error($type, $message) {
switch (strtolower($type)) {
case 'fatal':
trigger_error($message, E_USER_ERROR);
break;
case 'notice':
trigger_error($message, E_USER_NOTICE);
default:
trigger_error($message, E_USER_WARNING);
break;
}
}
And in an index.php
error("fatal", "A sample warning!");
My error given is:
Fatal error: A sample warning! in
C:\Users\Derek\Desktop\projects\synthesis\sys\Error.php on line 45
But the error didn't occur in error.php, it happened in index.php! How can I make it show where it really came from?
The debug_backtrace function allows you to obtain the stacktrace as an array. You can pick the original location from there.
Next to that you need to slip into the error message to make this look-alike. Example:
function divide($num1, $num2) {
if ($num1 == 0 || $num2 == 0) {
trigger_error_original("Cannot divide by 0", E_USER_ERROR);
} else {
return ($num1 / $num2);
}
}
function trigger_error_original($message, $type) {
$trace = debug_backtrace(FALSE);
list($location) = array_slice($trace, 1, 1) + array('file' => 'unknown', 'line' => 'unknown');
$message .= sprintf(" in %s on line %d\nTriggered", $location['file'], $location['line']);
trigger_error($message, $type);
}
divide(1, 0);
The error message than shows something like:
> php test-pad.php
Fatal error: Cannot divide by 0 in test-pad.php on line 18
Triggered in test-pad.php on line 15
The downside of this is, that you need to change your code to have this "feature". If you need this for debugging your own code, it's much better that you enable backtraces in your logs. The Xdebug extension does this for you, or you can write your own error handler that takes care of that.
See as well the related question Caller function in PHP 5?. I used array_slice so that you could create an additional parameter to define the number of steps you want to go "up" in the backtrace.
Use debug_backtrace(), and debug_print_backtrace() for a full call stack. These are especially effective when using Xdebug, which will override the function to colorize the output.
I have this same problem...
#1: while 10/0 = ERROR, 0/10 = 0 is perfectly legal, you shouldn't have an exception for that.
#2: when you include a file, it effectively becomes part of this new file, so perhaps you might have to toy a little bit with things like __FILE__ and see if you can make it point it to the file before it gets included in the other file..
You can use xdebug - it will show you the stacktrace or you can register your own error handndler and display the stacktrace. Just check the example in php.net for set_error_handler().
Maybe exceptions are better to use in your case. You get the full stacktrace and can locate where the function was called without relying on some tricky code :)