Left Join with multiple condition Yii2 error - yii2

according to this thread Yii2 - left join on multiple condition
i create sql function in Mfwpcontroller like this :
use Yii;
use app\models\Mfwp;
use app\models\User;
use app\models\MfwpSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use yii\web\Response;
public function actionLists($id)
{
$posts = Mfwp::find()
->joinWith(['user' => function (ActiveQuery $query) {
return $query
->andOnCondition(['=', 'user.id', 'mfwp.id_mfwp'])
->andWhere(['=', 'mfwp.id', $id]);
}])
->asArray()
->all();
Yii::$app->response->format = 'json';
return $posts;
}
and this is my model in mfwp
public function getMfwp()
{
return $this->hasOne(User::className(), ['id' => 'id_mfwp']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getSmWps()
{
return $this->hasMany(SmWp::className(), ['id_sm_wp' => 'id']);
}
when i try to execute by accessing http://bla-bla.oke/mfwp/lists?id=1, its show an error
Invalid Argument – yii\base\InvalidArgumentException
app\models\Mfwp has no relation named "user".
↵
Caused by: Unknown Method – yii\base\UnknownMethodException
Calling unknown method: app\models\Mfwp::getuser()
and this is my database structure https://imge.to/i/vE2nWC
so whats wrong with my code..thank you for your advice

Related

Laravel API resources, get only the latest occurrence from a collection

Hi I am developing an api in laravel for an online course system. In this scheme I have a standard table for users, a table for courses and a pivot table that relates courses and users according to which they sign up for each course.
This last table also carries the events related to the progress of each user in the course, that is, Subscribed, Progress x%, Completed, Approved, so that each user can have multiple entries in the course_users table.
So far everything is clear and everything is fine, the point is that at a certain moment I need to return a json object with the information of the courses and pointed users, this can be clearly achieved using resource collection in the following way:
CourseCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
use App\Http\Resources\CargoResource;
class CourseCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => CourseResource::collection($this->collection),
'links' => [
'self' => 'link-value',
],
];
}
}
CourseResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title'=> $this->title,
'description'=> $this->description,
'price'=> $this->price,
'users' => CourseUserResource::collection($this->whenLoaded('users'))
];
}
}
CourseUserResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseUserResource extends JsonResource
{
public function toArray($request)
{
return [
'course_id'=> $this-> course_id,
'user_id'=> $this->user_id,
'event'=> $this->event,
'event_date' => $this->created_at->format('Y-m-d')
];
}
}
The problem to be solved is that with this scheme I obtain a collection of events for each user and course, but what I am needing is only the last event of each user, to know what their status is in relation to the course.
I am analyzing the option to perform the query by sql and then manually build the json object, but I would like to have a "laravel style" solution
Any ideas will be welcome!
Added Models & Controllers for clarification
class Course extends Model
{
protected $fillable = [
'title',
'slug',
'description',
'course_category_id',
'price',
'published'
];
...
public function history()
{
return $this->belongsToMany(CourseUser::class, 'course_id', 'id')->latest();
}
public function scopePublished($query)
{
return $query->where('published', 1);
}
}
class CourseUser extends Model
{
protected $fillable = [
'course_id',
'user_id',
'event'
];
}
class SearchController extends ApiController
{
public function search(Request $request)
{
$results = Course::with('history')
->published
->where('title', 'like', $request->filter['title'])
->where('description', 'like', $request->filter['description'])
->get();
if (! count($results) > 0) {
return $this->sendResponse(
__('No results for your query.'),
[
'code'=>204,
'message'=> __('There are no results for your search criteria.')
],
204
);
}
return new CourseCollection($results);
}
}

Laravel API pass to Vuejs query join nested transaction and order Ttbles

