Yii2 redirecting postman request to a different route in the api rest - rest

I am accessing a route by the postman in an api made in Yii2, but the code that I insert in the action that corresponds to that route is not working. Follow the request print:
postman-request-print
The request return was not meant to be the one in the image, because the code I put in the 'create' action was this:
<?php
namespace app\modules\api\controllers;
use Yii;
use app\modules\pesquisa_mercado\models\PontoDaPesquisa;
class PesquisaPontoController extends \yii\rest\ActiveController
{
public $modelClass = 'app\modules\pesquisa_mercado\models\PesquisaPonto';
public function behaviors()
{
$behaviors = parent::behaviors();
return $behaviors + [
[
'class' => \yii\filters\auth\HttpBearerAuth::className(),
'except' => ['options'],
],
'verbs' => [
'class' => \app\modules\api\behaviors\Verbcheck::className(),
'actions' => [
'index' => ['GET'],
'create' => ['POST'],
'update' => ['PUT'],
'view' => ['GET'],
'delete' => ['DELETE'],
'finalizar-pesquisa' => ['POST'],
],
],
];
}
public function actions()
{
$actions = parent::actions();
unset($actions['update']);
return $actions;
}
public function actionCreate()
{
die("Test"); // test inserted here
}
}
That is, the return was to be 'Test'. For some reason I don't know, the route is being redirected to another place.
I also discovered that the request goes through the getLinks () method present in the PesquisaPonto model:
<?php
namespace app\modules\pesquisa_mercado\models;
class PesquisaPonto extends \yii\db\ActiveRecord implements \yii\web\Linkable
{
/**
* #inheritdoc
*/
public static function tableName()
{
return '{{%pesquisa_ponto}}';
}
/**
* #inheritdoc
*/
public function getLinks() // the requisition also passes through here!
{
return [
Link::REL_SELF => Url::to(['pesquisa-ponto/view', 'id' => $this->id], true),
'index' => Url::to(['pesquisa-ponto'], true)
];
}
}
Also follow the configuration of urlManager:
'urlManager' => [
'class' => 'yii\web\UrlManager',
'enablePrettyUrl' => true,
'showScriptName' => true,
'rules' => [
// Pontos de Pesquisa
// api/pesquisa-ponto
[
'class' => 'yii\rest\UrlRule',
'controller' => [
'api/pesquisa-ponto'
],
'pluralize' => false,
],
],
]
I still haven't found the reason why Yii2 is redirecting the route and not allowing the postman to access the 'create' action...

The actions() method in yii\rest\ActiveController looks like this
public function actions()
{
return [
// ...
'create' => [
'class' => 'yii\rest\CreateAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'scenario' => $this->createScenario,
],
// ...
];
}
In your implementation of actions() method you are only removing the configuration for update action but the configuration for create action is left untouched. That means that the action is run from the yii\rest\CreateAction not the actionCreate() method of the controller.
To run the action from the PesquisaPontoController::actionCreate() you have to unset the configuration for the create action as well.
public function actions()
{
$actions = parent::actions();
unset($actions['update'], $actions['create']);
return $actions;
}

Related

Symfony TypeTestCase How to populate collection of embedded form using mock?

