Yii2 - Capture paramether from router in beforeAction - yii

Is there a way to capture parameter from router in the beforeAction, so it could be use by all functions in controller?
I have this router:
'http://<user:\w+>.' . $domain . '/<controller:\w+>/<action:\w+>' => '<controller>/<action>',
I want to use as ID in all functions in controller, and make it available without injecting it into the function? Is this possible?

A raw solution can be intercept the $params array inside the bindActionParams:
class ParamController extends Controller {
public $user;
public function bindActionParams($action, $params)
{
if(isset($params['user'])){
// may be some business based on $this or $action
$this->user = $params['user'];
}
return parent::bindActionParams($action, $params); // TODO: Change the autogenerated stub
}
}
Of course you need to extends all your controllers from ParamsController.

Related

Symfony 3 get current user inside entity

I was wondering if there is a way that i can initialize the property owner with an entity User of FOSUserBundle so that it contains the user who created the Post
I want to do this inside the constructor as shown below.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post
{
/* here are defined some attributs */
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="posts")
* #ORM\JoinColumn(name="owner", referencedColumnName="id")
*/
private $owner;
public function __construct()
{
$this->owner = /* get current user */ ;
}
}
Is there a way to do this by replacing the comment in the constructor with something ?
Thank you for your answers
No, there isn't. [*]
There are at least two ways to deal with this:
Create your Post entities through a factory service which populates the
owner property:
namespace My\Bundle\EntityFactory;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostFactory
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function createPost()
{
$user = $this->tokenStorage()->getToken()->getUser();
$post = new Post($user);
}
}
(for this example, you will have to modify your Post constructor to
accept the owner as a parameter)
In services.yml:
services:
post_factory:
class: My\Bundle\EntityFactory\PostFactory
arguments: [#security.token_storage]
To create an entity from your controller:
$post = $this->container->get('post_factory')->createPost();
If you can tolerate that the owner will only be set once you persist the
entity, you can use a doctrine event listener:
namespace My\Bundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostOwnerAssignmentListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
if ($entity instanceof Post && !$entity->getOwner()) {
$entity->setOwner($this->tokenStorage->getToken()->getUser());
}
}
}
In services.yml:
services:
post_owner_assignment_listener:
class: My\Bundle\EventListener\PostOwnerAssignmentListener
arguments: [#security.token_storage]
tags:
- { name: doctrine.event_listener, event: prePersit }
The advantage here is that the owner gets assigned no matter how and where
the Post is created.
[*]: Well, technically with the default app.php you could access the
kernel by declaring global $kernel; in your constructor and go from there,
however this is very strongly discouraged and may break in strange and subtle
ways.
I think you are way over-complicating this issue. When you create a new Post in your controller, either in the controller or in the repository do something like this:
use AppBundle\Entity\Post; //at top of controller
$em = $this->getDoctrine()->getManager();
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$post = new Post();
$em->persist( $post );
$post->setOwner( $user );
// set other fields in your post entity
$em->flush();
For Symfony 4+ with Autowiring and Entity event listener:
In /EventListener/PostPrePersistListener.php:
namespace App\EventListener;
use App\Entity\Post;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class PostPrePersistListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(Post $post, LifecycleEventArgs $event)
{
$post->setOwner($this->tokenStorage->getToken()->getUser());
}
}
In services.yaml:
services:
App\EventListener\PostPrePersistListener:
autowire: true
tags:
- { name: doctrine.orm.entity_listener, entity: 'App\Entity\Post', event: prePersist }
Modifying services.yaml is required as Symfony cannot know that this custom service is tagged to hook on doctrine.event_listener
This works at Entity-level as asked, to ensure Controller do not handle the owner value.

how to use SimpleSAMLphp in yii framework?

