I am trying to unit test a function that calls another function that uses the storage facade. When I run the function in phpunit, it returns the error, "Error: Class 'Storage' not found." How can I run the function either without an error or defaulting the second function to return true?
Part of the code I am trying to test.
public function newMaterial($fileType, $groupId, $categoryId, $fileName, $file, $open, $close) {
$material = new Mat;
if (!$this->save($fileType, $file, $material)) {
return false;
}
}
This is the section of code that is causing the error.
protected function save($fileType, $file, Mat $material) {
//generate random name
do {
$key = str_random(32);
//check if exist
} while (Storage::exists($path . $key . '.' . $ext));
return true;
}
This is not all of my code, just the parts that are causing an issue.
In my test I am only calling the newMaterial Function and it returns the error.
Related
I have a dynamic action class that is reusable through the entire app UploadImageAction
It handles single image upload per request:
class UploadImageAction implements UploadImageContract
{
public function handle(Request $request, $imageProperty, $image, $imageDir)
{
if ($request->hasFile($imageProperty)) {
// Handle uploading lf_image
if (!is_null($image) && Storage::exists($image)) {
// Throw exceptions here
Storage::delete($image);
}
// Throw exceptions here
return $request->file($imageProperty)->store($imageDir);
}
}
}
The way I handle the action is by creating a method in service class:
protected function handleAttachments($request, $report)
{
// Handle Attachments
$uploadImageAction = resolve(UploadImageAction::class);
// Handle lf_image action
if($request->hasFile('attachments')) {
$report->attachments = $uploadImageAction->handle($request, 'attachments', $report->attachments, 'incidents');
}
return $report;
}
Then call in within the storeReport():
$report = Report::create($request->validated());
$report = $this->handleAttachments($request, $report); // Called here
What I'm trying to do is make it possible to upload multiple images, I wrote the below code block in the storeReport method in Service class just to test (commented out the handleAttachments()):
$ir_counter = $report->ir_number->prefix ?? 'IR#' . str_pad($report->ir_number, 4, '0', STR_PAD_LEFT);
$attachments = [];
if($request->hasfile('attachments'))
{
foreach($request->file('attachments') as $attachment)
{
$name = time().rand(100, 1000) . '.' . $attachment->extension();
$attachment->move(public_path('app/incident/' . $ir_counter . '/'), $name);
$attachments[] = $name;
}
}
$report->attachments = implode('|', $attachments);
It's working as expected. But how can I reflect the array/loop in the UploadImageAction code? When I try to do so, the DB column of attachments shows: [{}].
I just wrote a little "logging" class and want to ask a question about the usage of this class, how i could make it easier to use.
For example:
$log = new Log();
$log->Error("You have an error!", __FILE__, __CLASS__, __FUNCTION__, __LINE__);
This is how i write errors to a log file at the moment, but it seems to complex?! Is there a way to get the "MAGIC CONSTANTS" inside the logging class from the "calling" php ?
Here is the class code (any other tips are welcome too):
<?php
class Log
{
private $path;
public function __construct()
{
$config = new Config(); // init. from autoloader
$path = $config->app_log_dir;
if (!is_dir($path) && !is_writable($path))
{
error_log('[ERROR] [Log::__Construct()] -> ' . $path . ' does not exist or is not writeable!',0);
header("HTTP/1.0 500 Internal Server Error");
exit();
}
$this->path = $path;
}
public function Error($message, $file, $class = '', $function = '', $line)
{
$array_data = array($message, $file, $class, $function, $line);
$this->write('ERROR', $array_data);
}
public function TestError($message, $file = __FILE__, $class = __CLASS__, $function = __FUNCTION__, $line = __LINE__)
{
$array_data = array($message, $file, $class, $function, $line);
$this->write('TESTERROR', $array_data);
}
private function write($error_type, $array_data)
{
$date = date("Y-m-d H:i:s");
$dateFile = date("Y-m-d");
$message = "[{$date}] [{$error_type}] [{$array_data[1]}->{$array_data[2]}::{$array_data[3]}:{$array_data[4]}] $array_data[0]".PHP_EOL;
try
{
file_put_contents($this->path.'/'.$dateFile.'.log', $message, FILE_APPEND);
}
catch (Exception $e)
{
error_log('[ERROR] [Log::write()] -> ' . $e, 0);
header("HTTP/1.0 500 Internal Server Error");
exit();
}
}
}
Check out debug_backtrace().
So you can do :
public function Error($message, $debug)
{
$array_data = array($message, $debug);
$this->write('ERROR', $array_data);
}
$log->Error("Oh noo!!", print_r(debug_backtrace(),true) );
A backtrace contains a potentially huge amount of data so I'm not going to example a full one here, but it can contain:
function ; The current function name. See also __FUNCTION__.
line ; The current line number. See also __LINE__.
file ; The current file name. See also __FILE__.
class ; The current class name. See also __CLASS__.
object ; The current object.
type ; The current call type. If a method call, "->" is returned. If a static method call, "::" is returned. If a function
call, nothing is returned.
args ; If inside a function, this lists the functions arguments. If inside an included file, this lists the included file name(s).
debug_backtrace() is a goldmine of information to debug PHP. This covers everything you ask for in your question.
Pass those constants as function parameters instead:
public function Error(
$message,
$file = __FILE__,
$class = __CLASS__,
$function = __FUNCTION__,
$line = __LINE__,
) {
// ...
}
and call as always:
$log->Error('xxx');
If I may, your code smells, why not use PSR-3 compatible logger like Monolog? Or even handle errors like a pro with Whoops.
I have this code:
public function taxesData(Product $product)
{
$taxes = \Auth::user()->taxes;
foreach ($taxes as $tax) {
echo "$product->getTax($tax)";
}
}
which on testing gives this error:
Type error: Too few arguments to function App\Product::getTax(), 0 passed in E:\projects\ims\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.php on line 411 and exactly 1 expected
However, just a small change makes it works, but I am not able to understand. Why?
public function taxesData(Product $product)
{
$taxes = \Auth::user()->taxes;
foreach ($taxes as $tax) {
echo $product->getTax($tax);
}
}
Please help.
I tried to simplify it for the purpose of posting here... actually i am creating json with html component for a datatable ->
public function taxesData(Product $product)
{
$taxes = \Auth::user()->taxes;
return datatables()
->of($taxes)
->addColumn('check',function($tax) use($product){
if($product->hasTax($tax)){
return "<input type='checkbox' class='input-sm row-checkbox' name='tax[$tax->id]' value='$tax->id' checked>";
}else{
return "<input type='checkbox' class='input-sm row-checkbox' name='tax[$tax->id]' value='$tax->id'>";
}
})
->editColumn('tax', function($tax) use($product){
return "<span class='currencyinput form-control'>
<input id='rate' type='text' name='rate' value='$product->getTax($tax)' required autofocus>
</span>"
})
->toJson();
}
Adding getTax method
public function getTax(Tax $t)
{
if($this->hasTax($t)){
return $this->taxes->find($t->id)->pivot->tax;
}
else{
return $t->pivot->tax;
}
}
public function hasTax(Tax $tax)
{
foreach ($this->taxes as $t) {
if($t->id == $tax->id){
return true;
}
}
return false;
}
It fails because you are not following the correct syntax of echo strings.
This would work:
echo "{$product->getTax($tax)}";
or actually, because you dont' need the quotes for such a simple expression:
echo $product->getTax($tax);
Here's what I've done so far.
Just for simplicity, I've created a sample Model.
// SampleModel.php
public function relatedModels()
{
return $this->hasMany(RelatedModel::class);
}
// this is like an accessor, but since our model doesn't have
// a property called `relatedModels`, Laravel will ignore it
// until later...
public function getRelatedModels()
{
return "Sample";
}
Given the following code, here are the outputs.
$a = SampleModel::find($id);
$a->relatedModels;
// this returns a collection of related models to this model.
$a->getRelatedModels();
// this returns "Sample";
// HOWEVER, when we try to interpolate that member function call.
"$a->getRelatedModels()"
// this throws error that the method `getRelatedModels` must return a relationship.
// I've also tried to add an argument to my existing function to be in-line with your situation.
public function getRelatedModels($a) ...
// this works well
$a->getRelatedModels(1);
// but this, yes, it throws the error as same as what you've got.
"$a->getRelatedModels(1)";
The error pointed out this line in the framework's codebase.
// HasAttributes.php
protected function getRelationshipFromMethod($method)
{
$relation = $this->$method(); // <-- this line
For some reason, doing "$a->getRelatedModels(1)" triggers the __get magic method of the model.
Which branches down to this stack call.
// Model.php
public function __get($key)
{
return $this->getAttribute($key);
}
// |
// V
// HasAttributes.php
public function getAttribute($key)
{
...
return $this->getRelationValue($key);
}
// |
// V
// HasAttributes.php
public function getRelationValue($key)
{
...
if (method_exists($this, $key)) {
return $this->getRelationshipFromMethod($key);
}
}
// |
// V
// HasAttributes.php
protected function getRelationshipFromMethod($method)
{
$relation = $this->$method(); // <-- lastly to this
// $method = "getRelatedModels"
// since our method `getRelatedModels` needs an argument
// this call will fail since it wasn't able to provide an argument.
...
}
That's why you're getting the too few arguments passed exception. I want to investigate further but I have to go home!
I don't know if this is a legit bug for Laravel, but if you do think so, issue it on Laravel's github repository.
UPDATE
I've posted an issue in github and this is one of the comments which truly made sense for me.
This is neither a issue with Laravel, nor with PHP. You are just using the wrong syntax, see it here: https://github.com/laravel/framework/issues/23639
Github user #staudenmeir commented:
"$sampleModel->getRelatedModels()" is equivalent to "$sampleModel->getRelatedModels"."()".
The usage of variables in strings is limited to "$foo" and "$foo->bar". Function calls like "$foo->bar()"
don't work. You can (but shouldn't) use curly braces for that: "{$foo->bar()}"
The better solution is just simple string concatenation:
"text..." . $sampleModel->getRelatedModels() . "more text..."
So that is why the magic method __get is being called.
I got an error while running my code, it says call to a member function getBallparkDetailsStartDate() on a non-object.
if($projectStatusId == ProjectStatusKeys::BALLPARK_ACTIVE) {
$ballpark = $this->ballparkDetailsHandler->getBallparkDetailsByProjectId($projectId);
$projectDetails["startdate"] = $ballpark->getBallparkDetailsStartDate();
$projectDetails["enddate"] = $ballpark->getBallparkDetailsEndDate();
$projectDetails["projectid"] = $projectId;
$projectDetails["name"] = $ballpark->getBallparkDetailsBookingRef();
$projectDetails["status"] = ProjectStatusKeys::BALLPARK_ACTIVE;
}
I got the error in this line: $projectDetails["startdate"] = $ballpark->getBallparkDetailsStartDate();
Here is my other code:
public function __construct($ballparkDetailsId, $project,
$ballparkDetailsBookingRef,
$ballparkDetailsStartDate, $ballparkDetailsEndDate,
$ballparkDetailsExpiryDate, $ballparkDetailsDescription,
$ballparkDetailsNotes) {
$this->ballparkDetailsId = $ballparkDetailsId;
$this->project = $project;
$this->ballparkDetailsBookingRef = $ballparkDetailsBookingRef;
$this->ballparkDetailsStartDate = $ballparkDetailsStartDate;
$this->ballparkDetailsEndDate = $ballparkDetailsEndDate;
$this->ballparkDetailsExpiryDate = $ballparkDetailsExpiryDate;
$this->ballparkDetailsDescription = $ballparkDetailsDescription;
$this->ballparkDetailsNotes = $ballparkDetailsNotes;
}
public function getBallparkDetailsId() {
return $this->ballparkDetailsId;
}
public function getProject() {
return $this->project;
}
public function getBankName() {
return $this->getProject()->getBankName();
}
public function getBankRef() {
return $this->getProject()->getBankRef();
}
public function getRegionName() {
return $this->getProject()->getRegionName();
}
public function getProjectStatusName() {
return $this->getProject()->getProjectStatusName();
}
public function getBallparkDetailsBookingRef() {
return $this->ballparkDetailsBookingRef;
}
public function getBallparkDetailsStartDate() {
return $this->ballparkDetailsStartDate;
}
public function getBallparkDetailsEndDate() {
return $this->ballparkDetailsEndDate;
}
public function getBallparkDetailsExpiryDate() {
return $this->ballparkDetailsExpiryDate;
}
public function getBallparkDetailsDescription() {
return $this->ballparkDetailsDescription;
}
public function getBallparkDetailsNotes() {
return $this->ballparkDetailsNotes;
}
public function getProjectId() {
return $this->getProject()->getProjectId();
}
public function getProjectStatusId() {
return $this->getProject()->getProjectStatusId();
}
}
?>
The last time I check this it ran well. But now I don't know what's wrong with this? Please help me find the error. Thanks.
Apparently
$ballpark = $this->ballparkDetailsHandler->getBallparkDetailsByProjectId($projectId);
is not returning a "ballpark" at all. Probably it is returning an error, or something like an empty array.
Try var_dump()'ing $ballpark immediately before the line that raises the error, and see what it contains (probably False, NULL, array() or something equally un-ballparky.
Then, inspect the ballparkDetailsByProjectId() function in the BallparkDetailsHandler.php file. At a guess, you might be passing an invalid (i.e. nonexistent, removed, etc.) $projectId.
Then you might rewrite the code with error checking:
if($projectStatusId == ProjectStatusKeys::BALLPARK_ACTIVE) {
$ballpark = $this->ballparkDetailsHandler->getBallparkDetailsByProjectId($projectId);
if (!is_object($ballpark))
trigger_error("Error: bad project ID: '$projectId': $ballpark",
E_USER_ERROR);
$projectDetails["startdate"] = $ballpark->getBallparkDetailsStartDate();
$projectDetails["enddate"] = $ballpark->getBallparkDetailsEndDate();
$projectDetails["projectid"] = $projectId;
$projectDetails["name"] = $ballpark->getBallparkDetailsBookingRef();
$projectDetails["status"] = ProjectStatusKeys::BALLPARK_ACTIVE;
}
Then in the BallparkDetailsHandler.php file you could modify this code:
// Prepare query or die
if (!($stmt = $this->mysqli->prepare($query))
return "Error in PREPARE: $query";
$stmt->bind_param("i", $projectId);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($ballparkDetailsBookingRef, $bankRef, $regionName,
$projectStatusId, $projectStatusName, $ballparkDetailsDescription,
$ballparkDetailsNotes, $ballparkDetailsStartDate, $ballparkDetailsEndDate,
$ballparkDetailsExpiryDate);
$stmt->fetch();
// If no data, then die
if(!$stmt->num_rows)
return "No data in DB for projectID '$projectId': $query";
// Should be clear sailing from here on. Actually I ought to check
// whether all these new() here do return anything sensible, or not
$bank = new Bank("", "", $bankRef, "");
$region = new Region("", $regionName, "");
$projectStatus = new ProjectStatus($projectStatusId, $projectStatusName);
$project = new Project($projectId, $bank, $region, $projectStatus);
return new BallparkDetails("", $project,
$ballparkDetailsBookingRef, $ballparkDetailsStartDate,
$ballparkDetailsEndDate, $ballparkDetailsExpiryDate,
$ballparkDetailsDescription, $ballparkDetailsNotes);
$ballpark clearly doesn't contain the object you think it does on the line with the error. In fact, it obviously doesn't contain an object at all.
This implies that the preceding line (which sets $ballpark) isn't working properly. It would appear that it's returning a value that is is not an object.
I can't tell what that value is -- it could be null, or it could be an integer, string, array, etc. But whatever it is, it isn't a ballpark object.
I suggest you look at your getBallparkDetailsByProjectId() method to find the source of this problem.
I searched forever trying to find an answer, but was ultimately stumped. I've been writing code to allow multiple bots to connect to a chat box. I wrote all the main code and checked it over to make sure it was all okay. Then when I got to calling the function needed to make it work, it gave me an error saying:
Notice: Undefined variable: ip in C:\wamp\www\BotRaid.php on line 40
And also an error saying:
Fatal Error: Cannot access empty property in C:\wamp\www\BotRaid.php
on line 40
( Also a screenshot here: http://prntscr.com/ckz55 )
<?php
date_default_timezone_set("UCT");
declare(ticks=1);
set_time_limit(0);
class BotRaid
{
public $ip="174.36.242.26";
public $port=10038;
public $soc = null;
public $packet = array();
##############################
# You can edit below this #
##############################
public $roomid="155470742";
public $userid = "606657406";
public $k = "2485599605";
public $name="";
public $avatar=;
public $homepage="";
##############################
# Stop editing #
##############################
public function retry()
{
$this->connect($this->$ip,$this->$port); //Line 40, where I'm getting the error now.
$this->join($this->$roomid);
while($this->read()!="DIED");
}
public function connect($ip, $port)
{
if($this->$soc!=null) socket_close($this->$soc);
$soc = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
if(!$this->$soc)$this->port();
if(!socket_connect($this->$soc,$this->$ip,$this->$port))$this->port();
}
public function port()
{
$this->$port++;
if($this->$port>10038) $this->$port=10038;
$this->retry();
}
public function join($roomid)
{
$this->send('<y m="1" />');
$this->read();
$this->send('<j2 q="1" y="'.$this->$packet['y']['i'].'" k="'.$this->$k.'" k3="0" z="12" p="0" c"'.$roomid.'" f="0" u="'.$this->$userid.'" d0="0" n="'.$this->$name.'" a="'.$this->$avatar.'" h="'.$this->$homepage.'" v="0" />');
$this->port();
$this->$roomid;
}
public function send($msg)
{
echo "\n Successfully connected.";
socket_write($this->$soc, $this->$msg."\0", strlen($this->$msg)+1);
}
public function read($parse=true)
{
$res = rtrim(socket_read($this->$soc, 4096));
echo "\nSuccessfully connected.";
if(strpos(strtolower($res), "Failed"))$this->port();
if(!$res) return "DIED";
$this->lastPacket = $res;
if($res{strlen($res)-1}!='>') {$res.=$this->read(false);}
if($parse)$this->parse($res);
return $res;
}
public function parse($packer)
{
$packet=str_replace('+','#più#',str_replace(' ="',' #=#"',$packet));
if(substr_count($packet,'>')>1) $packet = explode('/>',$packet);
foreach((Array)$packet as $p) {
$p = trim($p);
if(strlen($p)<5) return;
$type = trim(strtolower(substr($p,1,strpos($p.' ',' '))));
$p = trim(str_replace("<$type",'',str_replace('/>','',$p)));
parse_str(str_replace('"','',str_replace('" ','&',str_replace('="','=',str_replace('&','__38',$p)))),$this->packet[$type]);
foreach($this->packet[$type] as $k=>$v) {
$this->packet[$type][$k] = str_replace('#più#','+',str_replace('#=#','=',str_replace('__38','&',$v)));
}
}
}
}
$bot = new BotRaid; //This is where I had the error originally
$bot->retry();
?>
Line 40 is below the "Stop Editing" line. Anyone have any suggestions? Or perhaps need me to clear some things up?
You are accessing the properties of the class incorrectly.
The line:
$this->connect($this->$ip,$this->$port);
Should be:
$this->connect($this->ip, $this->port);
Since there was no local variable called $ip, your expression was evaluating to $this-> when trying to access the property since PHP lets you access properties and functions using variables.
For example, this would work:
$ip = 'ip';
$theIp = $this->$ip; // evaluates to $this->ip
// or a function call
$method = 'someFunction';
$value = $this->$method(); // evaluates to $this->someFunction();
You will have to change all the occurrences of $this->$foo with $this->foo since you used that notation throughout the class.
As noted in the comment by #Aatch, see the docs on variable variables for further explanation. But that is what you were running into accidentally.