am new with symfony and i need some advices - symfony

I have a problem regarding a Symfony application, I want to take as input the "username" or "id" for my controller , and receive information that is in my table "user" and also 2 other table for example : A user has one or more levels , and also it has points must earn points to unlock a level , I want my dan Home page display the username and the level and extent that it has , I jn am beginner and not come to understand the books symfony that I use, I work with PARALLEL " symfony_book " and " symfony_cook_book " and also tutorial youtube May I blocks , here is the code for my cotroler
"
/**
* #Route("/{id}")
* #Template()
* #param $id=0
* #return array
*/
public function getUserAction($id)
{
$username = $this->getDoctrine()
->getRepository('voltaireGeneralBundle:FosUser')
->find($id);
if (!$username) {
throw $this->createNotFoundException('No user found for id '.$id);
}
//return ['id' => $id,'username' => $username];
return array('username' => $username);
}
and I have to use the relationship among classes
use Doctrine\Common\Collections\ArrayCollection;
class Experience {
/**
* #ORM\OneToMany(targetEntity="FosUser", mappedBy="experience")
*/
protected $fosUsers;
public function __construct()
{
$this->fosUsers = new ArrayCollection();
}
}
and
class FosUser {
/**
* #ORM\ManyToOne(targetEntity="Experience", inversedBy="fosUsers")
* #ORM\JoinColumn(name="experience_id", referencedColumnName="id")
*/
protected $fosUsers;
}
and i have always an error

In Symfony you cannot return an array in Action function!, Action function must always return a Response object...So if you want to return data to browser in Symfony, Action function have to return a string wrapped up in Response object.
In your controller code, to return the array to browser, You can serialize an array to JSON and send it back to browser:
public function getUserAction($id)
{
$username = $this->getDoctrine()
->getRepository('voltaireGeneralBundle:FosUser')
->find($id);
if (!$username) {
throw $this->createNotFoundException('No user found for id '.$id);
}
return new Response(json_encode(array('username' => $username)));
}
I suggest you to read more about HTTP protocol , PHP, and Symfony.

Related

Trying to use notifier in an event Subscriber

I'm learnig the event Subscriber on Symfony, my idea is when I create a new blog post, I want to send a notification. I'm already able to get the last entity created.
I've added to code to get the Symfony notifier and be able to send a notification on Disocrd. I currently have the following code :
class EasyAdminSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
AfterEntityPersistedEvent::class => ['setBlogPostSlug'],
];
}
/**
* #param AfterEntityPersistedEvent $event
* #param ChatterInterface $chatter
* #return void
* #throws \Symfony\Component\Notifier\Exception\TransportExceptionInterface
*/
public function setBlogPostSlug(AfterEntityPersistedEvent $event)
{
$chatter = new Chatter();
$entity = $event->getEntityInstance();
if (!($entity instanceof Blog)) {
return;
}
$message = (new ChatMessage('You got a new invoice for 15 EUR.'));
// if not set explicitly, the message is send to the
// default transport (the first one configured)
$chatter->send($message);
}
}
But when I run the code I have the followinf error :
App\EventSubscriber\EasyAdminSubscriber::setBlogPostSlug(): Argument #2 ($chatter) must be of type Symfony\Component\Notifier\ChatterInterface, string given
I don't understand what's wrong. Do you any idea why ? And how to make things work ?

Twilio Symfony - The controller must return a \"Symfony\\Component\\HttpFoundation\\Response\" but returned Twilio\\TwiML\\VoiceResponse."

