Im a big 'ol newbie at Laravel, and im trying to do a query scope but it doesnt seem to be working, i keep getting this error
Argument 1 passed to Letters::scopeForUser() must be an instance of User
My user IS logged in, but it still doesnt seem to be working.
This is my Letters model
<?php
class Letters extends Eloquent {
protected $table = 'letters';
public function scopeForUser(User $u)
{
return $query->where('userid', '=', $u->id);
}
}
and in my controller i have the following
Route::get('myletters', array(
'before' => 'auth|userdetail',
function()
{
// Grab the letters, if any, for this user
$letters = Letters::forUser(Auth::user())->get();
$data = [
'letters' => $letters
];
return View::make('myletters', $data);
}
));
Any help would be greatly appreciated.
Cheers
You should pass a variable $query as the first argument to your method in the Model. For example:
public function scopeForUser($query, User $u)
{
return $query->where('userid', '=', $u->id);
}
The first argument doesn't necessarily need to be $query, but it should be the same variable that you are using inside the scope method ($query in this case).
Related
I am trying to remove some fields before they are validated.
Trying to attempt this inside prepareForValidation()
use Illuminate\Foundation\Http\FormRequest;
class VideoRequest extends ApiRequest
{
// ..code..
protected function prepareForValidation()
{
$this->merge([
'public' => $this->toBoolean($this->public),
'notify' => $this->toBoolean($this->notify),
]);
$video_id = $this->route('video_id');
if($this->isMethod('put') && Video::salesStarted($video_id)){
Log::info('removing sales');
// attempt 1
$this->except(['sales_start', 'tickets', 'price']);
// attempt 2
$this->request->remove('sales_start');
// attempt 3
$this->offsetUnset('sales_start');
}
if($this->isMethod('put') && Video::streamStarted($video_id)){
Log::info('removing streams');
// attempt 1
$this->except(['stream_start', 'stream_count', 'archive']);
// attempt 2
$this->request->remove('sales_start');
// attempt 3
$this->offsetUnset('sales_start');
}
$thumb = $this->uploadThumbnail($video_id);
if($thumb !== null){
$this->merge($thumb);
}
}
// ..code..
}
I made sure the code was entering inside the if statement, however the fields are not being removed.
Running $this->request->remove() and $this->except() have no effect.
If I add safe() it throws Call to a member function safe() on null.
I also tried to use unset() but nothing seems to work.
The rules for the dates are like so:
'sales_start' => 'sometimes|required|date|after:now|before:stream_start',
'stream_start' => 'sometimes|required|date|after:now',
but the $request->validated() returns the errors although it shouldn't be validating the deleted fields.
"sales_start": [
"The sales start must be a date after now."
],
"stream_start": [
"The stream start must be a date after now."
]
Why are the fields not being deleted?
Edit
As requested I added some code.
This is what ApiRequest looks like:
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
abstract class ApiRequest extends FormRequest
{
protected function failedValidation(Validator $validator): void
{
$response['data'] = [];
$response['api_status'] = 'ng';
$response['status_message'] = 'failed validation';
$response['errors'] = $validator->errors()->toArray();
throw new HttpResponseException(
response()->json( $response, 422 )
);
}
protected function toBoolean($booleable)
{
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
}
And the request is called from the controller like so:
public function update(VideoRequest $request, $video_id)
{
... some code ...
$validated = $request->validated();
... some code ...
}
so $this refers to the VideoRequest that extends FormRequest.
Can't find anything about deleting. But acording to Laravel docs you pick what keys you need from a request as follows:
$request->only(['username', 'password']);
// plug everything you need into the array.
$request->except(['username', 'password']);
//plug everything you don't need into the array
The latter is probably most useful to you.
Example:
Say I have the following keys: ['username', 'password', 'randomVal'];
$request->only(['username', 'password']);
// Output:
['username', 'password']
$request->except(['username', 'password']);
// Output:
['randomVal']
To remove (unset) a key from a Request before it goes to the Controller you can use offsetUnset()
inside your request:
protected function prepareForValidation()
{
$this->offsetUnset('sales_start');//same goes for the other key to remove...
}
This is a bit of an ugly answer.
Instead of modifying the request before the validation, I tried adding exclude when getting rules().
So something along these lines:
public function rules() {
$ex = $this->isMethod('put') && Video::salesStarted($video_id) ? 'exclude|' : '';
return [
'sales_start' => $ex.'sometimes|required|other_stuff',
];
}
Note that the validation 'exclude' only works if added first.
So this won't work:
'sometimes|required|other_stuff|exclude' //exclude is called last
I am still unable to find out why remove(), exclude(), offsetUnset() were not working, so this is not the right answer, but I hope it helps if someone is having the same issue.
Edit
Setting this as correct answer as I was unable to find an alternative solution/fix.
I am returning an API response inside a Categories controller in Laravel 5.5 like this...
public function get(Request $request) {
$categories = Category::all();
return Response::json(array(
'error' => false,
'categories_data' => $categories,
));
}
Now I am trying to also have the option to return a specific category, how can I do this as I am already using the get request in this controller?
Do I need to create a new route or can I modify this one to return a specific category only if an ID is supplied, if not then it returns all?
Better case is to create a new route, but you can also change the current one to retrieve all models if the parameter is not supplied. You first gotta choose which approach you will be using. For splitting it into multiple calls you can see Resource controllers and for using one method you can follow Optional Route Parameters
It will be much cleaner if you will create another route. For example
/categories --> That you have
/categories/{id} -> this you need to create
And then add method at same controller
public function show($id) {
$categories = Category::find($id);
return Response::json(array(
'error' => false,
'categories_data' => $categories,
));
}
But if you still want to do it at one route you can try something like this:
/categories -> will list all categories
/categories?id=2 -> will give you category of ID 2
Try this:
public function get(Request $request) {
$id = $request->get('id');
$categories = $id ? Category::find($id) : Category::all();
return Response::json(array(
'error' => false,
'categories_data' => $categories,
));
}
I'm a bit confused how I am to add methods to Eloquent models. Here is the code in my controller:
public function show($id)
{
$limit = Input::get('limit', false);
try {
if ($this->isExpand('posts')) {
$user = User::with(['posts' => function($query) {
$query->active()->ordered();
}])->findByIdOrUsernameOrFail($id);
} else {
$user = User::findByIdOrUsernameOrFail($id);
}
$userTransformed = $this->userTransformer->transform($user);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound('User does not exist');
}
return $this->respond([
'item' => $userTransformed
]);
}
And the code in the User model:
public static function findByIdOrUsernameOrFail($id, $columns = array('*')) {
if (is_int($id)) return static::findOrFail($id, $columns);
if ( ! is_null($user = static::whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
So essentially I'm trying to allow the user to be retrieved by either user_id or username. I want to preserve the power of findOrFail() by creating my own method which checks the $id for an int or string.
When I am retrieving the User alone, it works with no problem. When I expand the posts then I get the error:
Call to undefined method
Illuminate\Database\Query\Builder::findByIdOrUsernameOrFail()
I'm not sure how I would go about approaching this problem.
You are trying to call your method in a static and a non-static context, which won't work. To accomplish what you want without duplicating code, you can make use of Query Scopes.
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
if ( ! is_null($user = $query->whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
You can use it exactly in the way you are trying to now.
Also, you can use firstOrFail:
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
return $query->whereUsername($id)->firstOrFail($columns);
}
Your method is fine, but you're trying to use it in two conflicting ways. The one that works as you intended is the one in the else clause, like you realised.
The reason the first mention doesn't work is because of two things:
You wrote the method as a static method, meaning that you don't call it on an instantiated object. In other words: User::someStaticMethod() works, but $user->someStaticMethod() doesn't.
The code User::with(...) returns an Eloquent query Builder object. This object can't call your static method.
Unfortunately, you'll either have to duplicate the functionality or circumvent it someway. Personally, I'd probably create a user repository with a non-static method to chain from. Another option is to create a static method on the User model that starts the chaining and calls the static method from there.
Edit: Lukas's suggestion of using a scope is of course by far the best option. I did not consider that it would work in this situation.
Here is what their documentation says
You can also pass a string to result() which represents a class to
instantiate for each result object (note: this class must be loaded)
$query = $this->db->query("SELECT * FROM users;");
foreach ($query->result('User') as $user) {
echo $row->name; // call attributes
echo $row->reverse_name(); // or methods defined on the 'User' class
}
Despite the fact that they are echoing $row instead of $user... this does not seem to work for me. Here is my version of testing it
Model
class User extends CI_Model{
var $first;
var $last;
..
function getName() {
return $this->first + " " + $this->last;
}
}
Controller
class Tester extends CI_Controller {
public function index() {
$this->load->model('User');
$query = $this->db->query('SELECT * from USERS');
$data = array (
'regular' => $query->result(),
'modeled' => $query->result('User')
);
$this->load->view('test', $data);
}
}
View
foreach ($regular as $row) {
echo "{$row->FIRST} {$row->LAST} <BR/>";
}
echo "<br/>";
foreach ($modeled as $row) {
echo "{$row->getName()} <BR/>";
}
Is there something that I'm doing wrong or misunderstanding? I would assume that based on their documentation, that if I assign a class to the result set, the class should be populated with the results? Now, how it goes on knowing which field to map to is a mystery to me and may very well be the reason why this doesn't work. I thought perhaps I needed to modify the constructor to do this mapping but I didn't see any documentation as to how I would go about doing that. I tried putting in a parameter for the constructor assuming it was an StdClass array but didn't seem to work.
Any clarifications would be great!
So it dawned on me to check the actual source code of the db_results function and I figured that it's due to the case of the query result columns. And it seems that CI defaults everything to UPPERCASE unless you specify it as lowercase in your query string.
So in conclusion, whatever the case of columns is in your query, should be the case of values in your Model!
Seems ridiculous though... I'll probably see if I can edit the core class to not be case-sensitive. Unless someone has better alternatives.
Hi
I am following the getting started guide for Codeigniterr given at http://www.ibm.com/developerworks/web/library/wa-codeigniter/
I have followed the instruction to create the front view and added controller to handle form submission. Ideally, when i submit the form, it should load the model class and execute the function to put details on the database, but instead it is just printing out the code of the model in the browser.
**Code of view (Welcome.php)**
----------------
<?php
class Welcome extends Controller {
function Welcome()
{
parent::Controller();
}
function index()
{
$this->load->helper('form');
$data['title'] = "Welcome to our Site";
$data['headline'] = "Welcome!";
$data['include'] = 'home';
$this->load->vars($data);
$this->load->view('template');
}
function contactus(){
$this->load->helper('url');
$this->load->model('mcontacts','',TRUE);
$this->mcontacts->addContact();
redirect('welcome/thankyou','refresh');
}
function thankyou(){
$data['title'] = "Thank You!";
$data['headline'] = "Thanks!";
$data['include'] = 'thanks';
$this->load->vars($data);
$this->load->view('template');
}
}
/* End of file welcome.php */
/* Location: ./system/application/controllers/welcome.php */
**Code of Model**
--------------
class mcontacts extends Model{
function mcontacts(){
parent::Model();
}
}
function addContact(){
$now = date("Y-m-d H:i:s");
$data = array(
'name' => $this->input->xss_clean($this->input->post('name')),
'email' => $this->input->xss_clean($this->input->post('email')),
'notes' => $this->input->xss_clean($this->input->post('notes')),
'ipaddress' => $this->input->ip_address(),
'stamp' => $now
);
$this->db->insert('contacts', $data);
}
**OUTPUT after clicking submit**
-----------------------------
class mcontacts extends Model{ function mcontacts(){ parent::Model(); } } function addContact(){ $now = date("Y-m-d H:i:s"); $data = array( 'name' => $this->input->xss_clean($this->input->post('name')), 'email' => $this->input->xss_clean($this->input->post('email')), 'notes' => $this->input->xss_clean($this->input->post('notes')), 'ipaddress' => $this->input->ip_address(), 'stamp' => $now ); $this->db->insert('contacts', $data); }
I have tried doing these things
1. Making all PHP codes executable
2. Change ownership of files to www-data
3. make permission 777 for whole of www
But, the code of model seems to be just printed ... PLEASE HELP
Just a few minor points that might help you:
In your controller, point the index method at the method you would like to call on that page. For example:
function index()
{
$this->welcome();
}
That will help keep things clean and clear, especially if anyone else comes in to work on the code with you later. I chose welcome because that's the name of your controller class and that will keep things simple.
In your model, this:
class mcontacts extends Model{
Should be:
class Mcontacts extends Model{
Capitalize those class names! That could be giving you the trouble you describe.
See here for more info on this: http://codeigniter.com/user_guide/general/models.html
Don't use camel case in your class or method names. This isn't something that will cause your code to fail, but it's generally accepted practice. See Codeigniter's PHP Style guide for more information on this: http://codeigniter.com/user_guide/general/styleguide.html
It's difficult to see with the formatting as it is, but do have an extra curly brace after the constructor method (mcontacts()) in the model? This would cause problems! Also although the code looks generally ok, there are probably better ways to use the framework especially if you do anything more complicated than what you've shown. For example, autoloading, form validation etc. Can I suggest you have a read of the userguide? It's very thorough and clear and should help you alot. http://codeigniter.com/user_guide/index.html