Annotations in extended Sonata user class not being read - symfony-sonata

I extended the user bundle from the Sonata project with Sonata EasyExtends bundle.
It is placed under src/Application/Sonata/UserBundle by default
Now I want to customize the extended class and add some fields.
I notice though that annotations are not being processed, I need to define the mappings in src/Application/Sonata/UserBundle/Resources/config/doctrine/User.orm.xml
Is there any way to make use of the annotations instead of the XML file?
I think it will solve a lot of my problems with referencing the user class, as now the command
php app/console doctrine:schema:update --force
doesnt seem to recognize the annotations..

Delete Application/Sonata/UserBundle/Resources/config/doctrine at first
and later change Entity/User.php and Entity/Group.php to annotation type:
<?php
namespace Application\Sonata\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
/**
* User
*
* #ORM\Table(name="fos_user_user")
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=127)
*/
protected $test;
}
<?php
namespace Application\Sonata\UserBundle\Entity;
use Sonata\UserBundle\Entity\BaseGroup as BaseGroup;
use Doctrine\ORM\Mapping as ORM;
/**
* Group
*
* #ORM\Table(name="fos_user_group")
* #ORM\Entity
*/
class Group extends BaseGroup
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
In the end you should type: php app/console doctrine:schema:update --force and everything should works pretty.

In resume, if you use XML, annotations does not rules.
If you delete config/doctrine folder, it will look for annotations, and there, you can put what you want.

Related

Laravel 8.24 listeners/events doesn't adds to queue

I want to implement email notifications for some events. Also I was this events to be processed asynchronosly using Laravel database queue.
Here is what I have:
Event class:
<?php
namespace App\Events;
use App\Models\ServerReviewVote;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class NewServerReviewVote
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(public ServerReviewVote $serverReviewVote)
{
}
}
Listener class:
<?php
namespace App\Listeners;
use App\Events\NewServerReviewVote;
use App\Notifications\NewServerReviewVote as NewServerReviewVoteNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendNewServerReviewVoteNotification implements ShouldQueue
{
/**
* Handle the event.
*
* #param NewServerReviewVote $event
* #return void
*/
public function handle(NewServerReviewVote $event)
{
$event->serverReviewVote->serverReview->author
->notify(new NewServerReviewVoteNotification($event->serverReviewVote->serverReview));
}
}
Notification class:
<?php
namespace App\Notifications;
use App\Models\ServerReview;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class NewServerReviewVote extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(private ServerReview $serverReview)
{
}
/**
* Get the notification's delivery channels.
*
* #param User $notifiable
* #return array
*/
public function via(User $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param User $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail(User $notifiable): MailMessage
{
return (new MailMessage)
->subject('New vote for your review!')
->greeting("Hello, {$notifiable->name}!")
->line("We got a new vote for your review for {$this->serverReview->server->name} server!")
->line("Your review currently have {$this->serverReview->votesUpCount()} upvotes and {$this->serverReview->votesDownCount()} downvotes.")
->line("Click the button below to navigate to the server page:")
->action($this->serverReview->server->name, route('servers.show', ['server' => $this->serverReview->server->slug]));
}
}
I'm firing event from this observer:
<?php
namespace App\Observers;
use App\Events\NewServerReviewVote;
use App\Models\ServerReviewVote;
class ServerReviewVoteObserver
{
/**
* #param ServerReviewVote $serverReviewVote
*/
public static function created(ServerReviewVote $serverReviewVote)
{
event(new NewServerReviewVote($serverReviewVote));
}
}
I configured queue database driver and my database has jobs table.
My expectations is that this event will be added to this table and than I can process it by using php artisan queue:work. But for some reason email sends synchronously instead of adding to queue. What have I missed?
Ok, I've been trying different ways and found how to make listeners to be added to queue.
I added public $connection = 'database' property to listener class.
Here is how it looks now:
<?php
namespace App\Listeners;
use App\Events\NewServerReviewVote;
use App\Notifications\NewServerReviewVote as NewServerReviewVoteNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendNewServerReviewVoteNotification implements ShouldQueue
{
/**
* Connection for queue
* #var string
*/
public string $connection = 'database';
/**
* Handle the event.
*
* #param NewServerReviewVote $event
* #return void
*/
public function handle(NewServerReviewVote $event)
{
$event->serverReviewVote->serverReview->author
->notify(new NewServerReviewVoteNotification($event->serverReviewVote->serverReview));
}
}
As far as I understood from Laravel docs this property isn't required but for some reason listener didn't dispatch to queue without it. Now it works good!
Also I cleared notification class as it doesn't need to implement ShouldQueue interface and use Queueable trait.
Here is how it looks now:
<?php
namespace App\Notifications;
use App\Models\ServerReview;
use App\Models\User;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class NewServerReviewVote extends Notification
{
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(private ServerReview $serverReview)
{
}
/**
* Get the notification's delivery channels.
*
* #param User $notifiable
* #return array
*/
public function via(User $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param User $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail(User $notifiable): MailMessage
{
return (new MailMessage)
->subject('New vote for your review!')
->greeting("Hello, {$notifiable->name}!")
->line("We got a new vote for your review for {$this->serverReview->server->name} server!")
->line("Your review currently have {$this->serverReview->votesUpCount()} upvotes and {$this->serverReview->votesDownCount()} downvotes.")
->line("Click the button below to navigate to the server page:")
->action($this->serverReview->server->name, route('servers.show', ['server' => $this->serverReview->server->slug]));
}
}
Now listener successfully dispatching to jobs table and can be processed by running php artisan queue:work database

Laravel dependency injection in a command method

I am sorry if for someone the title of my question turns out to be very common, but the truth is that I have been trying for hours to obtain the expected result and I have not succeeded.
It happens, that I am developing a small package for Laravel, and I cannot perform a dependency injection in a method within a command that will contain the package.
Inside the directory structure of my package, I have the ServiceProvider
<?php
namespace Author\Package;
use Author\Package\Commands\BaseCommand;
use Author\Package\Contracts\MyInterface;
use Illuminate\Support\ServiceProvider;
class PackageServiceProvider extends ServiceProvider
{
/**
* The commands to be registered.
*
* #var array
*/
protected $commands = [
\Author\Package\Commands\ExampleCommand::class
];
/**
* Register services.
*
* #return void
*/
public function register()
{
if (! $this->app->configurationIsCached()) {
$this->mergeConfigFrom(__DIR__ . '/../config/package.php', 'package');
}
$this->app->bind(MyInterface::class, BaseCommand::class);
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__ . '/../config/package.php' => config_path('package.php')
], 'package-config');
$this->configureCommands();
}
}
/**
* Register the package's custom Artisan commands.
*
* #return void
*/
public function configureCommands()
{
$this->commands($this->commands);
}
}
As you can see from the register method, I am creating a binding for when it calls the MyInterface interface, it returns the concrete BaseCommand class
public function register()
{
...
$this->app->bind(MyInterface::class, BaseCommand::class);
}
The structure of the ExampleCommand file is as follows:
<?php
namespace Author\Package\Commands;
use Author\Package\Contracts\MyInterface;
use Illuminate\Console\Command;
class ExampleCommand extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'my:command';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Command Description';
/**
* Execute the console command.
*
* #return void
*/
public function handle(MyInterface $interface)
{
// TODO
}
}
But when I run the command, I get the following error:
TypeError
Argument 1 passed to Author\Package\Commands\ExampleCommand::handle() must be an instance of Author\Package\Contracts\MyInterface, instance of Author\Package\Commands\BaseCommand given
I wonder why dependency injection is not working, in essence it should inject the concrete BaseCommand class into the handle method of the ExampleCommand class, but it isn't. I would appreciate any help you can give me.
Your BaseCommand must implement the interface that you have typehinted for that handle method. Dependency Injection happens before the method is called, so the container resolved your binding (as it is trying to pass an instance of BaseCommand to the method call, handle) but the binding does not return something that implements that contract so PHP won't allow that to be passed for that argument since it doesn't match the type of the argument in the signature (does not implement the contract).
In short: if you are going to bind a concrete to an abstract, make sure the concrete is actually of the type you are binding it to.

