Symfony, How to use DenyAccess with Doctrine object? - function

I want to control the same user access on some methods in my controller.
Currently, I use this :
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
However I am forced to use this line and inject the ToolRepository into each method ...
What would be the easiest way to do it?
I saw that there was the IsGranted annotation but my subject needs to be a doctrine object to control access with my Vote.
/**
* #Route("/player")
*/
class PlayerController extends AbstractController
{
/**
* #Route("/", name="player")
* #throws Exception
*/
public function Player(ToolRepository $toolRepository): \App\Entity\Tool
{
$playerTool = 'TestTool2';
$tool = $toolRepository->findOneBy(array('libelle' => $playerTool));
if (!$tool) {
throw new Exception('Tool : ' . $playerTool . 'not found!');
}
return $tool;
}
/**
* #Route("/main", name="player")
* #IsGranted ("ACCESS", subject="tool")
* #throws Exception
*/
public function mainPlayer(PlayerRepository $playerRepository, ToolRepository $toolRepository): Response
{
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
$players = $playerRepository->findAll();
return $this->render('player/player_mainpage.html.twig', ['players'=>$players]);
}
}

I think this ressource should answer you: Symfony voters.
You'll put your security logic in your custom voter which will be called in every function of your controller (or every methods where you want to control access) isGranted() function.
Calling your Player() function is a easier way to do this for beginner, you can keep like that but you shouldn't put it in Controller and use a Service instead .
Edit:
You may store your ToolRepository as Controller private property and inject it in a __construct() method so you don't have to inject ToolRepository in each method

Related

How to get real-time data on new data insert into MySQL db

I am using Laravel and pusher. Pusher is working good. But I want to know how can I get data when I will insert new data into database?
Process is if someone push/insert data on the table, then those data will automatically show without reloading the page.
Can anyone explain it? or give me any documentation or video link about it?
What you need are Broadcast Events.
Let's assume that you are inserting a Post and you want all users to get notified about a new post, therefore refresh the posts table index.
All your users should be subscribed to a presence channel, but you could use private or public channel. IMO, presence channel works better for this scenario since you are dispatching just 1 event for all users instead of 1 event per user in case of private channel
In your store function in PostController.php you dispatch the event once Post has been created:
use App\Events\PostCreated;
public function store(Request $request)
{
// Insert new post
$post = Post::create($request->all());
// Dispatch broadcast
PostCreated::dispatch($post);
return $result;
}
Then in your PostCreated.php Event, you send the post itself as the event payload:
<?php
namespace App\Events;
use App\Models\Post;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PostCreated implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $afterCommit = true;
public Post $post;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Post $post)
{
$this->post = $post;
}
/**
* The event's broadcast name.
*
* #return string
*/
public function broadcastAs()
{
return 'post.created';
}
/**
* Get the data to broadcast.
*
* #return array
*/
public function broadcastWith()
{
return $this->post;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel('posts');
}
}
Now you need that all users subscribe to the right channel. Assuming, again, that you are using laravel echo, this is how I do it within a Vue Js app by joining a presence channel posts and listening for post.created event.
this.echoInstance.join('posts')
.listen('.post.created', (post) => {
// Do something like refresh table
// or insert `post` object directly in posts array
})
Since you didn't provide any code, this is a generic sample. Next time, please share what you've done so far.

Yii2 field accessed only via magic method