I am creating a shopping cart and i have 3 tables here as you can see in the image. first is the products, orders and last the transactions, all products that has been ordered will be place in order table along with transaction id whoever belongs to that transaction, and transaction table would record the total balance and change and i want to fetch it in database using laravel query builder and convert it to json like in the 2nd image below, i hope my explanation is not that confusing. Thanks in advance guys :)
Follow the steps below:
1.Creating models and adding relationship between them.
You can create models for transaction , order & product using phpartisan.
php artisan make:model Transaction
php artisan make:model Order
php artisan make:model Product
configure them according to your mysql tables structure.
Adding relationships.
Transaction Model
public function order(){
return $this->hasOne(Order::class, 'transaction_id', 'id');
}
Order Model
public function transaction(){
return $this->belongsTo(Transaction::class);
}
public function products(){
return $this->hasMany(Product::class, 'id', 'product_id');
}
Product Model
public function order(){
return $this->belongsTo(Order::class);
}
2.Creating a controller to handle the results.
You can create a controller using phpartisan.
php artisan make:controller TransactionController -r
Setting up our controller.
public function TransactionDetails($transactionID){
$transaction = Transaction::where('id',$transactionID)->firstOrFail();
$order = $transaction->order;
$products = $order->products;
$result = array();
$result['transaction'] = $transaction;
$result['transaction']['order'] = $order;
$result['transaction']['order']['products'] = $products;
return response()->json($result);
}
This should work and give you your desired output , if any error occurs let me know.
generating models.
php artisan make:model Transaction
php artisan make:model Order
php artisan make:model Product
TransactionModel
public function orders()
{
return $this->hasMany(Order::class, 'transaction_id', 'id');
}
ProductModel
public function orders()
{
return $this->hasMany(Order:class, 'product_id', 'id');
}
OrderMode
public function product()
{
return $this->belongsTo(Product::class, 'product_id', 'id');
}
public function transaction()
{
return $this->belongsTo(Transaction::class, 'transaction_id', 'id');
}
You can create a ResourceCollection to generate your desired JSON output.
php artisan make:resource TransactionCollection
this will create a ResourceCollecion in app\Http\Resource\TransactionCollection
TransactionCollection
class TransactionCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$transactions = [];
foreach($this->collection as $transaction) {
$jsonTransaction = [
'id' => $transaction->id,
'date' => $transaction->transaction_date,
'total' => $transaction->transaction_total,
'change' => $transaction->transaction_change,
'orders' => []
];
foreach($transaction->orders as $order) {
$product = $order->product;
array_push($jsonTransaction['orders'], [
'id' => $order->id,
'product_name' => $product->product_name,
'category' => $product->product_category,
'price' => $order->order_price,
'quantity' => $order->order_quantity
]);
}
array_push($transactions, $jsonTransaction);
}
}
}
TransactionController
public function getAllTransactions()
{
// this will return your desired json result as you posted in the question.
return TransactionCollection::make(Transaction::all());
}

(1/1) FatalErrorException Call to a member function all() on null in laravel 5.4