Create contracts in laravel 5.4

Documentation on laravel.com is not sufficient. Can any one guide me through how to How To Create contracts in Laravel from scratch.
I need implementation of Contracts in Laravel. Right now, I'm using Laravel 5.4
Contract is just a fancy name for php interfaces. We have being using them all along and its not a new thing.
Contracts/Interfaces help us to maintain a loosely coupled code base. See the example from doc below.
<?php
namespace App\Orders;
class Repository
{
/**
* The cache instance.
*/
protected $cache;
/**
* Create a new repository instance.
*
* #param \SomePackage\Cache\Memcached $cache
* #return void
*/
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}
/**
* Retrieve an Order by ID.
*
* #param int $id
* #return Order
*/
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
Here when ever the Repository instantiate we should give a \SomePackage\Cache\Memcached instance in order for code to work. Hence our code is tightly coupled with \SomePackage\Cache\Memcached. Now look at below code.
<?php
namespace App\Orders;
use Illuminate\Contracts\Cache\Repository as Cache;
class Repository
{
/**
* The cache instance.
*/
protected $cache;
/**
* Create a new repository instance.
*
* #param Cache $cache
* #return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
Same thing but now we just need to provide some cache interface. And behind the scene you could have done something like this.
<?php
namespace App\Orders;
use Illuminate\Contracts\Cache\Repository as Cache;
class RedisCache implements Cache {
//
}
When above Repository instantiate, php will look at the Illuminate\Contracts\Cache\Repository and It has been implemented by RedisCache class.
I'm afraid Gayan's answer needs further elaboration to hit Rajan's question.
Yes Gayan is correct that creating a Contract class basically means creating a php interface.
Continuing the Cache example above, if we look into its source code (you can find it at this Github repo file), we can see something like this
<?php
namespace Illuminate\Contracts\Cache;
use Closure;
interface Repository
{
/**
* Determine if an item exists in the cache.
*
* #param string $key
* #return bool
*/
public function has($key);
/**
* Retrieve an item from the cache by key.
*
* #param string $key
* #param mixed $default
* #return mixed
*/
public function get($key, $default = null);
// the rest...
}
If we are using this interface in our laravel app, it is said to be a "Contract". It is declaring what methods/properties a class should have if it implements this interface. For example in our app...
<?php
namespace App\Whatever;
use Illuminate\Contracts\Cache\Repository;
class Foo implements Repository {
//
}
Then class Foo will need to have methods has and get in order to fulfil what has been stated in the Repository contract.

