So I found a few problems already which says that you have to override getAuthPassword() to give custom name of password column from database. Tried putting this method with the same name as column in a database and didnt work. It still shoots this error: Undefined index: password.
This is the auth:
if (Auth::attempt(Input::only('user_displayName'), Input::get('user_password')))
Tried changing user_password to password both in form and controller nothing works.
So the question is if I have a column in a database called "user_password" is there a way to make Auth work?
P.S checked every older solution I found
EDIT
Structure of user table:
+======================+
| User |
+======================+
| user_id |
+----------------------+
| user_displayName |
+----------------------+
| user_fname |
+----------------------+
| user_lname |
+----------------------+
| user_email |
+----------------------+
| user_password |
+----------------------+
| created_at |
+----------------------+
| updated_at |
+----------------------+
tldr; You can name your password field anything you like, as long as your User model implements the interface correctly.
However you can't pass different array key to the Auth::attempt method - only password index can be there
First off you're doing it wrong - you need to pass an array of credentials as 1st param:
if (Auth::attempt(Input::only('user_displayName', 'user_password')))
Next, unfortunately Eloquent provider has hard-coded password array index in the code, so you can't pass user_password to the attempt method.
So this is what you need:
$credentials = Input::only('user_displayName');
$credentials['password'] = Input::get('user_password');
if (Auth::attempt($credentials))
// or simply rename the input in your form to password and:
if (Auth::attempt(Input::only('user_displayName', 'password')))
I have not tested this but I believe you just have to override a function in UserTrait.php although don't hack the source. In your models/User.php file add the following function.
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->user_password;
}
There's a static solution for situations when implementing the given interface is not enough (e.g. you have two password columns, a user password and a support password).
$qry = DB::table('users')
->where('email', $email)
->whereNotNull('support_password')
->first();
if(!empty($qry))
{
$check = Hash::check($password, $qry->support_password);
if ($check)
{
Auth::loginUsingId($qry->id, true);
}
}
Here is my way of changing the default email field of the Laravel login to 'email_address' without changing the vendor code.
I made a trait class that extends the vendor AuthenticatesUsers trait. And only extended the username method.
App\Http\Controllers\Auth\AuthenticatesLogins.php:
namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
trait AuthenticatesLogins
{
use AuthenticatesUsers {
AuthenticatesUsers::username as parentUsername;
}
/**
* Get the login username to be used by the controller.
*
* #return string
*/
public function username()
{
$this->parentUsername();
return 'email_address';
}
}
Now in App\Http\Controllers\Controller\LoginController.php:
class LoginController extends Controller
{
use AuthenticatesLogins; // <-- for custom login fields
// etc.
And... done! Hope this helps.
Related
I have a controller App/Controllers/Api/Orders.php
<?php
namespace App\Controllers\Api;
use CodeIgniter\RESTful\ResourceController;
class Orders extends ResourceController
{
protected $modelName = 'App\Models\Order';
protected $format = 'json';
public function getIndex()
{
return $this->respond($this->model->findAll());
}
public function delete($id = null)
{
return $this->respond(['test' => 123]);
}
}
When I'm trying to access /api/orders/ with GET request it works fine (so auto routing is working).
But when I'm trying to send a DELETE request: /api/orders/15 I get error 404. What am I doing wrong? The main source of confusion for me is that Codeigniter 4 RESTful documentation seems to talk about manual routes or legacy auto routing but not the improved version where you need to specify methodFunction (like getIndex).
Running php spark routes gives me the following:
+--------------+------------------+------+---------------------------------------+----------------+---------------+
| Method | Route | Name | Handler | Before Filters | After Filters |
+--------------+------------------+------+---------------------------------------+----------------+---------------+
| GET(auto) | api/orders | | \App\Controllers\Api\Orders::getIndex | | toolbar |
| DELETE(auto) | api/orders/[/..] | | \App\Controllers\Api\Orders::delete | <unknown> | <unknown> |
CodeIgniter version: 4.3.1
app/Config/Routes.php
$routes->setDefaultNamespace('App\Controllers');
$routes->setDefaultController('Auth');
$routes->setDefaultMethod('index');
$routes->setTranslateURIDashes(false);
$routes->set404Override();
$routes->get('/', 'Home::index');
app/Config/Filters.php
public array $globals = [
'before' => [
// 'honeypot',
// 'csrf',
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
];
Solution:
Beyond prefixing the Controller method with an HTTP verb i.e delete, you need to provide a 'Controller method name' as the suffix. I.e: httpVerbMethodName.
Instead of: ❌
public function delete($id = null)
{
// ...
}
Use this: ✅
public function deleteOrder($id = null)
{
// ...
}
Route endpoint:
DELETE: http://localhost:4599/api/orders/order/15
| DELETE(auto) | api/orders/order[/..] | | \App\Controllers\Api\Orders::deleteOrder | | toolbar
Reference:
URI Segments
The segments in the URL, in following with the Model-View-Controller approach, usually represent:
example.com/class/method/ID
The first segment represents the controller class that should be invoked.
The second segment represents the class method that should be called.
The third, and any additional segments, represent the ID and any variables that will be passed to the controller.
Consider this URI:
example.com/index.php/helloworld/hello/1
In the above example, when you send a HTTP request with GET method, Auto Routing would attempt to find a controller named App\Controllers\Helloworld and executes getHello() method with passing '1' as the first argument.
Note
A controller method that will be executed by Auto Routing (Improved) needs HTTP verb (get, post, put, etc.) prefix like getIndex(), postCreate().
See Auto Routing in Controllers for more info.
I am trying to change the home page text languages from database using Pivot tables relationship.
All works fine and I am getting no errors but the word login is not shown.
I have 3 tables
languages
id | name | sign | default_back
1 | English | en | 1
2 | Russian | ru | 0
features
id | code | type
70 | login | 0
feature_language
id | feature_id | language_id | text
1 | 70 | 2 | Ru Login
2 | 70 | 1 | Login
Language Model
<?php // Languages Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Language extends Model
{
public function features(){
return $this->belongsToMany('App\Feature');
}
public function getDefaulLanguage(){
return $this->default_back;
}
public function featureTranslation($code){
return $this->features()->where('code', '=', $code)->first();
}
}
?>
Features Model
<?php // Features Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Feature extends Model
{
}
?>
Index controller
<?php // index controller
namespace App\Http\Controllers
use App\Language;
class HomeController extends Controller {
public function index(){
$languages = Language::get();
$language_default = Language::where('default_back', '>', '0')->first();
return view('index')->with('languages', $languages)
->with('language_default', $language_default);
}
}
?>
Index View
<?php
<h1>login : {{ $language_default->featureTranslation("login")->text}}</h1>
?>
Any help will be appreciated ))
First, you need to define your pivot table column name in your model.
Language Model:
public function features()
{
return $this->belongsToMany('App\Feature')->withPivot('text');
}
In you Index View, you need to access data like following:
{{ $language_default->featureTranslation("login")->pivot->text }}
Since your pivot table has extra column text, you need to define the column name while defining the relationship
According to Official Doc
By default, only the model keys will be present on the pivot object. If your pivot table contains extra attributes, you must specify them when defining the relationship:
public function features(){
return $this->belongsToMany('App\Feature')->withPivot('text');
}
And for Retrieving the intermediate table column, you need to use the pivot attribute.
Language Model
<?php // Languages Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Language extends Model
{
public function features(){
return $this->belongsToMany('App\Feature', 'feature_language', 'feature_id', 'language_id')->withPivot('text');
}
public function getDefaulLanguage(){
return $this->default_back;
}
public function featureTranslation($code){
return $this->features()->where('code', $code)->first()->pivot->text;
}
}
?>
Index View
<?php
<h1>login : {{ $language_default->featureTranslation("login") }}</h1>
?>
It's basically what I need: if any model row on my project is created, updated or removed, I need call a callback to work with this model row. I'll use that to create a changelog in other model, that will not trigger this global event to avoid looping.
I tried set it like that:
Model::creating($callback);
But it doesn't worked. Work only if I set it directly to all models that I have. It's bad because I need specific one-by-one. If I create a new one, I need specify it manually. Example:
User::creating($callback);
Company::creating($callback);
...
Any change will be logged on another model, called Log. It'll not trigger this $callback, because it'll register on table logs (managed by Log) each change in other models. Something like:
$user = new User;
$user->name = "John";
$user->age = 18;
$user->save();
$user->name = "John Doe";
$user->save();
$user->delete();
$user->restore();
$user->forceDelete();
I'll register something like:
id | object_type | object_id | event_type | object_data
.. App\User 1 created { name: John, age: 18 }
.. App\User 1 updated { name: John Doe }
.. App\User 1 trashed null
.. App\User 1 restored null
.. App\User 1 removed null
With Laravel 5.1
BaseTrait Trait:
trait BaseTrait
{
public static function bootBaseTrait()
{
static:creating(function($item) {
....
});
static:updating(function($item) {
....
});
}
// It binds the events in bootBaseTrait to any subclass
// Base run first, then run your subclass's events if you defined on the subclass
}
Base Model:
use XX\XX\BaseTrait;
use Illuminate\Database\Eloquent\Model;
class Base extends Model
{
use BaseTrait;
}
XXX Model:
use XX\XX\Base;
class XXX extends Base
{
}
How does this work?
You can see /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
protected static function bootTraits()
The method tries to call bootTraitName when you use a trait!
All Eloquent model events have the format eloquent.event:ClassName.
You could subscribe to the eloquent.* event.
Event::listen('eloquent.*', function($model) {});
Then you can check if the current model is a log model (in which case you'd return true straight out of it to avoid infinite recursion), otherwise, save the change to your log model.
I was wondering if anyone else came across this problem. I'm going through Jeffrey Way's book on testing in Laravel and I'm on the chapter which explains how to test controllers.
When I follow the examples from the book - I get the message:
Failed asserting that Illuminate\Http\Response Object (...) is an
instance of class "Illuminate\Http\RedirectResponse".
My test is as follow:
public function testStoreFails()
{
$input = ['title' => ''];
$this->mock
->shouldReceive('create')
->once()
->with($input);
$this->app->instance('Post', $this->mock);
$this->post('posts', $input);
$this->assertRedirectedToRoute('posts.create');
$this->assertSessionHasErrors(['title']);
}
And the very simple method in the controller (just to test this specific scenario):
public function create()
{
$input = Input::all();
$validator = Validator::make($input, ['title' => 'required']);
if ($validator->fails()) {
return Redirect::route('posts.create')
->withInput()
->withErrors($validator->messages());
}
$this->post->create($input);
return Redirect::route('posts.index')
->with('flash', 'Your post has been created!');
}
From what I can see the AssertionsTrait::assertRedirectedTo checks for instance of Illuminate\Http\RedirectResponse
/**
* Assert whether the client was redirected to a given URI.
*
* #param string $uri
* #param array $with
* #return void
*/
public function assertRedirectedTo($uri, $with = array())
{
$response = $this->client->getResponse();
$this->assertInstanceOf('Illuminate\Http\RedirectResponse', $response);
$this->assertEquals($this->app['url']->to($uri), $response->headers->get('Location'));
$this->assertSessionHasAll($with);
}
/**
* Assert whether the client was redirected to a given route.
*
* #param string $name
* #param array $parameters
* #param array $with
* #return void
*/
public function assertRedirectedToRoute($name, $parameters = array(), $with = array())
{
$this->assertRedirectedTo($this->app['url']->route($name, $parameters), $with);
}
which should work just fine as the Redirect facade resolves to the Illuminate\Routing\Redirector and its route() method calls createRedirect(), which returns the instance of the Illuminate\Http\RedirectResponse - so not quite sure what's causing it.
UPDATE:
Just checked the code again and it looks like the problem is within AssertionsTrait::assertRedirectedTo() method. The call to $this->client->getResponse() returns instance of Illuminate\Http\Response instead of Illuminate\Http\RedirectResponse - hence the $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $response) call fails. But I'm still not sure why - I'm extending the TestCase which is suppose to take care of all environment setup etc. Any idea?
Posting comment as answer:
Since you're working with a resourceful object, Laravel automatically creates some routes for you, namely:
+-----------------------------+---------------+-------------------------+
| URI | Name | Action |
+-----------------------------+---------------+-------------------------+
| GET|HEAD posts | posts.index | PostsController#index |
| GET|HEAD posts/create | posts.create | PostsController#create |
| POST posts | posts.store | PostsController#store |
| GET|HEAD posts/{posts} | posts.show | PostsController#show |
| GET|HEAD posts/{posts}/edit | posts.edit | PostsController#edit |
| PUT posts/{posts} | posts.update | PostsController#update |
| PATCH posts/{posts} | | PostsController#update |
| DELETE posts/{posts} | posts.destroy | PostsController#destroy |
+-----------------------------+---------------+-------------------------+
As you can see, a POST request to /posts (as you do it in your test) triggers the store() method on your PostsController, not the create() method which you assumed to be under test.
create and store as well as edit and update can be confusing sometimes. Here's the difference:
create() - show the form for creating a new resource
store() - actually create the resource from the posted data
edit($id) - show the form for editing the specified resource
update($id) - actually update the resource with the posted data
users transactions tasks
+----+--------+ +----+---------------+ +----+--------+
| id | name | | id | name | | id | name |
+----+--------+ +----+---------------+ +----+--------+
| 1 | User 1 | | 1 | Transaction 1 | | 1 | Task 1 |
| 2 | User 2 | | 2 | Transaction 2 | | 2 | Task 2 |
+----+--------+ +----+---------------+ +----+--------+
templates transaction_user task_transaction
+----+---------------+ +---------+----------------+ +---------+----------------+
| id | name | | user_id | transaction_id | | task_id | transaction_id |
+----+---------------+ +---------+----------------+ +---------+----------------+
| 1 | Template 1 | | 1 | 1 | | 1 | 1 |
| 2 | Template 2 | | 2 | 2 | +---------+----------------+
+----+---------------+ +---------+----------------+
task_template
+---------+-------------+
| task_id | template_id |
+---------+-------------+
| 2 | 2 |
+---------+-------------+
Motive:
If there is a logged in user, say user with the ID 1, and he/she wants to see a task (say task with the ID 1) then i want to make sure that the task with ID 1 Belongs to the user before i let him view it. Also i need someway to show user all tasks that belong to him. Task is just one model.. i need to handle this for all models. I have shared my code below, am i trying too hard?
I may have omitted some details here so please feel free to ask questions.
Thanks.
Code
<?php namespace SomeProject\Repositories;
use User;
use Account;
use Task;
use Document;
use Transaction;
use Property;
use DB;
use Respond;
abstract class DbRepository
{
/**
* The many to many relationships are handeled using pivot tables
* We will use this array to figure out relationships and then get
* a particular resource's owner / account
*/
public $pivot_models = array(
'Task' => array(
'Transaction' => 'task_transaction'
),
'Transaction' => array(
'User' => 'transaction_user'
),
'Document' => array(
'Property' => 'document_property',
'Task' => 'document_task',
'Message' => 'document_message'
)
);
public $entity_ids;
public function getOwnersByEntity(array $ids, $entity)
{
$this->entity_ids = [];
$user_ids = [];
$entity = ucfirst(strtolower($entity)); // arrays keys are case sensitive
if( $this->getPivotIds($ids, $entity) )
{
foreach ($this->entity_ids as $entity_name => $entity_ids_arr)
{
$entity_name_lowercase = strtolower($entity_name);
if($entity_name_lowercase != 'user')
{
$user_ids_from_entity = $entity_name::whereIn('id', $entity_ids_arr)
->lists('user_id');
}
else
{
// We already have the IDs if the entity is User
$user_ids_from_entity = $entity_ids_arr;
}
array_push($user_ids, $user_ids_from_entity);
}
$merged_user_ids = call_user_func_array('array_merge', $user_ids);
return array_unique($merged_user_ids);
}
else
{
return $entity::whereIn('id', $ids)->lists('user_id');
}
}
public function getPivotIds(array $ids, $entity)
{
$entity_lowercase = strtolower($entity);
if( array_key_exists($entity, $this->pivot_models) )
{
// Its a pivot model
foreach ($this->pivot_models[$entity] as $related_model => $table) // Transaction, Template
{
$related_model_lowercase = strtolower($related_model);
$this->entity_ids[$related_model] = DB::table($table)
->whereIn($entity_lowercase . '_id', $ids)
->lists($related_model_lowercase . '_id');
if( $this->getPivotIds($this->entity_ids[$related_model], $related_model) )
{
unset($this->entity_ids[$related_model]);
}
}
return true;
}
return false;
}
}
To check if given model is related to another one, which is what you want if I get you right, all you need is this tiny method making the most of Eloquent:
(Implement it in BaseModel, Entity or a scope, whatever suits you)
// usage
$task->isRelatedTo('transactions.users', $id);
// or
$template->isRelatedTo('tasks.transactions.users', Auth::user());
// or any kind of relation:
// imagine this: User m-m Transaction 1-m Item m-1 Group
$group->isRelatedTo('items.transaction.users', $id);
The magic happens here:
/**
* Check if it is related to any given model through dot nested relations
*
* #param string $relations
* #param int|\Illuminate\Database\Eloquent\Model $id
* #return boolean
*/
public function isRelatedTo($relations, $id)
{
$relations = explode('.', $relations);
if ($id instanceof Model)
{
$related = $id;
$id = $related->getKey();
}
else
{
$related = $this->getNestedRelated($relations);
}
// recursive closure
$callback = function ($q) use (&$callback, &$relations, $related, $id)
{
if (count($relations))
{
$q->whereHas(array_shift($relations), $callback);
}
else
{
$q->where($related->getQualifiedKeyName(), $id);
}
};
return (bool) $this->whereHas(array_shift($relations), $callback)->find($this->getKey());
}
protected function getNestedRelated(array $relations)
{
$models = [];
foreach ($relations as $key => $relation)
{
$parent = ($key) ? $models[$key-1] : $this;
$models[] = $parent->{$relation}()->getRelated();
}
return end($models);
}
Hey, but what's going on there?
isRelatedTo() works like this:
check if passed $id is a model or just an id, and prepares $related model and its $id for use in the callback. If you don't pass an object then Eloquent needs to instantiate all the related models on the $relations (relation1.relation2.relation3...) chain to get the one we are interested in - that's what happens in getNestedRelated(), pretty straightforward.
then we need to do something like this:
// assuming relations 'relation1.relation2.relation3'
$this->whereHas('relation1', function ($q) use ($id) {
$q->whereHas('relation2', function ($q) use ($id) {
$q->whereHas('relation3', function ($q) use ($id) {
$q->where('id', $id);
});
});
})->find($this->getKey());
// returns new instance of current model or null, thus cast to (bool)
since we don't know how deeply the relation is nested, we need to use recurrency. However we pass a Closure to the whereHas, so we need to use little trick in order to call itself inside its body (in fact we don't call it, but rather pass it as $callback to the whereHas method, since the latter expects a Closure as 2nd param) - this might be useful for those unfamiliar Anonymous recursive PHP functions:
// save it to the variable and pass it by reference
$callback = function () use (&$callback) {
if (...) // call the $callback again
else // finish;
}
we also pass to the closure $relations (as an array now) by reference in order to unshift its elements, and when we got them all (meaning we nested whereHas), we finally put the where clause instead of another whereHas, to search for our $related model.
finally let's return bool
There's really no easy nor canonical way, but here's a raw example of what I'd try to do.
class Entity extends Eloquent {
public function isRelatedTo($instance, $through)
{
$column = $instance->joiningTable($through) . '.' . $instance->getForeignKey();
$query = DB::table('');
this->buildJoin($query, $instance, $through);
return $query->where($column, '=', $instance->getKey())->exists();
}
public function relatesToMany($related, $through)
{
$that = $this;
$related = new $related;
return $related->whereIn($related->getKeyName(), function($query) use ($that, $related, $through) {
$that->buildJoin($query, $related, $through);
})->get();
}
protected function buildJoin($query, $related, $through)
{
$through = new $through;
$this_id = $this->getForeignKey();
$related_id = $related->getForeignKey();
$through_id = $through->getForeignKey();
$this_pivot = $this->joiningTable($through);
$related_pivot = $related->joiningTable($through);
$query->select($related_pivot . '.' . $related_id)->from($related_pivot)
->join($this_pivot, $related_pivot . '.' . $through_id, '=', $this_pivot . '.' . $through_id)
->where($this_pivot . '.' . $this_id, '=', $this->getKey());
}
}
Then, for your use case:
class User extends Entity {
public function isOwnerOf($task)
{
return $this->isRelatedTo($task, 'Transaction');
}
public function tasks()
{
return $this->relatesToMany('Task', 'Transaction');
}
}
Disclaimer: the code has not been tested.
Note that, in this very simplified example, relatesToMany directly returns a Collection. To have more advantages, it could instead return an instance of your own extension of Eloquent's Relation class - that takes longer to implement, clearly.
It shouldn't be difficult to add support for multiple intermediate entities; you could likely expect the $through argument to possibly be an array and then build the multi-join query accordingly.
If this is a Laravel project, then yes, you're trying far too hard.
If you're going to use Laravel, it's recommended that you use the features provided to you with Laravel, which are namely it's ORM, Eloquent, and it's bundled Schema tool. I'd recommend that you view Laravel's Getting Started page in their documentation, so that you can set your project up correctly to use Eloquent.
It would also be beneficial if you read up on the basics of how Eloquent handles relations in their models, as they do all of the work that you're trying to do.