I am building a test API. I have created a Controller Page which extends from yii\rest\Controller. Actions needs to send a response.
To access actions in this controller, a service_id value needs to be posted. If present I need to evaluate if that service_id exists, if it is active and belongs to the user logged in. If validation fails, I need to send a response.
I am trying to do it using beforeAction(), but the problem is that return data is used to validate if action should continue or not.
So my temporary solution is saving service object in a Class attribute to evaluate it in the action and return response.
class PageController extends Controller
{
public $service;
public function beforeAction($action)
{
parent::beforeAction($action);
if (Yii::$app->request->isPost) {
$data = Yii::$app->request->post();
$userAccess = new UserAccess();
$userAccess->load($data);
$service = $userAccess->getService();
$this->service = $service;
}
return true;
}
public function actionConnect()
{
$response = null;
if (empty($this->service)) {
$response['code'] = 'ERROR';
$response['message'] = 'Service does not exist';
return $response;
}
}
}
But I can potentially have 20 actions which require this validation, is there a way to return the response from the beforeAction method to avoid repeating code?
You can setup response in beforeAction() and return false to avoid action call:
public function beforeAction($action) {
if (Yii::$app->request->isPost) {
$userAccess = new UserAccess();
$userAccess->load(Yii::$app->request->post());
$this->service = $userAccess->getService();
if (empty($this->service)) {
$this->asJson([
'code' => 'ERROR',
'message' => 'Service does not exist',
]);
return false;
}
}
return parent::beforeAction($action);
}
maybe paste in beforeAction after $this->service = $service;
if (empty($this->service)) {
echo json_encode(['code' => 'ERROR', 'message' => 'Service does not exist']);
exit;
}
Related
I have opted out of using Laravel's built in User Authentication due to my application's requirements. We rely on a Third Party SSO to authenticate our users, and I was unable to get Socialite to work with their SSO, so I am having to custom build a Controller to handle the authentication process. The Controller is performing b-e-a-utifully up until the part when I need to redirect the user from the Callback Route & Controller to the Member Route & Controller. It won't redirect. Period. I have tried every method I know how to redirect to another route from within the controller and it will not work.
Here is my custom AuthController for Laravel 5.3:
<?php
namespace App\Http\Controllers;
use App\User;
use Curl\Curl;
use App\Http\Controllers\PhealController as Pheal;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Routing\Redirector;
class AuthController extends Controller
{
protected $curl;
private $data;
public function __construct ()
{
$this->curl = new Curl();
$this->pheal = new Pheal();
$this->data = [];
}
public function sendToSSO()
{
$url = env('EVE_SSO_LOGIN')."?response_type=code&redirect_uri=".env('EVE_CALLBACK_URL')."&client_id=".env('EVE_CLIENT_ID')."&scope=".env('EVE_SCOPES');
return redirect($url);
}
public function handleCallback(Request $request)
{
$this->curl->setHeader('Authorization', "Basic ". base64_encode(env('EVE_CLIENT_ID').":".env('EVE_SECRET')));
$this->curl->setHeader('Content-Type', "application/x-www-form-urlencoded");
$this->curl->setHeader('Host', "login.eveonline.com");
$this->curl->post('https://login.eveonline.com/oauth/token', [
'grant_type' => 'authorization_code',
'code' => $request->code
]);
$response = $this->curl->response;
if (isset($response->error)) {
throw new \Exception($response->error_description);
}
$this->data = [
'accessToken' => $response->access_token,
'refreshToken' => $response->refresh_token
];
$this->verifyToken();
}
public function verifyToken ()
{
$this->curl->setHeader('User-Agent', "David Douglas ddouglas#douglaswebdev.net");
$this->curl->setHeader('Authorization', "Bearer ". $this->data['accessToken']);
$this->curl->setHeader('Host', "login.eveonline.com");
$this->curl->get('https://login.eveonline.com/oauth/verify');
$response = $this->curl->response;
if (isset($response->error)) {
throw new \Exception($response->error_description);
}
$this->data['characterID'] = $response->CharacterID;
$this->data['characterName'] = $response->CharacterName;
$this->data['accessTokenExpire'] = $response->ExpiresOn;
try {
$characterInfo = $this->pheal->call('eve', 'CharacterInfo', ['characterID' => $this->data['characterID']])['result'];
} catch (\Exceoption $e) {
abort(404);
}
if (!isset($characterInfo['allianceID'])) {
abort(403, "Care Factor Alliance Members Only. Sorry :-(");
}
if ($characterInfo['allianceID'] !== env('CF-ALLIANCE-ID')) {
abort(403, "Care Factor Alliance Members Only. Sorry :-(");
}
$this->data['corporationID'] = $characterInfo['corporationID'];
$this->data['corporation'] = $characterInfo['corporation'];
$user = User::find($this->data['characterID']);
if ($user) {
$this->updateUserAndLogin($user);
} else {
$this->createNewUserAndLogin();
}
}
private function getData()
{
return $this->data;
}
public function createNewUserAndLogin()
{
dd('To be Created');
}
public function updateUserAndLogin($user)
{
$user->corporationID = $this->data['corporationID'];
$user->corporation = $this->data['corporation'];
$user->accessToken = $this->data['accessToken'];
$user->refreshToken = $this->data['refreshToken'];
$user->accessTokenExpire = $this->data['accessTokenExpire'];
$user->save();
//Auth::login($user);
return redirect('member/dashboard/');
}
}
I have also tried:
return redirect()->route('member.dashboard');
With no luck.
You mean the $this->createNewUserAndLogin()? Maybe trying return $this->updateUserAndLogin($user); and return $this->verifyToken(); so you return the response on the main method of the route?
Hi i want to remove some data. I do view where i want to delete aditional data. This is my controller veiew becouse i want to make there button where i can delete data:
public function actionView($id) {
return $this->render('view', [
'model' => $this->findModel($id),
'userDate'=>$this->findData($id)
]);
}
public function actionDelet($id) {
$this->findData($id)->delete();
return $this->redirect(['index']);
}
public function findData($id){
if (($model = Profile::findOne($id)) !== null) {
$id=$model->Rel_UserData;
$user = UserData::find()->where(['Id' => $id])->one();
return $user;
} else {
throw new NotFoundHttpException('The requested page does not st.');
}
}
I guess solution is like that:
$item = $this->findData($id);
$item->delete();
You already have a relational function calling the UrRoyalUserData class: getRelRoyalUserData(). You can simplify your code doing:
public function findData($id)
{
if (($model = Profile::findOne($id)) !== null) {
$user = $model->relRoyalUserData;
return $user;
}
throw new NotFoundHttpException('The requested page does not st.');
}
Besides that, can you change your action and check what is returning?
public function actionDelet($id)
{
var_dump($this->findData($id));
}
If it throws you the same error, that means you dont have that Profile in your table. But if don't return a UrRoyalUserData class, the problem is you don't have any UrRoyalUserData related to that Profile.
I am using a repository pattern in my Laravel 4 project but come across something which I think I am doing incorrectly.
I am doing user validation, before saving a new user.
I have one method in my controller for this:
public function addNewUser() {
$validation = $this->userCreator->validateUser($input);
if ( $validation['success'] === false )
{
return Redirect::back()
->withErrors($validation['errors'])
->withInput($input);
}
return $this->userCreator->saveUser($input);
}
Then the validateUser method is:
public function validate($input) {
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'email_address' => 'unique:users'
);
$messages = [
];
$validation = Validator::make($input, $rules, $messages);
if ($validation->fails())
{
$failed = $validation->messages();
$response = ['success' => false, 'errors' => $failed];
return $response;
}
$response = ['success' => true];
return $response;
}
This may be okay, but I dont like doing the if statement in my controller? I would rather be able to handle that in my validation class.
But to be able to redirect from the validation class, I need to return the method in the controller.
What if I then want to have 5 methods called, I cant return them all?
I would like to be able to simply call the methods in order, then in their respective class handle what I need to and if there is any errors redirect or deal with them. But if everything is okay, simply ignore it and move to the next function.
So example:
public function addNewUser()
{
$this->userCreator->validateUser($input);
$this->userCreator->formatInput($input);
$this->userCreator->sendEmails($input);
return $this->userCreator->saveUser($input);
}
If doing the if statement in the controller isn't as bad as I think then I can continue, but this seems incorrect?
For repository pattern, you can use this :-
setup your basemodel like this
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model{
protected static $rules=null;
protected $errors=null;
public function validateForCreation($data)
{
$validation=\Validator::make($data,static::$rules);
if($validation->fails())
{
$this->errors=$validation->messages();
return false;
}
return true;
}
/**
* #return errors
*/
public function getErrors() { return $this->errors; }
}
now in your repository, add these methods
protected $model;
protected $errors=null;
public function model(){ return $this->model; }
public function getErrors(){ return $this->errors; }
public function create($inputs)
{
if(!$this->model->validateForCreation($inputs))
{
$this->errors=$this->model->getErrors();
return false;
}
$new=$this->model->create($inputs);
return $new;
}
and the controller will look like this..
public function postCreate(Request $request)
{
$inputs=$request->all();
if($new=$this->repo->create($inputs))
{
return redirect()->back()
->with('flash_message','Created Successfully');
}
return redirect()->back()->withInput()->withErrors($this->repo->getErrors())
->with('flash_message','Whoops! there is some problem with your input.');
}
Learning about Ioc and Repositories and stuck at last hurdle!
Assuming I am validating input, how do I pass back messages from the Validator within the repository to the controller?
UserRepository
interface UserRepository {
public function all();
public function create($input);
public function findById($id);
}
Sentry2UserRepository
class Sentry2UserRepository implements UserRepository {
...
public function create($input) {
$validation = Validator::make($input, User::$rules);
if ($validation->passes()) {
Sentry::createUser( array_except( $input, ['password_confirmation']));
// Put something here to tell controller that user has been successfully been created
return true;
}
else {
// pass back to controller that validation has failed
// with messages
return $validation->messages(); ?????
}
...
My UserController
UserController extends BaseController {
...
public function postRegister() {
$input['first_name'] = Input::get('first_name');
$input['last_name'] = Input::get('last_name');
$input['email'] = Input::get('email');
$input['password'] = Input::get('password');
$input['password_confirmation'] = Input::get('password_confirmation');
// Something like
if ($this->user->create($input)) {
Session::flash('success', 'Successfully registered');
return Redirect::to('/');
}
else {
Session::flash('error', 'There were errors in your submission');
return Redirect::to('user/login')->withErrors()->withInput();
}
}
...
}
Only 1.5 weeks into Laravel so please go easy on me.
Assuming your repository is working fine for you already:
class Sentry2UserRepository implements UserRepository {
public $validation;
public function create($input) {
$this->validation = Validator::make($input, User::$rules);
if ($this->validation->passes()) {
Sentry::createUser( array_except( $input, ['password_confirmation']));
// Put something here to tell controller that user has been successfully been created
return true;
}
else {
// pass back to controller that validation has failed
// with messages
return false;
}
}
}
Then you just have to access it within your controller using
$this->user->validation->messages()
I'm trying to send a specific message from Model in beforeSave() method. Flash messages don't work. I could send this message from Controller and use some parameters but I don't this this best solution. Use of print isn't good either.
So my question is how to send any message to controller/view from model?
You have to bubble an error message, try this
in your model :
public function beforeSave($options = array()){
if($not_ok){
$this->error = __('My error message');
return false;
}
return true;
}
in your controller :
public function add(){
$save = $this->Modelname->save($this->request->data);
if(!$save){
$this->Session->setFlash($this->Modelname->error);
$this->redirect($this->referer());
}
}
Well Session->setFlash() will not work, obviously, as it's part of a Session component, but
Session component uses static singleton class CakeSession, which has method CakeSession::write() all you've to do is pass array to write method that would have same structure as Session::setFlash() would generate and therefore when you use Session::flash() in view you will get same result as from setFlash() from controller.
For refrence: http://api.cakephp.org/2.2/class-CakeSession.html
Snippet from comment, to be placed in Model method.
App::uses('CakeSession','Model/Datasource');
CakeSession::write('Message', array(
'flash' => array(
'message' => 'your message here',
'element' => 'default',
'params' => null,
),
));
By doing the following you will be able to set flashes within your models at any point and not have to worry about declaring them again within your controller because they will get automatically set within your app controller before the page is rendered.
In AppController:
public function beforeRender() {
parent::beforeRender();
$this->generateFlashes();
}
public function generateFlashes() {
$flashTypes = array('alert', 'error', 'info', 'success', 'warning');
$model = $this->modelClass;
foreach($flashTypes as $type) {
if(!empty($this->$model->$type)) {
$message = '<strong>' . ucfirst($type) . ':</strong> ' . $this->$model->$type;
$this->Flash->error($message, array('escape' => false));
}
}
}
In Model:
public function beforeSave($options = array()){
if($not_ok){
$this->error = __('My error message');
return false;
}
return true;
}
In Controller:
public function add(){
// $this->modelClass will use whatever the actual model class is for this
// controller without having to type it out or replace the word modelClass
$save = $this->{$this->modelClass}->save($this->request->data);
if(!save){
// no need to set flash because it will get created in AppController beforeRender()
$this->redirect($this->referer());
}
}