JSON Web Token only work in Laravel with Eloquent model

I am using Laravel and I want to use JSon Web Token (JWT). I download the tymon vendor. When I tried to generate the token it raise me an error said that my model is not an instance of Eloquent model. So I check the vendor code and I saw this in EloquentUserAdapter:
<?php
namespace Tymon\JWTAuth\Providers\User;
use Illuminate\Database\Eloquent\Model;
class EloquentUserAdapter implements UserInterface
{
/**
* #var \Illuminate\Database\Eloquent\Model
*/
protected $user;
/**
* Create a new User instance
*
* #param \Illuminate\Database\Eloquent\Model $user
*/
public function __construct(Model $user)
{
$this->user = $user;
}
/**
* Get the user by the given key, value
*
* #param mixed $key
* #param mixed $value
* #return Illuminate\Database\Eloquent\Model
*/
public function getBy($key, $value)
{
return $this->user->where($key, $value)->first();
}
}
My problem here is that this adapter only use Eloquent model injection. I am using Doctrine models. So my questions are:
Is possible to change this adapter to return new model (my doctrine
model). I asking because I am new in PHP and Laravel and I saw that
EloquentUserAdapter is used in other places in the vendor.
If I create a new Adapter I think that I have to return a Eloquent
model, so how do I can redefine only the model and reuse the other
classes and methods of the vendor?
Any clue?
Yes, you can:
use App\Entities\User;
use Doctrine\ORM\EntityManagerInterface;
use Tymon\JWTAuth\Providers\User\UserInterface;
class DoctrineUserAdapter implements UserInterface
{
protected $em;
public function __construct(User $user, EntityManagerInterface $em)
{
$this->em = $em;
}
public function getBy($key, $value)
{
return $this->em->find('App\Entities\User', $value);
}
}
You can inject EntityManagerInterface object as a second parameter, first parameter is a User model type from 'providers.user' in jwt.php configuration, why? Look at the code in JWTAuthServiceProvider.php:
/**
* Register the bindings for the User provider.
*/
protected function registerUserProvider()
{
$this->app['tymon.jwt.provider.user'] = $this->app->share(function ($app) {
return $app->make($this->config('providers.user'), [$app->make($this->config('user'))]);
});
}
My simple user model:
use DOctrine\ORM\Mapping as ORM;
/**
* Users *
* #ORM\Table(name="users")
* #ORM\Entity
*/
class User implements \Illuminate\Contracts\Auth\Authenticatable
{
use \LaravelDoctrine\ORM\Auth\Authenticatable;
/**
*
* #var integer *
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
public $id;
}
You can do this in version 1.0.0. More about problem: https://github.com/tymondesigns/jwt-auth/issues/343

Model validation Symfony2

I have read how to validate forms in server side with sf2. The solution is by using the Constraints in the Entity as annotations, validation.yml or inside the EntityType (Form).
Everything is fine, however, all of these validations work just with the form. But when you instance a new object and try to persist, validation doesn't work.
I will give you an example.
Imagine I have a user entity:
/**
* #ORM\Entity
* #ORM\Table(name="sf_user")
*/
class User{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column( name="username", type="string", length=50, unique=true )
*/
protected $username;
/**
* #var string
* #ORM\Column( name="email", type="string", length=100, unique=true )
*/
protected $email;
public static function loadValidatorMetadata(\Symfony\Component\Validator\Mapping\ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('username', new \Symfony\Component\Validator\Constraints\NotBlank());
$metadata->addPropertyConstraint('email', new \Symfony\Component\Validator\Constraints\NotNull());
}
}
Then, in some controller I try to save my form with:
$this->form = $this->create(new UserType());
$this->form->setData(new User());
$this->form->bind($this->request);
if( $this->form->isValid())
{
//Persist with entity manager
}
Everything works perfectly because I have an association between my Entity and my form. But what happen if i need to instance an object without a form?. I should do something like this:
$user = new User();
$user->setUsername("username");
//Persist with entity manager
If I do that, entity is not validated and DB throws an error because the field "email" is required.
Should I always associate my entity with the form to validate? If that is the case, I don't agree at all because if I am working with web services, I don't wanna create a form just to validate on the server side.
So, how could I do this validation?. Thanks for your help.
You can use the validation service
$validator = $this->get('validator');
$validator->validate($user);
see the docs about this.
By the way there is a cleaner way to specify validation in you entity.
use Symfony\Component\Validator\Constraints as Assert;
class User{
/**
* #Assert\NotNull
*/
protected $username;
/**
* #Assert\NotBlank
* #Assert\Email
*/
protected $email;

Resources