I want to implement Twilio browser to browser call with Symfony5 and ApiPlatform
I'm following this tuto:
https://www.twilio.com/docs/voice/client/tutorials/calls-between-devices?code-sample=code-generate-twiml-from-client-parameters-3&code-language=PHP&code-sdk-version=5.x
I have this function, that's the one I want my TwiML app to be configured on
/**
* #Route("/twilio/handle/twiml/{clientId}", name="twilio_handl_twiml")
* #param $clientId
* #return VoiceResponse
*/
public function handleTwiml($clientId): VoiceResponse
{
/** #var Client $client */
$client = $this->clientRepository->findOneBy(['id' => 11]);
$to = $client->getUser()->getLastName().$client->getUser()->getId();
$voiceResponse = new VoiceResponse();
$number = htmlspecialchars($to);
$dial = $voiceResponse->dial(null, array('callerId' => '+15017122661'));
if (isset($to)) {
if (preg_match("/^[\d\+\-\(\) ]+$/", $number)) {
$dial->number($number);
} else {
$dial->client($number);
}
} else {
$voiceResponse->say('There has been an issue. Thanks for calling!');
}
return $voiceResponse;
}
And I've declared it as a custom route on one of my entities in the "get" section:
* "twilio_handl_twiml"={
* "path"="/twilio/handle/twiml/{clientId}",
* "controller"="TwilioController:class"
* },
Now the function creates a proper VoiceResponse object
But when I call this route I get the following error message:
The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned an object of type Twilio\TwiML\VoiceResponse.
Now does anyone know why I couldn't return whatever kind of Response I want from a custom route ?
I don't really see why the framework would declare this as an error
If anyone can help me understand better this error I'd appreciate it
Thanks!
Twilio developer evangelist here.
As #Cerad has said in the comments, you need to respond with an object derived from the Symfony Response object.
I haven't used Symfony, so please excuse me if this is wrong, but I think you can update your handler to the following, it might work:
use Symfony\Component\HttpFoundation\Response;
/**
* #Route("/twilio/handle/twiml/{clientId}", name="twilio_handl_twiml")
* #param $clientId
* #return Response
*/
public function handleTwiml($clientId): VoiceResponse
{
/** #var Client $client */
$client = $this->clientRepository->findOneBy(['id' => 11]);
$to = $client->getUser()->getLastName().$client->getUser()->getId();
$voiceResponse = new VoiceResponse();
$number = htmlspecialchars($to);
$dial = $voiceResponse->dial(null, array('callerId' => '+15017122661'));
if (isset($to)) {
if (preg_match("/^[\d\+\-\(\) ]+$/", $number)) {
$dial->number($number);
} else {
$dial->client($number);
}
} else {
$voiceResponse->say('There has been an issue. Thanks for calling!');
}
$response = new Response(
$voiceResponse->asXML(),
Response::HTTP_OK,
['content-type' => 'application/xml']
);
return $response;
}
The key here is to build up the Symfony response with the content of the voice response ($voiceResponse->asXML()) and also set the content type to application/xml.

Make security available for doctrine onFlush within functional test