4 and i have a form when submitted i want to validate its fields, what happened is when i submit the form this is what it gets
(1/1) FatalErrorException
Call to a member function all() on null
This is my code below
$validator = app('validator')->make($this->request->all(),[
'postTitle' => 'required',
'postContent' =>'required']);
In laravel 5.2 this validator works well but in laravel 5.4 it returns null
can someone help me figured this thing out?
Any help is muchly appreciated. TIA
this is my full code
<?php
namespace App\Repositories;
use App\Repositories\Contracts\addPostRepositoryInterface;
use Validator;
use Illuminate\Http\Request;
use DB;
use Session;
use Hash;
class addPostRepository implements addPostRepositoryInterface{
protected $request;
// Intialize request instance
public function __contruct(Request $request){
$this->request = $request;
}
public function addPosts(Request $request){
//validate posts
echo "test";
$validator = Validator::make($request->all(), [
'postTitle' => 'required',
'postContent' =>'required',
]);
//if validation fails return error response
if($validator->fails())
return redirect()->route('get.addPost')->withErrors($validator)->withInput();
try{
}catch(\Exception $e){
return redirect()->route('get.addPost')->withErrors(["error"=>"Could not add details! Please try again."])->withInput();
}
}
public function postCreate($screen){
switch($screen){
case 'add':
return $this->addPosts($screen);
break;
}
}
//getAddPost View
public function getAddPost(){
return view('addPost');
}
}
Seems an issue with the method injection (in the constructor) or something.
You may try creating the request object on the local(addPosts()) function.
Please try below alternative solution.
<?php
namespace App\Repositories;
use App\Repositories\Contracts\addPostRepositoryInterface;
use Validator;
use DB;
use Session;
use Hash;
class addPostRepository implements addPostRepositoryInterface{
public function addPosts(Request $request){
//validate posts
$reqeust = new \Illuminate\Http\Request;
$validator = Validator::make($request->all(), [
'postTitle' => 'required',
'postContent' =>'required',
]);
//if validation fails return error response
if($validator->fails())
return redirect()->route('get.addPost')->withErrors($validator)->withInput();
try{
}catch(\Exception $e){
return redirect()->route('get.addPost')->withErrors(["error"=>"Could not add details! Please try again."])->withInput();
}
}
public function postCreate($screen){
switch($screen){
case 'add':
return $this->addPosts($screen);
break;
}
}
//getAddPost View
public function getAddPost(){
return view('addPost');
}
// REST OF THE CODE GOES HERE...
}
Given the information you gave, I will demonstrate you how to validate a request properly in Laravel 5.4
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'postTitle' => 'required',
'postContent' =>'required',
]);
if ($validator->fails()) {
return redirect('your.view')
->withErrors($validator)
->withInput();
}
// Store the blog post...
}
This will successfully validate the request for you wherever need be. If the validation fails, you will be forwarded to your view with the according errors.
Make sure you use Validator; on top of your file.
For more information, you can read up on https://laravel.com/docs/5.4/validation

Restricting controller action to creator of post in Yii2

