I'm trying to mark exam "is_complete" if the "result" reaches 2. I would like to do this when the form is submitted by the user after completing the test and calculating the results. Both variables are in the same table. Is this possible through PHP or do I need to use Java script.
Here is how I've tried to work out the code.
On the model
public function answers()
{
return $this->hasMany('App\ExamResultsAnswers');
}
public function passed()
{
$instance = new ExamResult;
$instance->result < 2;
$var = ExamResult::where('result', '>=', 2)->get();
$var_is_greater_than_two = ($var >= 2 ? true : false);
$condition = new ExamResult;
$condition->is_complete ='1';
if ($this->compare($instance, $condition)) {
return $instance->$column == 1;
}
}
On the controller
public function exam($course_id, Request $request)
{
$course = Course::where('id', $course_id)->firstOrFail();
$answers = [];
$exam_score = 0;
foreach ($request->get('question') as $question_id => $answer_id) {
$question = ExamQuestion::find($question_id);
$correct_answer = ExamOption::where('exam_question_id', $question_id)
->where('id', $answer_id)
->where('is_correct', 1)->count() > 0;
$answers[] = [
'exam_question_id' => $question_id,
'exam_option_id' => $answer_id,
'corect' => $correct_answer
];
if ($correct_answer) {
$exam_score += $question->score;
}
}
$exam_result = ExamResult::create([
'exam_id' => $course->exam->id,
'employee_id' => \Auth::id(),
'result' => $exam_score,
]);
$exam_result->answers()->createMany($answers);
$exam_result->passed();
return redirect()->route('learn.show', [$course, $request])->with('message', 'Test score: ' . $exam_score);
}
The controller is supposed to do the following
Find all the course and the exam that are associated with the course- this works
Then find all the questions and the options for the questions - this works
Then if the user selects the correct answer
then count all correct answers - this works
Then I want to save the
results and mark it complete if the answers are above a 2 correct
answers. - Here the code saves the exam_id, employee_id and result,
but it doesn't make it complete if the result is equal to 2. That is
why I was trying to do this on the model.
Use the following code to mark the Exam as completed:
$exam_result = ExamResult::create([
'exam_id' => $course->exam->id,
'employee_id' => \Auth::id(),
'result' => $exam_score,
]);
$exam_result->answers()->createMany($answers);
if($exam_result->result > 2) {
$exam_result->is_complete = 1;
$exam_result->save();
}
Let me know if i misunderstood the requirement.
Another optimised solution
$exam_result = ExamResult::create([
'exam_id' => $course->exam->id,
'employee_id' => \Auth::id(),
'result' => $exam_score,
'is_complete' => $exam_score > 2
]);
$exam_result->answers()->createMany($answers);
Related
When I try to push an array of records to my Laravel Collection, I end up with only the first record being inserted into the collection. It was working well before I made a few changes (related to $limit and $test) but now produces this bug.
public static function processOldOrders(int $limit = 1, bool $test = true)
{
//Read CSV and turn into a collection
$allOldOrders = Excel::toArray([], 'orders.csv');
$orderCollection = collect();
$count = 0;
foreach ($allOldOrders as $key => $oldOrder) {
$orderCollection->push(
(object) [
'order_id' => ltrim($oldOrder[$key][0], "#"),
'order_created' => $oldOrder[$key][4],
'email' => $test ? 'test#test.com' : trim(strtolower($oldOrder[$key][5])),
'phone' => ltrim($oldOrder[$key][12], "'"),
'first_name' => $oldOrder[$key][7],
'last_name' => $oldOrder[$key][8],
'purchase_status' => 'a_purchase',
'total_price' => $oldOrder[$key][33],
'items' => [],
'gift_cards' => [],
'coupons' => [],
'shipping_data' => [],
]
);
$count++;
if ($count >= $limit) {
break;
}
}
dd($orderCollection);
In your processOldOrders method by default you set limit = 1. And on the bottom,
$count = 0;
foreach ($allOldOrders as $key => $oldOrder) {
...
$count++;
if ($count >= $limit) {
break;
}
...
there you check it with count. At first time $count is equal to 0, and you plus it 1. Then you checking it with $limit. By default limit is 1 . So it works only one time. Yu have to more than one limit where you call processOldOrders method
There's nothing wrong with your code. But you forgot the output of Excel::toArray(), that it has sub-arrays.
array:1 [
0 => array [ // <-- init
0 => array [ // <-- CSV data
...
So, you can change your $allOldOrders to $allOldOrders[0] :
foreach ($allOldOrders[0] as $key => $oldOrder) {
...
}
I have an array for showing users' contacts list to each other.
I want to add ->paginate(10) features for json response but I could not find where I must put it.
My index function:
public function index()
{
$contacts = [];
$user = request()->user();
// Loop through the contacts and format each one
Contact::for($user->id)->get()->each(function ($contact) use ($user, &$contacts) {
$friend = $contact->user1_id === $user->id ? $contact->user2 : $contact->user1;
$contacts[] = $friend->toArray() + ['room' => $contact->room->toArray()];
});
return response()->json($contacts);
}
You can create a collection for the contact and use LenfthAwarePaginator
class ContactResourceCollection extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
$response = [
'data' => $this->collection,
];
if($this->resource instanceof LengthAwarePaginator)
{
$response['pagination'] = [
'total' => $this->resource->total(),
'lastPage' => $this->resource->lastPage(),
'perPage' => $this->resource->perPage(),
'currentPage' => $this->resource->currentPage(),
'nextPageUrl' => $this->resource->nextPageUrl(),
'previousPageUrl' => $this->resource->previousPageUrl(),
];
}
return $response;
}
}
In the controller method add this line:
return new UserResourceCollection($users);
Here is the total code
$contacts = Contact::where('user_id', $user->id)->paginate(12);
if($contacts->count()){
$pageIndex = array();
$lastPage = $contacts->lastPage();
$user = request()->user();
for($i= 2; $i<=$lastPage; $i++){
array_push($pageIndex, $i);
}
return response()->json([
'contacts' => $contacts->map(function ($contact) use ($user) {
if($contact->user1_id === $user->id){
return [
'friend' => $contact->user2,
'room' => $contact->room,
];
} else {
return [
'friend' => $contact->user1,
'room' => $contact->room,
];
}
})->toArray(),
'per_page' => $contacts->perPage(),
'on_first_page' => $contacts->onFirstPage(),
'last_page' => $contacts->lastPage(),
'first_page_url' => $contacts->url(1),
'next_page_url' => $contacts->nextPageUrl(),
'prev_page_url' => $contacts->previousPageUrl(),
'last_page_url' => $contacts->url($contacts->lastPage()),
'total' => $contacts->total(),
'pageIndexArray' => $pageIndex,
'errors' => false,
]);
} else {
// Do Nothing
}
Call
GET 'URL?page='+Page_index to get the response in JS (AJAX)
I am not sure but try : replace get() to paginate(10)
Contact::for($user->id)->paginate(10)->each(function ($contact) use ($user, &$contacts) {
$friend = $contact->user1_id === $user->id ? $contact->user2 : $contact->user1;
$contacts[] = $friend->toArray() + ['room' => $contact->room->toArray()];
});
Can you change the query into:
$contacts = Contact::for($user->id)->paginate(10);
Then after this query you can use for loop for $contact;
foreach ($contacts as $key => $contact)
{
$friend = $contact->user1_id === $user->id ? $contact->user2 : $contact->user1;
$contacts[] = $friend->toArray() + ['room' => $contact->room->toArray()];
}
Paginate first before get into loop/each.
If I have an Illuminate\Support\Collection, how do I sort by multiple properties with both asc and desc? (This is a simple hypothetical - not at all looking for tips on query building.)
$collection = User::all(); // not looking for User::orderBy()->get() solutions. Read the question.
$sorting_insructions = [
['column'=>'first_name', 'order'=>'asc'],
['column'=>'date_of_birth', 'order'=>'desc'],
];
$collection->sort(function($a,$b) use ($sorting_instructions){
// something...
});
I know this question is from a while back, but in case anyone stumbles across it, Laravel does provide this functionality now (v8.x at the time of writing).
See the official docs for more detail. (4th code block example in the sort-by section)
https://laravel.com/docs/8.x/collections#method-sortby
From the docs:
If you would like to sort your collection by multiple attributes, you may pass an array of sort operations to the sortBy method. Each sort operation should be an array consisting of the attribute that you wish to sort by and the direction of the desired sort:
$collection = collect([
['name' => 'Taylor Otwell', 'age' => 34],
['name' => 'Abigail Otwell', 'age' => 30],
['name' => 'Taylor Otwell', 'age' => 36],
['name' => 'Abigail Otwell', 'age' => 32],
]);
$sorted = $collection->sortBy([
['name', 'asc'],
['age', 'desc'],
]);
$sorted->values()->all();
/*
[
['name' => 'Abigail Otwell', 'age' => 32],
['name' => 'Abigail Otwell', 'age' => 30],
['name' => 'Taylor Otwell', 'age' => 36],
['name' => 'Taylor Otwell', 'age' => 34],
]
*/
Applied to the example from the question:
/*
$sorting_instructions = [
['column'=>'first_name', 'order'=>'asc'],
['column'=>'date_of_birth', 'order'=>'desc'],
];
*/
$collection = User::all();
$sortedCollection = $collection->sortBy([
['first_name','asc'],
['date_of_birth','desc'],
])
Whoever uses this, just keep in mind - you'll need to tweak it depending if your using collection of objects or associative arrays. Should be an easy tweak. Just change the $a[]/$b[] stuff to $a-> and $b->
public static function multiPropertySort(Collection $collection, array $sorting_instructions){
return $collection->sort(function ($a, $b) use ($sorting_instructions){
//stuff starts here to answer question...
foreach($sorting_instructions as $sorting_instruction){
$a[$sorting_instruction['column']] = (isset($a[$sorting_instruction['column']])) ? $a[$sorting_instruction['column']] : '';
$b[$sorting_instruction['column']] = (isset($b[$sorting_instruction['column']])) ? $b[$sorting_instruction['column']] : '';
if(empty($sorting_instruction['order']) or strtolower($sorting_instruction['order']) == 'asc'){
$x = ($a[$sorting_instruction['column']] <=> $b[$sorting_instruction['column']]);
}else{
$x = ($b[$sorting_instruction['column']] <=> $a[$sorting_instruction['column']]);
}
if($x != 0){
return $x;
}
}
return 0;
})->values();
}
Simple as doing this:
return $this->items->sortBy([
['is_hidden', 'asc'],
['title', 'asc']
]);
Add those fields you want to order as an array with field name and direction.
:)
If you are using Eloquent to get your collection instance, It would be much better to use orderBy method in your query, especially if columns were indexed:
$sorting_insructions = [
['column'=>'first_name', 'order'=>'asc'],
['column'=>'date_of_birth', 'order'=>'desc'],
];
$collection = App\User::query();
foreach ($sorting_insructions as $value) {
$collection->orderBy($value['column'], $value['order']);
}
$users = $collection->get();
EDIT Since the question was edited telling that sort should be used outside the query builder, I think chaining sortBy and sortByDesc in reverse order from $sorting_insructions gives the same result:
$collection = App\User::all();
$sorting_insructions = [
['column'=>'first_name', 'order'=>'asc'],
['column'=>'date_of_birth', 'order'=>'desc'],
];
for ($i = count($sorting_insructions) - 1; $i >= 0 ; $i--) {
extract($sorting_insructions[i]);
if ( $order === 'asc') {
$collection = $collection->sortBy( $column );
} else {
$collection = $collection->sortByDesc( $column );
}
}
public static function multiPropertySort(
Collection $collection,
array $rules
)
{
return $collection->sort(
function ($a, $b) use ($rules) {
foreach($rules as $rule){
$sortColumn = array_get($rule, 'column');
array_set(
$a,
$sortColumn,
array_get($a, $sortColumn, '')
);
array_set(
$b,
$sortColumn,
array_get($b, $sortColumn, '')
);
if ($sortOrder = array_get($rule, 'order', 'asc')) {
$x = (array_get($a, $sortColumn) <=> array_get($b, $sortColumn));
} else {
$x = (array_get($b, $sortColumn) <=> array_get($a, $sortColumn));
}
if ($x != 0) {
return $x;
}
}
return 0;
}
);
}
Let's imagine something imperative...
Using this lib uploading works great. I have number of objects in an excel and I go through them and do whatever I desire.
The question is while uploading the excel I am ought to check whether a particular object already exists, if so increment the $rejected variable otherwise create and increment the $uploaded variable. As a result I would like to return the results: how many uploaded and how many rejected? Whats the best way to do as such? It is obvious I can't access those variables inside the function. What's the best practice here?
public function uploadUsingFile($file)
{
$rejected = 0;
$uploaded = 0;
Excel::load($file, function ($reader) {
foreach ($reader->toArray() as $row)
{
$plateAlreadyExist = Plate::where('serial_number', $row['plate_serial_number'])->exists();
if ($plateAlreadyExist) {
$rejected += 1;continue;
}
$supplier = Supplier::firstOrCreate(['name' => $row['supplier_name']]);
$statusName = EquipmentStatusCode::firstOrCreate(['name' => $row['status_name']]);
$plateType = PlateType::firstOrCreate(['name' => $row['plate_type_name']]);
$process = Process::firstOrCreate(['name' => $row['process_name']]);
$project = Project::firstOrCreate(['name' => $row['project_name']]);
$plateQuality = PlateQuality::firstOrCreate(['name' => $row['plate_quality']]);
$wafer = Wafer::firstOrCreate(['serial_number' => $row['wafer_serial_number']]);
$data = [
'serial_number' => $row['plate_serial_number'],
'crc_code' => $row['crc_code'],
'supplier_id' => $supplier['id'],
'equipment_status_code_id' => $statusName['id'],
'plate_type_id' => $plateType['id'],
'process_id' => $process['id'],
'project_id' => $project['id'],
'plate_quality_id' => $plateQuality['id'],
'wafer_id' => $wafer['id'],
'created_by' => Auth::user()->id,
];
if($data)
{
Plate::create($data);
$uploaded += 1;
}
}
});
return [ 'uploaded' => $uploaded, 'rejected' => $rejected ];
}
You can pass a reference to the variables into the closure by using the use keyword:
...
Excel::load($file, function ($reader) use(&$rejected, &$uploaded){
...
}
Anonymous Functions
In a repository, I need a custom getALL query which checks for items based on an array of data. e.g. I need to be able to send $params = [ 'type' => [ 'type1', 'type2' ], 'username' => $username ] but I can currently only send one parameter such as: $params = ['type' => 'type1', 'username' => $username].
What would be the best way of adding acceptance of an array (like the one above) to this getAll query?:
public function getAllQuery($params = [])
{
$query = $this->createQueryBuilder('c');
if(count($params))
{
foreach($params as $param => $value)
{
//todo need to make it accept an array of parameters
if(in_array($param, $this->getClassMetadata()->getFieldNames()) && $param != 'deleted')
{
$query
->andWhere(sprintf('c.%1$s LIKE :%1$s', $param))
->setParameter($param, sprintf('%%%s%%', $value));
}
/*code already exists here for checking other parameters
outside of 'type' (e.g. 'username') */
}
}
return $query->getQuery();
}
I am not able to comment so I will answer without to be sur that I've understand the question. Are you waiting something like this ? In this case it's more a PHP problem than Doctrine.
The results of the past code : here.
$params = array( 'type' => array( 'type1', 'type2' ), 'username' => 'flavien');
$requiredFields = array('type');
if(count($params))
{
foreach($params as $param => $value)
{
if(in_array($param, $requiredFields) && $param != 'deleted')
{
if(is_array($value))
{
echo "My param: {$param}" . PHP_EOL;
for($i=0; $i < count($value); $i++) {
echo $value[$i] . PHP_EOL;
}
}
}
}
}