I'm actually testing my api code written with:
symfony 4
api-platform
FOS User
JWT
I use codeption for my tests and everything is ok so far.
For several entities, I fire onFlush doctrine callback and it's working just fine when authenticated from my front application in react.
At this point I get my authenticated user in the callback via an injected security component.
However when doing the same things via codeception, even if onFlush is fired, I'm not able to retrieve my user neither the token via the security injection.
I tried to inject the token instead, also the entire service container, none has worked.
This is my OnFlush class:
{
/**
* #var Security
*/
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function onFlush(OnFlushEventArgs $args): void
{
$user = $this->security->getUser();
...
And here how I set my authorization header in codeception test:
$I->haveHttpHeader('Authorization', 'Bearer ' . $token);
$I->sendPUT(
'/entity/uuid.json',
[
'attribute' => $value
]
);
I would like to get the user having the specified token whe executing the test in the callback.
PS: Before executing the PUT test, I did the same thing with GET and just got the related entities, when I remove Authorization header I do get all users entities. It seems that it's not working only in callback.
Thanks
After a lot research, it's obviously a codeception problem.
I ended up making this particular test with phpunit as codeception couldn't load the service container in doctrine events.
If you try to edit your services.yaml file and to execute your tests, it works on first time as the service container is re-built (re-cached).
But once cached, it will always return an empty container (without tokenstrorage, security, ...).
Creating a helper method to provide the user wouldn't work neither, I'll leave the code here in case of need:
/**
* Create user or administrator and set auth cookie to client
*
* #param string $user
* #param string $password
* #param bool $admin
*/
public function setAuth(string $user, string $password, bool $admin = false): void
{
/** #var Symfony $symfony */
try {
$symfony = $this->getModule('Symfony');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Symfony\'');
}
/** #var Doctrine2 $doctrine */
try {
$doctrine = $this->getModule('Doctrine2');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Doctrine2\'');
}
$user = $doctrine->grabEntityFromRepository(User::class, [
'username' => $user
]);
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$symfony->grabService('security.token_storage')->setToken($token);
/** #var Session $session */
$session = $symfony->grabService('session');
$session->set('_security_main', serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$symfony->client->getCookieJar()->set($cookie);
}
Creating a phpunit test with below code would do the job just fine:
/**
* #param string $method
* #param string $url
* #param array $content
* #param bool $authorization
*/
protected static function performRequest (string $method, string $url, array $content = [], $authorization = false): void
{
$headers = [
'CONTENT_TYPE' => 'application/json',
];
if ($authorization)
{
$headers = array_merge($headers, [
'HTTP_AUTHORIZATION' => 'Bearer ' . self::$token
]);
}
self::$client->request(
$method,
'/api/' . $url,
[],
[],
$headers,
json_encode($content)
);
}
I got exactly the same problem. Here is my solution:
<?php
use Codeception\Stub;
use Codeception\Module\Doctrine2;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Security;
class YourEventSubscriberCest
{
/**
* #var Security
*/
protected Security $security;
/**
* #param FunctionalTester $I
*/
public function _before(FunctionalTester $I, Doctrine2 $doctrine2)
{
$user = new User();
$security = Stub::makeEmpty(Security::class, [
'getUser' => $user,
]);
$backup = $this->replaceSecurity($doctrine2, $security);
if ($backup instanceof Security) {
$this->security = $backup;
}
}
public function _after(FunctionalTester $I, Doctrine2 $doctrine2)
{
$this->replaceSecurity($doctrine2, $this->security);
}
protected function replaceSecurity(Doctrine2 $doctrine2, object $newSecurity): ?object
{
$listeners = $doctrine2->_getEntityManager()->getEventManager()->getListeners('onFlush');
foreach ($listeners as $listener) {
if ($listener instanceof YourEventSubscriber) {
$reflection = new \ReflectionObject($listener);
$property = $reflection->getProperty('security');
$property->setAccessible(true);
$oldSecurity = $property->getValue($listener);
$property->setValue($listener, $newSecurity);
return $oldSecurity;
}
}
}
}

Laravel 5.8 Custom Email and password Columns

I have a WordPress running application, which I would like to access using a separate interface that uses Laravel 5.8.(don't worry about the hashing)
As such, instead of cloning passwords back and forth, I would like to use the user_email and user_pass columns in the Laravel User model instead.
I have tried what the official docs say :
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* Handle an authentication attempt.
*
* #param \Illuminate\Http\Request $request
*
* #return Response
*/
public function authenticate(Request $request)
{
$credentials = $request->only('user_email', 'user_pass');
if (Auth::attempt($credentials)) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}
I then edited the blade files, but no avail. Any pointers?
Laravel provides a way to change the default columns for auth (email, password) by overriding some functions.
In your User model add this function that overrides the default column for password:
App/User.php
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->user_pass;
}
And, in your LoginController change from email to user_email
App/Http/Controllers/Auth/LoginController.php
/**
* Get the login username to be used by the controller.
*
* #return string
*/
public function username()
{
return 'user_email';
}
Now you have overridden the default columns used by Laravel's Auth logic. But you are not finished yet.
LoginController has a function that validates the user's input and the password column is hardcoded to password so in order to change that, you also need to add these functions in LoginController:
App/Http/Controllers/Auth/LoginController.php
/**
* Validate the user login request.
*
* #param \Illuminate\Http\Request $request
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function validateLogin(Request $request)
{
$request->validate([
$this->username() => 'required|string',
'user_pass' => 'required|string',
]);
}
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only($this->username(), 'user_pass');
}
Next step is to create a custom Provider, let's call it CustomUserProvider that will be used instead of the default one EloquentUserProvider and where you will override the password field.
App/Providers/CustomUserProvider.php
<?php
namespace App\Providers;
class CustomUserProvider extends EloquentUserProvider
{
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('user_pass', $credentials))) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (Str::contains($key, 'user_pass')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
return $query->first();
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['user_pass'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
}
Now that you extended the default provider you need to tell Laravel to use this one instead of EloquentUserProvider. This is how you can do it.
App/Providers/AuthServiceProvider.php
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
$this->app->auth->provider('custom', function ($app, $config) {
return new CustomUserProvider($app['hash'], $config['model']);
});
}
Finally update the config information config/auth.php and change the driver from eloquent to custom (that's how I named it above; you can change it to whatever you want). So the config/auth.php file should have this bit:
'providers' => [
'users' => [
'driver' => 'custom',
'model' => App\User::class,
],
],
Hope it helps!
Regards
It would be up and working, If you can just use sessions here instead of using Auth::attempt just like working on core PHP.

Symfony2 and ParamConverter(s)

Accessing my route /message/new i'm going to show a form for sending a new message to one or more customers. Form model has (among others) a collection of Customer entities:
class MyFormModel
{
/**
* #var ArrayCollection
*/
public $customers;
}
I'd like to implement automatic customers selection using customers GET parameters, like this:
message/new?customers=2,55,543
This is working now by simply splitting on , and do a query for getting customers:
public function newAction(Request $request)
{
$formModel = new MyFormModel();
// GET "customers" parameter
$customersIds = explode($request->get('customers'), ',');
// If something was found in "customers" parameter then get entities
if(!empty($customersIds)) :
$repo = $this->getDoctrine()->getRepository('AcmeHelloBundle:Customer');
$found = $repo->findAllByIdsArray($customersIds);
// Assign found Customer entities
$formModel->customers = $found;
endif;
// Go on showing the form
}
How can i do the same using Symfony 2 converters? Like:
public function newAction(Request $request, $selectedCustomers)
{
}
Answer to my self: there is not such thing to make you life easy. I've coded a quick and dirty (and possibly buggy) solution i'd like to share, waiting for a best one.
EDIT WARNING: this is not going to work with two parameter converters with the same class.
Url example
/mesages/new?customers=2543,3321,445
Annotations:
/**
* #Route("/new")
* #Method("GET|POST")
* #ParamConverter("customers",
* class="Doctrine\Common\Collections\ArrayCollection", options={
* "finder" = "getFindAllWithMobileByUserQueryBuilder",
* "entity" = "Acme\HelloBundle\Entity\Customer",
* "field" = "id",
* "delimiter" = ",",
* }
* )
*/
public function newAction(Request $request, ArrayCollection $customers = null)
{
}
Option delimiter is used to split GET parameter while id is used for adding a WHERE id IN... clause. There are both optional.
Option class is only used as a "signature" to tell that converter should support it. entity has to be a FQCN of a Doctrine entity while finder is a repository method to be invoked and should return a query builder (default one provided).
Converter
class ArrayCollectionConverter implements ParamConverterInterface
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
function apply(Request $request, ConfigurationInterface $configuration)
{
$name = $configuration->getName();
$options = $this->getOptions($configuration);
// Se request attribute to an empty collection (as default)
$request->attributes->set($name, new ArrayCollection());
// If request parameter is missing or empty then return
if(is_null($val = $request->get($name)) || strlen(trim($val)) === 0)
return;
// If splitted values is an empty array then return
if(!($items = preg_split('/\s*'.$options['delimiter'].'\s*/', $val,
0, PREG_SPLIT_NO_EMPTY))) return;
// Get the repository and logged user
$repo = $this->getEntityManager()->getRepository($options['entity']);
$user = $this->getSecurityContext->getToken()->getUser();
if(!$finder = $options['finder']) :
// Create a new default query builder with WHERE user_id clause
$builder = $repo->createQueryBuilder('e');
$builder->andWhere($builder->expr()->eq("e.user", $user->getId()));
else :
// Call finder method on repository
$builder = $repo->$finder($user);
endif;
// Edit the builder and add WHERE IN $items clause
$alias = $builder->getRootAlias() . "." . $options['field'];
$wherein = $builder->expr()->in($alias, $items);
$result = $builder->andwhere($wherein)->getQuery()->getResult();
// Set request attribute and we're done
$request->attributes->set($name, new ArrayCollection($result));
}
public function supports(ConfigurationInterface $configuration)
{
$class = $configuration->getClass();
// Check if class is ArrayCollection from Doctrine
if('Doctrine\Common\Collections\ArrayCollection' !== $class)
return false;
$options = $this->getOptions($configuration);
$manager = $this->getEntityManager();
// Check if $options['entity'] is actually a Dcontrine one
try
{
$manager->getClassMetadata($options['entity']);
return true;
}
catch(\Doctrine\ORM\Mapping\MappingException $e)
{
return false;
}
}
protected function getOptions(ConfigurationInterface $configuration)
{
return array_replace(
array(
'entity' => null,
'finder' => null,
'field' => 'id',
'delimiter' => ','
),
$configuration->getOptions()
);
}
/**
* #return \Doctrine\ORM\EntityManager
*/
protected function getEntityManager()
{
return $this->container->get('doctrine.orm.default_entity_manager');
}
/**
* #return \Symfony\Component\Security\Core\SecurityContext
*/
protected function getSecurityContext()
{
return $this->container->get('security.context');
}
}
Service definition
arraycollection_converter:
class: Acme\HelloBundle\Request\ArrayCollectionConverter
arguments: ['#service_container']
tags:
- { name: request.param_converter}
It's late, but according to latest documentation about #ParamConverter, you can achieve it follow way:
* #ParamConverter("users", class="AcmeBlogBundle:User", options={
* "repository_method" = "findUsersByIds"
* })
you just need make sure that repository method can handle comma (,) separated values

Resources