For a simple blog manager where I want to associate some tags to some Post I have create a repository able to create a new entity when it's name can't be found in database:
public function requireByName(string $name): PostTag
{
$result = $this->findOneBy(['name' => [$name]]);
if (!$result instanceof PostTag) {
$result = $this->create($name);
$this->getEntityManager()->flush();
}
return $result;
}
I have managed to create a form TagType populating PostTag entity with the results of the PostTagRepository::requireByName method:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('name', TextType::class, [
'setter' => function (PostTag &$tag, string $name) {
$tag = $this->tagRepository->requireByName($name);
},
]);
}
All of this is tested with the instruction provided in Symfony documentation How to unit test your form
public function testItSetsRepositoryResultToInboundModel(): void
{
$formTagName = 'my new tag';
$formData = ['name' => $formTagName];
$createdPostTag = new PostTag($formTagName);
$this->tagRepository->method('requireByName')
->with($formTagName)
->willReturn($createdPostTag);
$this->testedForm->submit($formData);
self::assertTrue($this->testedForm->isSynchronized());
$formEntity = $this->testedForm->getData();
self::assertInstanceOf(PostTag::class, $formEntity);
self::assertSame($createdPostTag, $formEntity);
}
public function testItMapsTagsWithItsNameInFormField(): void
{
$tagName = 'my new tag';
$formData = new PostTag($tagName);
$form = $this->factory->create(TagType::class, $formData);
self::assertTrue($form->has('name'));
self::assertEquals($tagName, $form->get('name')->getData());
}
My next step is to create a TypeTestCase for my PostType form where I can assume that results from PostTagRepository are populated in my Post entity once I submit an array of tag names. Here is the code I produced so far (I load some extensions - Validator, CKEditor - required from the legacy behavior) :
class PostTypeTest extends TypeTestCase
{
use ValidatorExtensionTrait;
private PostTagRepository&MockObject $tagRepository;
protected function setUp(): void
{
$this->tagRepository = $this->createMock(PostTagRepository::class);
parent::setUp(); // TODO: Change the autogenerated stub
}
/** #return FormExtensionInterface[]
*
* #throws Exception
*/
protected function getExtensions(): array
{
$tagType = new TagType($this->tagRepository);
return [
$this->getValidatorExtension(),
$this->getCKEditorExtension(),
new PreloadedExtension([$tagType], []),
];
}
private function getCKEditorExtension(): PreloadedExtension
{
$CKEditorType = new CKEditorType($this->createMock(CKEditorConfigurationInterface::class));
return new PreloadedExtension([$CKEditorType], []);
}
public function testItMapsRepositoryResultToTagCollection(): void
{
$model = new Post();
$form = $this->factory->create(PostType::class, $model);
$requiredTag = new PostTag('poo');
$this->tagRepository->method('requireByName')
->with('poo')
->willReturn($requiredTag);
$form->submit([
'title' => 'a title',
'content' => 'some content',
'isPublic' => true,
'tags' => [
['name' => 'poo'],
],
]);
self::assertTrue($form->isSynchronized());
self::assertNotEmpty($model->getTags());
}
}
And here is the PostType code :
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title', TextType::class, [
'label' => 'Titre',
'required' => true,
'constraints' => [new Assert\NotBlank(), new Assert\Length(['min' => 5, 'max' => 255])],
]
)
->add(
'content',
CKEditorType::class,
[
'required' => true,
'label' => 'Contenu',
'config' => [
'filebrowserBrowseRoute' => 'elfinder',
'filebrowserBrowseRouteParameters' => [
'instance' => 'default',
'homeFolder' => '',
],
],
]
)
->add('isPublic', CheckboxType::class, [
'label' => 'Le Post est public',
'required' => false,
])
->add('tags', CollectionType::class, [
'entry_type' => TagType::class,
'entry_options' => ['label' => false],
])
->add('submit', SubmitType::class, [
'label' => 'app.actions.validate',
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(
[
'data_class' => Post::class,
]
);
}
}
All properties are populated with submitted values but the $model->getTags() Collection remains empty.
Help would be really appreciated if someone could tell me what I am doing wrong ?

ZEND 2 framework how to async navigation menu