Is there an easy way to restrict a controller action to the owner/creator of the post without using full blown RBAC?
Right now I'm doing this for every controller:
public function actionUpdate( $id ) {
$model = $this->findModel( $id );
if ( $model->user_id != Yii::$app->user->identity->id ) {
throw new NotFoundHttpException( 'The requested page does not exist.' );
}
}
But I think there must be a better way to restrict certain controllers to the users who created the $model thats being edited.
1) The recommended way is to use RBAC and rules. It's covered well in official docs in according dedicated section.
Example of rule that checks if author id matches current user id passed via params:
namespace app\rbac;
use yii\rbac\Rule;
/**
* Checks if authorID matches user passed via params
*/
class AuthorRule extends Rule
{
public $name = 'isAuthor';
/**
* #param string|integer $user the user ID.
* #param Item $item the role or permission that this rule is associated with
* #param array $params parameters passed to ManagerInterface::checkAccess().
* #return boolean a value indicating whether the rule permits the role or permission it is associated with.
*/
public function execute($user, $item, $params)
{
return isset($params['post']) ? $params['post']->createdBy == $user : false;
}
}
Then you need to tie it with existing permission (can be done in migration or with extensions):
$auth = Yii::$app->authManager;
// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);
// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);
// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);
Then you can check if you user can update post like this:
use yii\web\ForbiddenHttpException;
use Yii;
public function actionUpdate($id)
{
$model = $this->findModel($id);
if (!Yii::$app->user->can('updatePost', ['post' => $model])) {
throw new ForbiddenHttpException('You are not allowed to edit this post');
}
...
}
Also note that in case you found model first and user has no access to edit it, logically it's better to throw 403 Forbidden exception rather than 404, since it's found, but not allowed for editing.
Don't forget to include rule like that in AccessControl behavior:
[
'allow' => true,
'actions' => ['update'],
'roles' => ['#'],
],
It means that update action of this controller can be only accessed by authorized users excluding guests.
2) If for some reason you don't want to use RBAC, you can use your approach:
use yii\web\ForbiddenHttpException;
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->user_id != Yii::$app->user->id ) {
throw new ForbiddenHttpException('You are not allowed to edit this post.');
}
...
}
To improve this you can abstract from this check by moving this logic to helper method:
namespace app\posts\components;
use Yii;
class PostPermission
{
/**
* #param $model Post
* #return boolean
*/
public static function allowedToUpdate($model)
{
return $model->user_id = Yii:$app->user->id;
}
}
Then call it like that:
use app\posts\components\PostPermission;
use yii\web\ForbiddenHttpException;
if (!PostPermission::allowedToUpdate($model) {
throw new ForbiddenHttpException('You are not allowed to edit this post.');
}
It's just an example, method doesn't have to be static, you can construct instance using $model.
You can just directly create method in Post model, but it's better to not pollute model with such logic.
3) Another alternative that I can advise is to restrict scope initially to current user when finding model:
use yii\web\NotFoundHttpException;
/**
* #param integer $id
* #return Post
* #throws NotFoundHttpException
*/
protected function findModel($id)
{
$model = Post::find(['id'=> $id, 'user_id' => Yii::$app->user->id])->one();
if ($model) {
return $model;
} else {
throw new NotFoundHttpException('This post does not exist.');
}
}
This can be improved for site administrators:
use yii\web\NotFoundHttpException;
/**
* #param integer $id
* #return Post
* #throws NotFoundHttpException
*/
protected function findModel($id)
{
$query = Post::find()->where(['id' => $id]);
if (!Yii::$app->user->is_admin) { // replace with your own check
$query->andWhere(['user_id' => Yii::$app->user->id]);
}
$model = $query->one();
if ($model) {
return $model;
} else {
throw new NotFoundHttpException('This post does not exist.');
}
}
Then you only write:
public function actionUpdate($id)
{
$model = $this->findModel($id);
...
}
That way in both cases (model not found and not allowed for editing by current user), 404 Not Found exception will be raised. From other side, nothing is wrong with that, because technically for this user this model does not exist (since he is not author of it).
We can use
AccessControlFilter
for restricting controller action instead of RBAC. This below code will give access to the actionUpdate if it is only pass the denyCallback.
use yii\filters\AccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['update','delete'],
'rules' => [
[
'actions' => ['update'],
'allow' => false,
'denyCallback' => function ($rule, $action) { //PHP callable that should be called when this rule will deny the access.
//Write your logic here to deny the action
throw new \Exception('You are not allowed to access this page');
}
],
],
],
];
}
public function actionUpdate()
{
return $this->render('update');
}
}
For your reference https://github.com/yiisoft/yii2/blob/master/docs/guide/security-authorization.md

Yii2, Model find() with custom attribute

I want to pull the model data with custom attribute that assigned in a function in model.
Example)
class Test extends ActiveRecord
{
public static function tableName()
{
return '{{%test}}';
}
public function rules()
{
//....
}
public function attributeLabels()
{
return [
'id' => 'ID',
'first_name' => 'First Name',
'last_name' => 'Last Name',
];
}
public function getFullName()
{
$fullName = $this->first_name.' '.$this->last_name;
return $fullName;
}
}
Test::find().with('fullName') => it doesn't work
How can I get all the data with fullname attribute?
with is for relations. You can get fullname attribute just by calling $model->fullName. Actually fullName is not an attribute, yii2 utilise php's magic method __get() to get it from getFullName() method.
Example:
$model = Test::findOne($id);
echo $model->fullName;
Example 2:
$models = Test::find()->all();
foreach($models as $model)
{
echo $model->fullName;
}
Also consider using of fields/extraFields methods if you want use your models as arrays instead of objects