I have two project in yii framework and I want to use both project using SimpleSAMLphp with SSO. The condition, I need is if I login from the first project, i want access to the second project.
Thank you in advance.
First you load the SAML library by temporarily disabling the Yii autoloader. This is just to let you use the SAML classes and methods:
<?php
class YiiSAML extends CComponent {
private $_yiiSAML = null;
static private function pre() {
require_once (Yii::app()->params['simpleSAML'] . '/lib/_autoload.php');
// temporary disable Yii autoloader
spl_autoload_unregister(array(
'YiiBase',
'autoload'
));
}
static private function post() {
// enable Yii autoloader
spl_autoload_register(array(
'YiiBase',
'autoload'
));
}
public function __construct() {
self::pre();
//We select our authentication source:
$this->_yiiSAML = new SimpleSAML_Auth_Simple(Yii::app()->params['authSource']);
self::post();
}
static public function loggedOut($param, $stage) {
self::pre();
$state = SimpleSAML_Auth_State::loadState($param, $stage);
self::post();
if (isset($state['saml:sp:LogoutStatus'])) {
$ls = $state['saml:sp:LogoutStatus']; /* Only for SAML SP */
} else return true;
return $ls['Code'] === 'urn:oasis:names:tc:SAML:2.0:status:Success' && !isset($ls['SubCode']);
}
public function __call($method, $args) {
$params = (is_array($args) and !empty($args)) ? $args[0] : $args;
if (method_exists($this->_yiiSAML, $method)) return $this->_yiiSAML->$method($params);
else throw new YiiSAMLException(Yii::t('app', 'The method {method} does not exist in the SAML class', array(
'{method}' => $method
)));
}
}
class YiiSAMLException extends CException {
}
Then you define a filter extending the CFilter Yii class:
<?php
Yii::import('lib.YiiSAML');
class SAMLControl extends CFilter {
protected function preFilter($filterChain) {
$msg = Yii::t('yii', 'You are not authorized to perform this action.');
$saml = new YiiSAML();
if (Yii::app()->user->isGuest) {
Yii::app()->user->loginRequired();
return false;
} else {
$saml_attributes = $saml->getAttributes();
if (!$saml->isAuthenticated() or Yii::app()->user->id != $saml_attributes['User.id'][0]) {
Yii::app()->user->logout();
Yii::app()->user->loginRequired();
return false;
}
return true;
}
}
}
And finally, in the controllers you are interested to restrict, you override the filters() method:
public function filters() {
return array(
array(
'lib.SAMLControl'
) , // perform access control for CRUD operations
...
);
}
Hope it helps.
It can be done simply using "vendors" directory.
Download PHP Library from https://simplesamlphp.org/
Implement it in Yii Framework as a vendor library. (http://www.yiiframework.com/doc/guide/1.1/en/extension.integration)
Good Luck :)
I came across an Yii Extension for SimpleSAMLphp in github
https://github.com/asasmoyo/yii-simplesamlphp
You can load the simplesamlphp as a vendor library and then specify the autoload file in the extension.
Apart from the extension you can copy all the necessary configs and metadatas into the application and configure SimpleSAML Configuration to load the configurations from your directory, so you can keep the vendor package untouched for future updates.

CakePHP - Best way to Call API in controller?

In Message Model, I have
id/Subject/Comment/SenderID/RecipientID
So in Message Controller
public function index(){
$msgs = $this->Message->find();
$this->set('msgs', $msgs);
}
In Message view
foreach ($msgs as $msg)
echo ...
endforeach
BUT instead of outputting sender ID and recipient ID, I want to be able to get the username via an API which has already been set up, http://domain.com/userid/1, it will return the username in a json.
I know it's a bad practice to do this in view, but can you suggest how should I do this in controller?
Create a model for your API calls, tell cake not to look for a db table for it and have the function with the logic to pull the username there.
App::uses('AppModel', 'Model');
class MyAPI extends AppModel {
public $useTable = false;
public function getUserNameForID(Int $id = null) {
//Your logic here
return $userName;
}
}
Then in your controller you need to load the MyAPI model and pass it your data recursivly
$this->loadModel('MyAPI');
foreach (...) {
...
$userName = $this->MyAPI->getUserNameForID($userID);
...
}