I want to optimize page loading to asynchronously load navigation menu through Ajax.
Now it is working the standard way in layout.phtml using:
echo $this->navigation('CatalogNavigation')->menu()->setPartial('catalog_menu');
That line does not work in controller:
$view = $this->navigation('CatalogNavigation')->menu()->setPartial('catalog_menu'); //this line is not working
return new JsonModel(array('view' => $view()));
Here is what worked, the additional ->get('navigation'):
$navigation = $this->getServiceLocator()->get('viewHelperManager')->get('navigation');
$catalog_navigation = $navigation('CatalogNavigation');
$view = $catalog_navigation->menu()->setPartial('catalog_menu');
return new JsonModel(array( 'view' => (string)$view ));
Try code below:
//factory
class NavigationFactory extends DefaultNavigationFactory
{
protected function getName()
{
return 'navigation-example';
}
}
//module.config.php
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'factories' => array(
'translator' => 'Zend\Mvc\Service\TranslatorServiceFactory',
'navigation-example' => 'Application\Factory\NavigationFactory'
),
),
// global.php:
return array(
'navigation' => [
'navigation-example' => [
[
'label' => 'Google',
'uri' => 'https://www.google.com',
'target' => '_blank'
],
[
'label' => 'Home',
'route' => 'home'
]
]
]
);
// in controller
$viewHelperManager = $this->getServiceLocator()->get('viewHelperManager');
$navigation = $viewHelperManager->get('navigation');
$catalog_navigation = $navigation('navigation-example');
$menu = (string) $catalog_navigation->menu();
return new JsonModel(compact('menu'));

Queue is created but never executed