/**
* This is the model class for table "hashtag".
*
* #property string $text
*
* #property TweetHashtag[] $tweetHashtags
* #property Tweet[] $tweets
*/
class Hashtag extends ActiveRecord
{
.........
public function getTweetHashtags()
{
return $this->hasMany(TweetHashtag::className(), ['hashtag_text' => 'text']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getTweets()
{
return $this->hasMany(Tweet::className(), ['id' => 'tweet_id'])->viaTable('tweet_hashtag', ['hashtag_text' => 'text']);
}
}
When I do in some component
$hashtags = Hashtag::find()
->with('tweets')
->where(['text' => $hashtagText])
->all();
foreach($hashtags as $hashtag)
{
print_r($hashtag->tweets);
}
It`s working but why tweets - field accessed only via magic method and how can i fix it? And tweetHashtags working well.
Class Tweet have same relationship but public function getHashtags() working without this problem.
Your question is not clear. Each method on a Component class that start with get (like getName) can be accessed with property form (e.g. name). On special case, relations of Yii's ActiveRecord, if you access to relation by property form, you get results. In fact $this->tweets is a shorthand for $this->getTweets()->all().
P.S: On Yii2 Document http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#accessing-relational-data:
Note: While this concept looks similar to the object property feature,
there is an important difference. For normal object properties the
property value is of the same type as the defining getter method. A
relation method however returns an yii\db\ActiveQuery instance, while
accessing a relation property will either return a yii\db\ActiveRecord
instance or an array of these.
$customer->orders; // is an array of `Order` objects
$customer->getOrders(); // returns an ActiveQuery instance
This is useful for creating customized queries, which is described in the next section.

Symfony 2 Controller Action no return Response

I have been some time without programing in Synfony and I have some doubts.
Is posible that and Action Controller return a variable (for example and integer) instead of a Response Object or Json Object.
What I need is call a function inside another function in a different Controller. If the 2 functions live in the same Controller it has no problem (like this):
class AController{
public function AAction(){
$var = $this->BAction(); //Do whatever I want with $var
return Response ("Hello");
}
public function BAction(){
return 34; //return an integer instead of a Response
}
}
THE PROBLEM IS when the BAction is in another Controller. If I use a forward, Symfony expect that BAction return a Response object or a Json array, but I only want to return a simple variale.
Is this posible?? Return a simple integer...
Thanks a lot!!
No a Action must return a Response Object. But if you have two controllers (that will say two different classes) then you could create a service.
app/config/config.yml
services:
app.my_ownservice:
class: AppBundle\Services\OwnService
arguments:
entityManager: "#doctrine.orm.entity_manager"
app/Services/OwnService.php
namespace AppBundle\Services;
use Doctrine\ORM\EntityManager;
class OwnService {
/**
*
* #var EntityManager
*/
private $em;
public function __constructor(EntityManager $entityManager)
{
$this->em = $entityManager;
}
public function doSomething(){
// you could use the entitymanager here
return 'Okay i will do something.';
}
}
And from each controller (or whatever) you can do:
$myOwnService = $this->get('app.my_ownservice');
$text = $myOwnService->doSomething();
// echo $text;
A controller should never use another controllers action. Thats not the problem that Controllers solve. Symfony business logic structure is SOA based. (https://en.wikipedia.org/wiki/Service-oriented_architecture) Therefore for custom business logic you should always use either:
Services: http://symfony.com/doc/current/book/service_container.html
Events: http://symfony.com/doc/current/components/event_dispatcher/introduction.html

Typehint inherited class variables in PHPStorm

In PHPStorm, I can type-hint a variable this way:
/** #var Point $point */
$point->x();
However, say I inherited a variable from a parent class, and want to type-hint it:
class PointProxy extends Proxy
{
public function x()
{
...
/** #var Point $this->geometry */
return $this->geometry->x();
}
}
This doesn't work, PHPStorm acts as if I had type-hinted $this, and not $this->geometry.
Is there a way to make such a type-hint work without redeclaring the $geometry property in the subclass, or is this unsupported?
Try this code. Also you can press alt+enter at undefined properties and select Add #property it will help you to create phpdoc faster.
/**
* #property Point $geometry
*/
class PointProxy extends Proxy {
public function x() {
return $this->geometry->
}
}
If the parent object types the property as Geometry but you want it typed as a Point (which descends from Geometry) in your child class, I would recommend creating an accessor in your child class. Possibly with some type checking.
class PointProxy extends Proxy
{
/**
* Access the geometry object on parent class as a Point
*
* #return Point
*/
private point()
{
if(!is_a($this->geometry, 'Point'))
{
// Log an error or something, this is not a state we should be in
}
else
{
return $this->geometry;
}
}
public function x()
{
...
return $this->point->x();
}
}
I ran into a very similar problem. I had a generic storage class that dealt with database operations and then a proxy class on top which proxied all storage class methods through a try catch (using __call()) so that I could handle exceptions in one location.
Now whenever I accessed the storage instance like $storage->retrievePhoto($id), the PHPStorm IDE could not typehint for me. My solution involved adding another class name annotation to the $storage object.
For example, see below. Since the specific proxy class is really just a wrapper over the original storage class, it doesn't present any problems although it is still not 100% to my liking but it works.
final class PhotoRepository
{
/**
* #var \Repositories\Photos\PhotoStorage
* \Repositories\Photos\PhotoStorageExceptionHandlerProxy
*/
private $storage;
/**
* #param \Repositories\Photos\PhotosStorageExceptionHandlerProxy $storage
*/
public function __construct(PhotosStorageExceptionHandlerProxy $storage)
{
$this->storage = $storage;
}

Using Fractal Transformer with ember-data

I am using PHP league's Fractal as the transformer for my API. However, I think I must be doing something wrong as the item transformer wraps everything in an array like it would a collection which is against the JSON API standard I believe.
So for a user with ID of one I get something like this:
{
"users":[
{
"id":1,
"firstName":"Jacob",
"surname":"Windsor",
}
]
}
When surely it should be this?
{
"users":
{
"id":1,
"firstName":"Jacob",
"surname":"Windsor",
}
}
I am using ember.js and this is causing problems with naming conventions.
I am using Laravel and in my userController I have something like this:
public function show($id)
{
$user = User::find($id);
return $this->respondItem($user);
}
Then in the apiController that everything extends from:
public function respond($response, $status = 200){
return Response::make($response, $status);
}
public function respondTransform($resource){
$fractal = new Fractal\Manager();
$fractal->setSerializer(new JsonApiSerializer());
return $this->respond($fractal->createData($resource)->toJson());
}
public function respondItem($data, $transformer = null, $namespace = null){
! isset($transformer) ? $transformer = $this->transformer : $transformer = $transformer;
! isset($namespace) ? $namespace = $this->namespace : $namespace = $namespace;
$resource = new Item($data, $transformer, $namespace);
return $this->respondTransform($resource);
}
I must be doing something wrong. The fractal docs have no examples specifically for items only collections so I am unsure what I have done.
So it seems that Fractal doesn't quite obey ember-data's conventions which is an annoying problem but very easily overcome using custom serialziers.
I have a psr-4 autoloaded file named CustomJsonSerializer which I have included in my ApiController class. If you follow the article on php league's site (posted above) its fairly easy to do. I have these two methods.
public function collection($resourceKey, array $data)
{
return array($resourceKey ?: 'data' => $data);
}
/**
* Serialize an item resource
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function item($resourceKey, array $data)
{
return [$resourceKey => $data];
}
You can see that the collection is responding as it normally would, i.e I haven't changed it. But the item method just responds without the extra array. Simple! You have to include all the other methods as well and I haven't got round to sorting out pagination but it should be fairly simple.
I hope this helps anyone wanting to use ember-data with Fractal. I highly recommend it, fractal has made my life so much easier. You could build transformers yourself but it makes it so much easier and more easily modified in the future.
Edit:
Please make sure you keep the $resourceKey in both the methods. You need to be using it and setting it when calling the transformer. |Ember-data requires a resource key.
Assuming your userController extends ApiController, you could simply do:
public function show($id)
{
$user = User::findOrFail($id);
return $this->setStatusCode(200)->withItem($user, new UserTransformer);
}
You do need to implement the UserTransformer class. If you need help with that, let me know in the comments.
I actually found that a much simpler adjustment of JsonApiSerializer did what I needed for Ember:
(I just took out the count($data) check)
<?php
namespace Acme\Serializer;
use RuntimeException;
use League\Fractal\Serializer\JsonApiSerializer;
class EmberSerializer extends JsonApiSerializer
{
/**
* Serialize the top level data.
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function serializeData($resourceKey, array $data)
{
if (! $resourceKey) {
throw new RuntimeException('The $resourceKey parameter must be provided when using '.__CLASS__);
}
return array($resourceKey => $data);
}
}