Yii Framework - from url to route

I searched, but couldnt find something.
So, I have route rules:
...
'/reg' => '/user/user/registration',
...
in
Yii::app()->request
I couldn find any route information.
So, how can I get in module init function and having only url, route lile
/reg -> user/user/registration
UPD
The route is only available from the running controller. By the time when a module is initialized the controller is not yet available, thus you can't find out the route there. (You can follow CWebApplication::processRequest to see what happens when a request is resolved up to the point where the controller is run.)
It depends on what you try to achieve, but you could override WebModule::beforeControllerAction to do something before the module controller is run.
Today (next day after my question), I could solve this.
I will try to explain:
As Michael wrote, we cant know in module in which controller we are.
But I net get just reversed route, so, its quite esay.
Yii::app()->getUrlManager()->parseUrl('/reg');
This will return my reversed route
user/user/registration
parseUrl
Solution for Yii 1.1.15 workes for me.
class HttpRequest extends CHttpRequest {
protected $_requestUri;
protected $_pathInfo;
public function setUri($uri){
$this->_requestUri = $uri;
}
public function setPathInfo($route){
$this->_pathInfo = $route;
}
public function getPathInfo(){
/* copy from parent */
}
public function getRequestUri(){
/* copy from parent */
}
}
The usage:
$uri_path = 'my/project-alias/wall';
/** #var HttpRequest $request */
$request = clone Yii::app()->getRequest();
$request->setUri($uri_path);
$request->setPathInfo(null);
$route = Yii::app()->getUrlManager()->parseUrl($request);
//$route equals 'project/profile/wall' etc here (like in route rules);
I'm using a slightly different sub-class of CHttpRequest:
class CustomHttpRequest extends \CHttpRequest
{
/**
* #var string
*/
var $pathInfo;
/**
* #var string
*/
private $method;
public function __construct($pathInfo, $method)
{
$this->pathInfo = $pathInfo;
$this->method = $method;
}
public function getPathInfo()
{
return $this->pathInfo; // Return our path info rather than the default
}
public function getRequestType()
{
return $this->method;
}
}
Then to call it (to create a controller, which is what I want):
$request = new CustomHttpRequest($uri, $method); // e.g. 'my/project-alias/wall' and 'GET'
$route = \Yii::app()->getUrlManager()->parseUrl($request);
list($jcontroller, $actionName) = \Yii::app()->createController($route);

setAttribute() function in YII is not working

I am using PHP Yii Framework with MongoDB(yiimongodbsuite). I have created a Model which extends from EMongoDocument.
<?php
class MyModel extends EMongoDocument
{
public $attr1;
public $attr2;
// rules, custom validations and other functions....
public function setAttributes($values, $safeOnly=true)
{
if(!is_array($values))
return;
if($this->hasEmbeddedDocuments())
{
$attributes=array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
foreach($this->embeddedDocuments() as $fieldName => $className)
if(isset($values[$fieldName]) && isset($attributes[$fieldName]))
{
$this->$fieldName->setAttributes($values[$fieldName], $safeOnly);
unset($values[$fieldName]);
}
}
parent::setAttributes($values, $safeOnly);
}
}
In Controller,
$dataModel = new MyModel();
$dataModel->setAttributes($_POST['MyModel']);
if($dataModel->validate()){
$dataModel->save();
}
the above code is not setting the attribute value.
Please let me know if there is any mistake.
You need to make sure that the 'safe' validation rules is used on each level.
To understand more read this http://www.yiiframework.com/wiki/161/understanding-safe-validation-rules/
Try to determine which valdation errors you have:
if(!$model->validate()) {
die( print_r($model->getErrors()) );
}