I'm trying to trigger push notifications and emails in the background of my Lumen API. I created the WarningUser class that creates both queues:
<?php
namespace App\Utils;
use App\Jobs\ProcessNotification;
use App\Mail\AllMail;
use Illuminate\Support\Facades\Mail;
class WarningUser
{
public static function send($user, $message, $url, $data = [])
{
$url = env('APP_URL_FRONT') . $url;
dispatch(new ProcessNotification($user, $data, $message, $url));
$emailData = EmailTexts::texts('pt', $data)[$message];
Mail::to($user->email)->queue(new AllMail($emailData['title'], $emailData['content']));
return true;
}
}
First we have the ProcessNotification job, which connects to Firebase and sends the notification:
<?php
namespace App\Jobs;
use Kreait\Firebase\Factory;
use Kreait\Firebase\Messaging\Notification;
use Kreait\Firebase\Messaging\CloudMessage;
class ProcessNotification extends Job
{
protected $user = null;
protected $data = null;
protected $message = null;
protected $url = null;
public function __construct($user, $data, $message, $url)
{
$this->user = $user;
$this->data = $data;
$this->message = $message;
$this->url = $url;
}
public function handle()
{
if (\is_string($this->user->token) and $this->user->token !== '') {
$messaging = (new Factory())
->withServiceAccount(__DIR__.'/../../private-key.json')
->createMessaging();
$notificationData = NotificationTexts::texts('pt', $this->data)[$this->message];
$messageAlert = CloudMessage::withTarget('token', $this->user->token)
->withNotification(Notification::create($notificationData['title'], $notificationData['content']))
->withData([ 'url' => $this->url ]);
$messaging->send($messageAlert);
}
}
}
And finally AllMail that sends a simple email:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class AllMail extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
protected $title;
protected $body;
public function __construct($title, $body)
{
$this->title = $title;
$this->body = $body;
}
public function build()
{
return $this
->subject($this->title)
->view('email')
->with([
'title' => $this->title,
'body' => $this->body
]);
}
}
I've tested both codes without using queues, and they work, but when I put the queue it stops working. The processes are recorded in my database (mongodb):
But the queue is never processed, I tried to execute php artisan queue: work andphp artisan queue: listen, but neither case works.
The queue is never attempted to be processed, nor does it go to the failed_jobs table
My config / queue.php is as follows. And my .env is with QUEUE_CONNECTION = database
<?php
return [
'default' => env('QUEUE_CONNECTION', 'database'),
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => 'localhost',
'queue' => 'default',
'retry_after' => 90,
'block_for' => 0,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'your-queue-name'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
],
],
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database'),
'database' => env('DB_CONNECTION', 'mongodb'),
'table' => 'failed_jobs',
],
];
Can someone help me? I no longer know what the error may be.
PS: At no time is a browser or console error displayed
I managed to solve, for Mongo we need to use some more settings:
The connection in queue.php must be:
'connections' => [
'database' => [
'driver' => 'mongodb',
'table' => 'jobs',
'queue' => 'default',
'expire' => 60,
],
...
And we need to register the package provider that works with mongo:
Jenssegers\Mongodb\MongodbQueueServiceProvider::class,

Zend ServiceManager using setter injection

in symfony i can use the setter injection for services via call option (https://symfony.com/doc/current/service_container/calls.html)
The example from the symfony documentation:
class MessageGenerator
{
private $logger;
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
// ...
}
service.yml
services:
App\Service\MessageGenerator:
# ...
calls:
- method: setLogger
arguments:
- '#logger'
I need this behaviour for my zend project. i want to inject a InputFilter into my FormFieldSet.
I didn't find anything about this in the zend documentation. Can i use something like this or exist a better solution for my problem in zend?
Based on this question and your previous question about Forms, Fieldsets and InputFilters, I'm thinking you want to achieve something similar to the following use case.
Use case
You have a
Location Entity
Address Entity
Location has a OneToOne to an Address (required, uni-directional)
Requirements
To manage the Location, you'll need:
LocationForm (-Factory)
LocationFormInputFilter (-Factory)
LocationFieldset (-Factory)
LocationFieldsetInputFilter (-Factory)
AddressFieldset (-Factory)
AddressFieldsetInputFilter (-Factory)
Configuration
To configure this in ZF3, you'll have to do add the following
'form_elements' => [
'factories' => [
AddressFieldset::class => AddressFieldsetFactory::class,
LocationForm::class => LocationFormFactory::class,
LocationFieldset::class => LocationFieldsetFactory::class,
],
],
'input_filters' => [
'factories' => [
AddressFieldsetInputFilter::class => AddressFieldsetInputFilterFactory::class,
LocationFormInputFilter::class => LocationFormInputFilterFactory::class,
LocationFieldsetInputFilter::class => LocationFieldsetInputFilterFactory::class,
],
],
Forms & Fieldsets
In the LocationForm, add your LocationFieldset and what else your Form needs, such as CSRF and submit button.
class LocationForm extends AbstractForm
{
public function init()
{
$this->add([
'name' => 'location',
'type' => LocationFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
//Call parent initializer. Adds CSRF & submit button
parent::init();
}
}
(Note: my AbstractForm does a bit more, I would suggest you have a look here, such as remove empty (child fieldsets/collections) Inputs so data is not attempted to be created in the DB)
In the LocationFieldset, give add Inputs for the Location, such as a name, and the AddressFieldset:
class LocationFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Name'),
],
]);
$this->add([
'type' => AddressFieldset::class,
'name' => 'address',
'required' => true,
'options' => [
'use_as_base_fieldset' => false,
'label' => _('Address'),
],
]);
}
}
In the AddressFieldset just add Inputs for the Address Entity. (Same as above, without the Fieldset type Input)
InputFilters
To validate the Form, you can keep it very simple:
class LocationFormInputFilter extends AbstractFormInputFilter
{
/** #var LocationFieldsetInputFilter */
protected $locationFieldsetInputFilter;
public function __construct(LocationFieldsetInputFilter $filter)
{
$this->locationFieldsetInputFilter = $filter;
parent::__construct();
}
public function init()
{
$this->add($this->locationFieldsetInputFilter, 'location');
parent::init();
}
}
(The AbstractFormInputFilter adds CSRF validator)
Notice that we simply ->add() the LocationFieldsetInputFilter, but we give it a name (2nd parameter). This name is used later in the complete structure, so it's important to both keep it simple and keep it correct. Simplest is to give it a name that one on one matches the object of the Fieldset it's supposed to validate.
Next, the LocationFieldsetInputFilter:
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/**
* #var AddressFieldsetInputFilter
*/
protected $addressFieldsetInputFilter;
public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter)
{
$this->addressFieldsetInputFilter = $addressFieldsetInputFilter;
parent::__construct();
}
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'address'); // Again, name is important
$this->add(
[
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]
);
}
}
Factories
Now, you must bind them together, which is where your question about Setter injection comes from I think. This happens in the Factory.
A *FormFactory would do the following:
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$inputFilterPluginManager = $container->get('InputFilterManager');
$inputFilter = $inputFilterPluginManager->get(LocationFormInputFilter::class);
/** #var LocationForm $form */
$form = new LocationForm();
$form->setInputFilter($inputFilter); // The setter injection you're after
return $form;
}
A *FieldsetFactory would do the following (do the same for Location- and AddressFieldsets):
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var LocationFieldset $fieldset */
// name matters! Match the object to keep it simple. Name is used from Form to match the InputFilter (with same name!)
$fieldset = new LocationFieldset('location');
// Zend Reflection Hydrator, could easily be something else, such as DoctrineObject hydrator.
$fieldset->setHydrator(new Reflection());
$fieldset->setObject(new Location());
return $fieldset;
}
A *FormInputFilterFactory would do the following:
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$inputFilterPluginManager = $container->get('InputFilterManager');
/** #var LocationFieldsetInputFilter $locationFieldsetInputFilter */
$locationFieldsetInputFilter = $inputFilterPluginManager->get(LocationFieldsetInputFilter::class);
// Create Form InputFilter
$locationFormInputFilter = new LocationFormInputFilter(
$locationFieldsetInputFilter
);
return $locationFormInputFilter;
}
A *FieldsetInputFilterFactory would do the following:
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var AddressFieldsetInputFilter $addressFieldsetInputFilter */
$addressFieldsetInputFilter = $this->getInputFilterManager()->get(AddressFieldsetInputFilter::class);
$addressFieldsetInputFilter->setRequired(true);
return new LocationFieldsetInputFilter(
$addressFieldsetInputFilter
);
}
Note:
Setting an InputFilter as (not) required is something I've added here
If your InputFilter (such as AddressFieldsetInputFilter) does not have a child InputFilter, you can can skip getting the child and straight away return the new InputFilter.
I think I covered it all for a complete picture. If you have any questions about this, please comment.
What you need are Initializers from Zend Service Manager.
The initializer can be a class that is called whenever a service has been created.
In that class, you need to check the type of service that is created, and if it's appropriate type than inject whatever you want.
To register one Initializer add in config under service_manager key:
'service_manager' => [
'initializers' => [
MyInitializer::class
],
]
and then just create that class
class MyInitializer implements InitializerInterface
{
public function __invoke(ContainerInterface $container, $instance)
{
// you need to check should you inject or not
if ($instance instanceof MessageGenerator) {
$instance->setLogger($container->get('logger'));
}
}
}
You need to have registred MessageGenerator in zend-servicemanager also. In this way, when you try to retrive MessageGenerator from SM, after creation MyInitializer is called.

