I'm using Bolt 4 CMS which is based on Symfony 5. In a controller I wrote, I would like to list all the users from my database, to retrieve their email addresses and send them an email. For now I am simply trying to retrieve the email address from the username.
In this example https://symfony.com/doc/current/security/user_provider.html, it shoes how to create your own class to deal with users from the database:
// src/Repository/UserRepository.php
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
class UserRepository extends ServiceEntityRepository implements UserLoaderInterface
{
// ...
public function loadUserByUsername(string $usernameOrEmail)
{
$entityManager = $this->getEntityManager();
return $entityManager->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.username = :query
OR u.email = :query'
)
->setParameter('query', $usernameOrEmail)
->getOneOrNullResult();
}
}
In my custom controller, I then call this class and function:
// src/Controller/LalalanEventController.php
namespace App\Controller;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use App\Repository\LalalanUserManager;
class LalalanEventController extends AbstractController
{
/**
* #Route("/new_event_email")
*/
private function sendEmail(MailerInterface $mailer)
{
$userManager = new LalalanUserManager();
$email = (new Email())
->from('aaa.bbb#ccc.com')
->to($userManager('nullname')->email)
->subject('Nice title')
->text('Sending emails is fun again!')
->html('<p>See Twig integration for better HTML integration!</p>');
$mailer->send($email);
}
}
Unfortunately, in the example, the class extends from ServiceEntityRepository, which requires a ManagerRegistry for the constructor. Does anyone have a clue what could I change to solve this?
Thanks in advance!
As said in the doc,
User providers are PHP classes related to Symfony Security that have two jobs:
Reload the User from the Session
Load the User for some Feature
So if you want only to get list of users, you have just to get the UserRepository like this:
/**
* #Route("/new_event_email")
*/
private function sendEmail(MailerInterface $mailer)
{
$userRepository = $this->getDoctrine()->getRepository(User::class);
$users = $userRepository->findAll();
// Here you loop over the users
foreach($users as $user) {
/// Send email
}
}
Doctrine reference: https://symfony.com/doc/current/doctrine.html
You need also to learn more about dependency injection here: https://symfony.com/doc/current/components/dependency_injection.html
Related
I'm trying to create an SQLFilter for a query in my Symfony app.
The issue is that the filter is not applied on the query (and not called), even though is it enabled correctly (see below).
The repository is not linked to an entity, because the database is external to my app, but it still has access to the data.
Am I missing something ?
Here's the filter:
<?php
namespace App\SQL\Filter;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class UserRoleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
return 'c.roleId = 1';
}
}
I registered it in config/packages/doctrine.yaml:
doctrine:
filters:
user_role: App\SQL\Filter\UserRoleFilter
The controller:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\Persistence\ManagerRegistry;
use App\Repository\CustomerRepository;
class CustomerController extends AbstractController
{
public function myAction(Request $request, ManagerRegistry $doctrine, CustomerRepository $customerRepository)
{
$doctrine->getManager()->getFilters()->enable('user_role');
$customers = $customerRepository->findAll();
}
}
The repository:
<?php
namespace App\Repository;
use Doctrine\DBAL\Connection;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
class CustomerRepository
{
protected Connection $conn;
protected ObjectManager $em;
public function __construct(ManagerRegistry $doctrine)
{
$this->em = $doctrine->getManager();
$this->conn = $this->em->getConnection();
}
public function findAll(): array
{
dump($this->em->getFilters()->isEnabled('user_role')); // returns true
return $this->conn->createQueryBuilder()
->select('c.*')
->from('customer', 'c')
->executeQuery()
->fetchAllAssociative();
}
}
From looking at the source for Doctrine/DBAL, it doesn't look like the filter would ever be applied to the query you are executing.
Within your repository class you are creating an instance of Doctrine\DBAL\Query\QueryBuilder which only holds a reference to Doctrine\DBAL\Connection.
Then the select data is set to its private parameter $sqlParts.
When executeQuery() is called is concatenates the content of sqlParts into a string with no mention or reference to any filter objects nor their constraints. This can be seen on line 308 of the QueryBuilder class.
QueryBuilder::executeQuery()
You can also see how the select query is concatenated on line 1320 of QueryBuilder.
QueryBuilder::getSQLForSelect()
The only way I can see to add it easily would be to add it directly to a where clause, e.g.
public function findAll(): array
{
return $this->conn->createQueryBuilder()
->select('c.*')
->from('customer', 'c')
->where("c.roleId = 1") // Or pull it from the filter object in some way
->executeQuery()
->fetchAllAssociative();
}
If you want to see where the filter constraints are added to the queries you can find that data in the ORM package from Doctrine, however these are all linked to entities and table aliases.
SqlWalker::generateFilterConditionSQL()
BasicEntityPersistor::generateFilterConditionSQL()
ManyToManyPersistor::generateFilterConditionSQL()
I'm learning symfony and I would like to have a search bar to show user with email. But I got and error
Attempted to call an undefined method named "getEntityManager" of class "App\Repository\SearchRepository".
If someone can help me or explain me how to do it's would be very nice. Thanks
In SearchRepository
class SearchRepository
{
public function findAllWithSearch($email){
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.email :email'
)->setParameter('email', $email);
return $query->execute();
}
}
In SearchController
class SearchController extends AbstractController
{
/**
* #Route("/admin/search/", name="admin_search")
* #Security("is_granted('ROLE_ADMIN')")
*/
public function searchUser(SearchRepository $repository, Request $request)
{
$q = $request->query->get('search');
$comments = $repository->findllWithSearch($q);
return $this->render('admin/search/search.html.twig',
[ 'user' => $repository,
]);
}
}
and search.twig.html
<form action="" method="get">
<input type="search" name="search" value="" placeholder="Recherche.." />
<input type="submit" value="Valider" />
</form>
Quick Answer
SearchRepository needs to extend \Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository.
<?php
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class SearchRepository extends ServiceEntityRepository
public function findAllWithSearch($email)
{
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.email :email'
)->setParameter('email', $email);
return $query->execute();
}
Fix your Application's Architecture
Looks like you're at the very beginning of your journey. The above code will fix your issue, but you need to pay attention to the architecture of your code.
Repository Classes are like containers for your entities.
You would not have a repository for Search (unless you're storing Search entities).
You would usually put this into a UserRepository. Which should be charged with the responsibility of being a repo for User Entity objects.
There are magic methods within Repositories that will allow you to find Entities.
Using your specific example, you could use something like
$repoInstance->findByEmail($email);
within your controller and this will return all records entities that
match your email address.
More about Working with Doctrine Repositories
For more information on how repositories work, consume and experiment with the documentation from this link:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/working-with-objects.html
From the doctrine documentation:
A repository object provides many ways to retrieve entities of the
specified type. By default, the repository instance is of type
Doctrine\ORM\EntityRepository. You can also use custom repository
classes.
So, if your search is going to deal with User objects, you can use the standard UserRepository by doing the following in your Controller:
/**
* #Route("/admin/search/", name="admin_search")
* #Security("is_granted('ROLE_ADMIN')")
*/
public function searchUser(Request $request)
{
$q = $request->query->get('search');
// Get standard repository
$user = $this->getDoctrine()->getManager()
->getRepository(User::class)
->findBy(['email' => $q]); // Or use the magic method ->findByEmail($q);
// Present your results
return $this->render('admin/search/search_results.html.twig',
['user' => $user]);
}
There is no need for a custom repository for your use case, but if you want to create one and use it for autowiring you must extend ServiceEntityRepository, a container-friendly base repository class provided by Symfony. You can get more details in the documentation. In this case you might want to also review how to annotate your entity to tell the EntityManager that you'll be using a custom repository.
Sidenote: By default, the action attribute of the form defaults to the same route you are visiting, so if that fragment is part of a layout you'll have to set it explicitly to your SearchController action: action="{{ path('admin_search') }}"
If UserRepository already exists and extend ServiceEntityRepository try to move findAllWithSearch to UserRepository.
If not your SearchRepository must looks like this
/**
* #method User|null find($id, $lockMode = null, $lockVersion = null)
* #method User|null findOneBy(array $criteria, array $orderBy = null)
* #method User[] findAll()
* #method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserSearchRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findByEmail(string $email)
{
return $this->findBy(['email' => $email]);
}
}
I am new on Laravel and use Authorization. I am looking for the way to change default sql for Auth. Actually, Laravel does it using this simple sql command at below:
SELECT * FROM users WHERE login="something" AND password = "something" LIMIT 1
I am trying to change default sql like this:
SELECT
u.id, u.name, c.company
FROM
users u, companies c
WHERE
u.login="something" AND
u.password = "something" AND
u.companyId = c.id
LIMIT 1
I understood that I should create custom Authorization system: crate new user Provider and Auth Provider.
Firstly, I created Auth folder inside App and added there CustomUserProvider.php
CustomUserProvider.php
<?php namespace App\Auth;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider as UserProviderInterface;
use App\Models\User;
class CustomUserProvider implements UserProviderInterface {
protected $model;
public function __construct(UserContract $model)
{
$this->model = $model;
}
public function retrieveById($identifier)
{
}
public function retrieveByToken($identifier, $token)
{
}
public function updateRememberToken(UserContract $user, $token)
{
}
public function retrieveByCredentials(array $credentials)
{
}
public function validateCredentials(UserContract $user, array $credentials)
{
}
}
My customAuthProvider.php file, in App/Providers:
<?php namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Auth\CustomUserProvider;
use Illuminate\Support\ServiceProvider;
class CustomAuthProvider extends ServiceProvider {
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
$this->app['auth']->extend('custom',function()
{
return new CustomUserProvider(new User);
});
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
At the end I set driver to custom in config/Auth.php
'driver' => 'custom'
I am looking for the way using this custom classes how can I use custom sql command for Authorization (Login)?
Or maybe this way is wrong?
If all you need are additional constraints on the query that fetches user from the database during authentication, there is much simpler way to do that.
First of all, Laravel offers an AuthenticatesUsers trait that you can use in your controller to handle authentication requests. The default implementation fetches user from the database using username field and then, if matching user is found, it validates their password.
The list of attributes that is used to fetch user from the database can be customized by overriding getCredentials method in your controller. In your case the following should be enough to load user using their username and company id:
protected function getCredentials(Request $request)
{
return $request->only($this->loginUsername(), 'password', 'companyId);
}
Once you add that, user should provide their username, companyId and password in the login form and they will be authenticated only if there exists a user with given username that belongs to given company and the password provided is valid.
UPDATE: If you decide not to use the trait, but want to authenticate users manually, you can do so in a really similar manner. When calling Auth::attempt() you just need to pass all the criteria that should be used to authenticate the user, e.g.:
Auth::attempt([
'username' => Input::get('username'),
'companyId' => Input::get('companyId'),
'password' => Input::get('password')
]);
I tried this package and it helped me:
https://github.com/ollieread/multiauth/
I was watching this lesson and was trying to figure out which directory to put the EmailNotifier class file since it's an Event.
I don't know if it belongs in App\Events or App\Handlers\Events.
This is what I currently have:
<?php namespace App\Mailers;
use Illuminate\Mail\Mailer as Mail;
abstract class Mailer {
private $mail;
function __construct(Mail $mail)
{
$this->mail = $mail;
}
public function sendTo($user, $subject, $view, $data)
{
$this->mail->queue($view, $data, function ($message) use ($user, $subject)
{
$message->to($user->email)->subject($subject);
});
}
}
<?php namespace App\Mailers;
use App\User;
class UserMailer extends Mailer {
/**
* #param User $user
*/
public function sendWelcomeMessageTo(User $user)
{
$subject = 'Welcome To Backstage!';
$view = 'emails.registeration.confirm';
$data = [];
return $this->sendTo($user, $subject, $view, $data);
}
}
<?php namespace App\Handlers\Events;
class EmailNotifier extends Event {
private $mailer;
public function __construct(UserMailer $mailer)
{
$this->mailer = $mailer;
}
public function whenUserHasRegistered(UserHasRegistered $event)
{
$this->mailer->sendWelcomeMessageTo($event->user);
}
}
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class UserHasRegistered extends Event {
use SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
//
}
}
That's more of a discretionary concern. You generally want to categorize similar purpose items into the same namespace. Handlers\Events sounds like a place to put event handlers maybe, or perhaps it's a place for events that stem from handlers.
It sounds like you are on the right track placing an event in an Events namespace. Convention and consistency is the key. It doesn't matter so much as to what the final namespace is, just as long as it is consistent. IMO a more logical approach would be to have App\Event for all of your events and potentially sub namespace it from there for event categories. Handlers would be more self explanatory if they were somewhere like App\EventHandler and again sub namespaced into groups as needed.
That way it is pretty clear to an outsider who may need to work with your code in the future. That's my two cents as far as a general organization structure goes.
With deeper context into Laravel as the link laracasts.com implies. The App\Event namespace is for events which is what your EmailNotifier looks to be, where App\Handlers\Events is generally for handlers, subscribers, listeners, whatever you want to call them.
I have this method in my Controller that from indexAction() is called several times. Should I keep it as it is, or should I create Helper Class (Service) for it and other reusable methods, since I could avoid passing arguments such as $em in this case? I can't understand a context of services and when it is comfortable to use them.
public function getProfile($em, $username, $password) {
$dql = $em->createQuery('SELECT Profiles FROM ProjectProjectBundle:Profiles AS Profiles WHERE Profiles.email = :email AND Profiles.password = :password')
->setParameters(array(
'email' => $username,
'password' => $password
));
return $dql->getArrayResult();
}
First of all, know this: You should never, never, never have SQL/DQL in your controllers. Never.
Secondly, you have a few options, but I'm only going to outline one. I'm assuming you have Entities defined so let's start with an options based on that.
Tell doctrine where the Profiles entity's repository is located
src/Project/ProjectBundle/Entity/Profiles.php
/**
* #ORM\Table()
* #ORM\Entity(repositoryClass="Project\ProjectBundle\Entity\ProfilesRepository")
*/
class Profiles {}
Create the ProfilesRepository class and imlement a custom finder
src/Project/ProjectBundle/Entity/ProfilesRepository.php
namespace Project\ProjectBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ProfilesRepository extends EntityRepository
{
/**
* Find a Profiles entity with the given credentials
*
* #return \Project\ProjectBundle\Entity\Profiles|null
*/
public function findByCredentials($username, $password)
{
return $this->findBy(array('email' => $username, 'password' => $password ));
}
}
Update the convenience method in your controller
/**
* Fetch a user's profile
*
* #return \Project\ProjectBundle\Entity\Profiles
*/
private function fetchProfile($username, $password)
{
try { // Exception-safe code is good!
$profilesRepo = $this->getDoctrine()->getEntityRepository('ProjectProjectBundle:Profiles');
return $profilesRepo->findByCredentials($username, $password);
}
catch (\Exception $e)
{
// Do whatever you want with exceptions
}
return null;
}
A few notes on the proposal
I ditched the DQL in favor of Repository finders
I ditched the array representation of rows in favor of Entity representations. As long as you're actually using Entities, this is preferable.
I renamed getProfile() to fetchProfile() since it's a better description for what the method does. I also made it private because that's just good secure coding practice.
Use the EntityRepositories
// src/YourProjectName/BundleName/Entity/ProfileRepository.php
<?php
namespace YourProjectName\BundleName\Entity;
use Doctrine\ORM\EntityRepository;
class ProfileRepository extends EntityRepository
{
public function myGetProfile($username, $password)
{
$dql = $this->getEntityManager()->createQuery('SELECT Profiles FROM ProjectProjectBundle:Profiles AS Profiles WHERE Profiles.email = :email AND Profiles.password = :password')
->setParameters(array(
'email' => $username,
'password' => $password
));
return $dql->getArrayResult();
}
}
Then from any controllers you'll be able to do
$repository = $em->getRepository('YourBundle:Profile');
$repository->myGetProfile('username', 'password');
I would use an entity reposity (Just posted this and realised Isaac has just posted so please refer)
/** Repository Class **/
<?php
namespace YourProjectName\BundleName\Entity;
use Doctrine\ORM\EntityRepository;
class ProfileRepository extends EntityRepository {
public function myGetProfile($username, $password) {
return $this->getEntityManager()
->createQueryBuilder('p')
->from('ProjectProjectBundle:Profiles','p')
->where('p.email = :email')
->andWhere('p.password = :password')
->setParameters(['email' => $email, 'password' => $password])
->getQuery()
->getArrayResult();
}
}
/** In a controller **/
$results = $em->getRepository('ABundle:Profile')->myGetProfile($username, $password);
Also don't forget to add this repository to your enity class or it will not work
If you have one main function that is called I would also recommend shorting this with #ParamConverter using repository method.
The first distinction I would make is that you grouped Helper Classes and Services as the same thing. You might have a global helper class for formatting strings or working with filepaths, but that wouldn't make a good service.
Re-reading http://symfony.com/doc/current/book/service_container.html provided a good analogy with a Mailer service. Maybe in your case you'd want a Profiler service to manage your Profile objects.
$profiler = $this->get('profile_manager');
$current_profile = $profiler->getProfile();
$other_profile = $profiler->getProfile('username','password');