How to differentiate oauth social media plugin in YII2

I have a website which using twitter, facebook or google to login to system.
I use oAuth and here is my code.
config
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'facebook' => [
'class' => 'yii\authclient\clients\Facebook',
'clientId' => 'asdsad',
'clientSecret' => 'xzxas',
],
'twitter' => [
'class' => 'yii\authclient\clients\Twitter',
'consumerKey' => 'sadsd',
'consumerSecret' => 'dasdasd',
],
],
],
controller
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
'auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'oAuthSuccess'],
],
];
}
public function oAuthSuccess($client) {
// get user data from client
$userAttributes = $client->getUserAttributes();
echo '<pre>';
print_r($userAttributes);
die;
The question is how do i know that which one of social media the user use to log to system?
To differentiate your oauth client you can put some instance condition as:--
public function oAuthSuccess($client) {
$reponse = $client->getUserAttributes();
$session = Yii::$app->session;
$token = $client->accessToken->params['access_token'];
$session->set('token' ,$token);
$id = ArrayHelper::getValue($reponse , 'id');
$session->set('auth_id', $id);
//Facebook Oauth
if($client instanceof \yii\authclient\clients\Facebook){
//Do Facebook Login
}
//Google Oauth
elseif($client instanceof \yii\authclient\clients\GoogleOAuth){
//Do Google Login Condition
}
}
public function oAuthSuccess($client) {
// get user data from client
$userAttributes = $client->getUserAttributes();
if($client->getName() == 'twitter'){
........
}else if($client->getName() == 'facebook'){
.........
}