In symfony you can use the command make:crud. That works excellent with forms and twig in symfony. But is there also a way to do it with api's? That I will send an POST to the route of the annotation.
Like in python;
url = 'https://127.0.0.1:8000/players/new'
myobj = {
'postName': 'postData',
}
This python code is used when i want to test a POST.
This is a piece of a make:crud what i used, only showing the New function of the CRUD. This only works with forms. I cant send directly a POST(ex, python) to it.
/**
* #Route("/players/new", name="players_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$player = new Players();
$form = $this->createForm(PlayersType::class, $player);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($player);
$entityManager->flush();
return $this->redirectToRoute('players_index');
}
return $this->render('players/new.html.twig', [
'player' => $player,
'form' => $form->createView(),
]);
}
I changed your endpoint slightly to make it more API friendly. In all the public facing APIs that I've built they all return JSON. That just eases the burdens for implementation. I always use a status 201 for creation, and 400 for bad requests. This serves a traditional role for RESTful API paradigms and implementations.
/**
* #Route("/players/{player}", name="get_player", methods={"GET"})
*/
public function getPlayer(Player $player): Response {
// You might need to tweak based on your Entity name
return new JsonResponse($player);
}
/**
* #Route("/players/new", name="players_new", methods={"POST"})
*/
public function newPlayer(Request $request): Response
{
$player = new Players();
$form = $this->createForm(PlayersType::class, $player);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($player);
$entityManager->flush();
} catch (\Exception $e) {
// Probably a better exception to catch here, log it somewhere
throw $e;
}
return new JsonResponse('', Response::HTTP_CREATED);
}
return new JsonResponse(
'Invalid data provided to API, please refer to documentation',
Response::HTTP_BAD_REQUEST
);
}
Changing the method from new to newPlayer was done because a method named new is confusing down the road. One thing I would also like to point out is that with Doctrine it's best to have your entities singular. Example: Player instead of Players. You can have the relationship be players within the Entity.
The Entity Game could have $players which is a relationship to OneToMany Player Entities.
Catching the exception on flush is good standard practice there. You should return a meaningful JsonResponse as well after you've logged it.
As per the official documentation, API platform is a “powerful but easy to use full-stack framework dedicated to API driven projects”. API platform helps developers significantly speed up their development process, building complex and high performance, hypermedia-driven APIs.
It ships with Symfony 4, the Doctrine ORM, a dynamic Javascript admin created with React, and React Admin, Varnish Cache server, Helm Chart to help deploy the API in a Kubernetes cluster and a Progressive Web Application skeleton. It also includes a Docker setup for providing Nginx servers to run the API and JavaScript apps. Most inspiring is the ability of API platform to natively generate project documentation with support of OpenAPI!
In this tutorial, I will take you through how to create a simple bucket list API with CRUD operations.
Prerequisites
PHP - Version 7.0 or higher.
Docker
Postgres
Getting Started
Follow the instructions below to setup your development environment:
$ mkdir demo-app
$ cd demo-app
Download the latest compressed .tar.gz distribution. Then extract it inside of our working directory and run the commands below:
$ cd api-platform-2.4.5
$ docker-compose pull
$ docker-compose up -d
The docker-compose pull command downloads all images specified in the docker-compose.yml file. In order to start the containers, run docker-compose up -d. The -d flag runs the containers in detached mode, meaning they run in the background. In order to view the container logs, you can run this command docker-compose logs -f in a separate terminal.
Related
Given the following pest test:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The above should fully work; however, the second request post('/courses')...
fails saying that:
Failed asserting that <...> contains "WebTechnologies".
If I remove the first request:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The test passes.
If I remove the second request instead:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
});
It also passes.
So why should the combination of the two cause them to fail? I feel Laravel is caching the original response, but I can't find anything within the documentation supporting this claim.
I have created an issue about this on Laravel/Sanctum as my problem was about authentication an stuff...
https://github.com/laravel/sanctum/issues/377
One of the maintainers of Laravel Said:
You can't perform two HTTP requests in the same test method. That's not supported.
I would have wanted a much clearer explanation on why it's not supported.
but I guess, we would never know. (Unless we dive deep into the Laravel framework and trace the request)
UPDATE:
My guess is that, knowing how Laravel works, for each REAL request Laravel initializes a new instance of the APP...
but when it comes to Test, Laravel Initializes the APP for each Test case NOT for each request, There for making the second request not valid.
here is the file that creates the request when doing a test...
vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php
it's on the call method line: 526 (Laravel v9.26.1)
as you can see...
Laravel only uses 1 app instance... not rebuilding the app...
Line 528: $kernel = $this->app->make(HttpKernel::class);
https://laravel.com/docs/9.x/container#the-make-method
the $kernel Variable is an instance of vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
My guess here is that the HttpKernel::class is a singleton.
P.S. I can do a little more deep dive, but I've procrastinated too much already by answering this question, it was fun thou.
TL;DR.
You can't perform two HTTP requests in the same test method. That's not supported.
UPDATE:
I was not able to stop myself...
I found Laravel initializing Kernel as a singleton
/{probject_dir}/bootstrap/app.php:29-32
Please make sure to not use any classic singleton pattern which isn't invoked with singleton binding or facades.
https://laravel.com/docs/9.x/container#binding-a-singleton
$this->app->singleton(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
The Laravel app won't be completely restarted during tests unlike different incoming HTTP requests - even if you call different API endpoints in your tests
I setup a Unit Test in a Shopware custom (static) Plugin following this guide:
Shopware documentation
Everything runs fine and I'm able to run a unit test
class ProductReturnsTest extends TestCase
{
use IntegrationTestBehaviour;
use StorefrontPageTestBehaviour;
public function testConfirmPageSubscriber(): void
{
$container = $this->getKernel()->getContainer();
$dd = $container->get(CustomDataService::class); <== IT BREAKS HERE ServiceNotFoundException: You have requested a non-existent service
$dd = $container->get('event_dispatcher'); // WORKS WITH SHOPWARE ALIASES NOT WITH PLUGINS
}
}
I can make container->get on any shopware alias but as soon I try to recall and get from the container any service decleared in any xml of any 3th party plugin, i get
ServiceNotFoundException: You have requested a non-existent service "blabla"
What is wrong ?
Take a look at the answer given here: https://stackoverflow.com/a/70171394/10064036.
Probably your plugin is not marked as active in the DB your tests run against.
The test environment has a mostly unpopulated database to allow tests to to run unaffected with their own fixtures only. Therefore after each test there should be a rollback to all transactions made within the test. This principle also includes plugin installations and database transactions they may execute in their lifecycle events.
You may want to install your plugin properly before your tests, so you get a representative state of the environment with the plugins lifecycle events getting dispatched and thereby caused possible changes.
public function setUp(): void
{
$this->installPlugin();
}
private function installPlugin(): void
{
$application = new Application($this->getKernel());
$installCommand = $application->find('plugin:install');
$args = [
'--activate' => true,
'--reinstall' => false,
'plugins' => ['YourPluginName'],
];
$installCommand->run(new ArrayInput($args, $installCommand->getDefinition()), new NullOutput());
}
I am using a rest api to store/retrieve my data which is stored in a postgres database. The api is not laravel, its an external service!
Now i want to create a website with laravel (framework version 7.3.0) and i'm stuck on how to implement the api calls correctly.
For example: i want to have a custom user provider with which users can log-in on the website. But the validation of the provided credentials is done by the api not by laravel.
How do i do that?
Just make a Registration controller and a Login Controller by "php artisan make:controller ControllerName" and write Authentication logics there.
In previous versions of Laravel you had a command like "php artisan make:auth" that will make everything needed to do these operations. But in Laravel 7.0 you need to install a package called laravel/ui.
Run "composer required laravel/ui" to install that package
Then run "php artisan ui bootstrap --auth"
and now, you are able to run "php artisan make:auth"
This command will make whole Registration (Signup) and Login system for you.
and in orer to work with REST, you may need to know REST (Http) verbs. Learn about GET, POST, PUT, PATH, DELETE requests and how to make those request with PHP and Laravel collection methods. Learn about JSON parsing, encoding, and decoding. Then you can work with REST easily. and work without any template codes from other packages.
Thank you so much. I hope this answer give you some new information/thought. Thanks again.
Edit:
This might not be the best way. But this is what I did at that time. I tried curl and guzzle to build the request with session cookie and everything in the header to make it look like a request from a web browser. Couldn't make it work.
I used the web socket's channel id for the browser I want the changes to happen and concatenated it with the other things, then encrypted it with encrypt($string). After that, I used the encrypted string to generate a QR code.
Mobile app (which was already logged in as an authenticated used) scanned it and made a post request with that QR string and other data. Passport took care of the authentication part of this request. After decrypting the QR string I had the web socket's channel id.
Then I broadcasted in that channel with proper event and data. Caught that broadcast in the browser and reloaded that page with JavaScript.
/*... processing other data ...*/
$broadcastService = new BroadcastService();
$broadcastService->trigger($channelId, $eventName, encrypt($$data));
/*... returned response to the mobile app...*/
My BroadcastService :
namespace App\Services;
use Illuminate\Support\Facades\Log;
use Pusher\Pusher;
use Pusher\PusherException;
class BroadcastService {
public $broadcast = null;
public function __construct() {
$config = config('broadcasting.connections.pusher');
try {
$this->broadcast = new Pusher($config['key'], $config['secret'], $config['app_id'], $config['options']);
} catch (PusherException $e) {
Log::info($e->getMessage());
}
}
public function trigger($channel, $event, $data) {
$this->broadcast->trigger($channel, $event, $data);
}
}
In my view :
<script src="{{asset('assets/js/pusher.js')}}"></script>
<script src="{{asset('assets/js/app.js')}}" ></script>
<script>
<?php
use Illuminate\Support\Facades\Cookie;
$channel = 'Channel id';
?>
Echo.channel('{{$channel}}')
.listen('.myEvent' , data => {
// processing data
window.location.reload();
});
</script>
I used Laravel Echo for this.
Again this is not the best way to do it. This is something that just worked for me for that particular feature.
There may be a lot of better ways to do it. If someone knows a better approach, please let me know.
As of my understanding, you are want to implement user creation and authentication over REST. And then retrieve data from the database. Correct me if I'm wrong.
And I'm guessing you already know how to communicate over API using token. You are just stuck with how to implement it with laravel.
You can use Laravel Passport for the authentication part. It has really good documentation.
Also, make use of this medium article. It will help you to go over the step by step process.
I have a controller has an action that looks something like this:
/**
* #Route("/my_route_path", name="my_route_name")
*/
public function doSomethingAction(Request $request)
{
$myPath = $request->getScheme().'://'.$request->getHttpHost().''.$request->getBasePath();
$data = file_get_contents($myPath. '/data_folder/data.json');
return $this->render('#Entry/my_template.html.twig', array(
'data' => json_decode($data, true)
));
}
And I create a functional test for this controller like this:
/** #test */
public function doSomething_should_success()
{
$client = static::createClient();
$crawler = $client->request('GET', '/my_route_path');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
But I can't run the functional test I still get : Failed asserting that 500 is identical to 200
So, after I checked the test.log file I find this error : file_get_contents(http://localhost/data_folder/data.json) : failed to open stream
As now the problem is comming from $request->getBasePath() because always contain empty string but the expected behaviour is return PATH_TO_MY_PROJECT_FOLDER\web in my case must return projects\web_apps\MY_PROJECT_FOLDER_NAME\web
So, the simplified question: why the request object always contain an empty basePath string in the unit test but it works very well on the browser.
The Request object helps you handle the request of a client, that is something like GET /my_route_path plus lots of headers and a server that is directed at.
The web server passes those information on to php and symfony, and symfony will turn this into a Request object. Symfony has usually one entry point, which is public/index.php (symfony 4) or web/app.php (symfony 3) which is assumed to be / or possibly /basePath/ (the basepath will be communicated by the web server and handled by Symfony).
Symfony will generate a Request object, where the basepath is essentially abstracted away, and whenever you generate a url (via Controller::generateUrl) the base path is taken into account. that's why the basepath is important for Requests.
This is actually described pretty well in the comments of the Request's functions:
getBasePath vs getPathInfo.
However, this only concerns the public facing URLs and doesn't have anything to do with how you structure your project and where that project is located, because that's completely irrelevant to the Request (separation of concerns and stuff).
So I guess, you are actually looking for the root directory of your project.
To find the location of your project dir, there is the very base version, where you directly use the PHP magic var __DIR__ which contains the directory the current script file is in, and you can navigate from there. since controllers are usually located such that their path is projectdir/src/Controller/TheController.php a __DIR__.'/../.. would give you the projectdir. However, that's not really clean. The better version:
Depending on the symfony version you're using, you should retrieve the project dir via the ParameterBagInterface (symfony 4)
function doSomethingAction(ParameterBagInterface $params) {
$projectDir = $params->get('kernel.project_dir');
}
or via the container (symfony 3) see also: new in symfony 3.3: A simpler way to get the project root directory
function doSomethingAction() {
$projectDir = $this->getParameter('kernel.project_dir');
}
In my case I had to inyect RequestStack $stackand access the main request, after that my "BasePath" has value. This is because I where in a subrequest and I had to access to the top level of the request.
This post helped me to understood: Symfony2 - get main request's current route in twig partial/subrequest
/**
* #Route("/myroute", name="myroute")
*/
public function myroute(RequestStack $stack)
{
$request = $stack->getMainRequest();
$route = $request->getPathInfo();
}
I created file commands/TestCommand.php in my yii-powered project:
class TestCommand extends CConsoleCommand
{
public function actionIndex()
{
echo "Hello World!\n";
}
}
And it's became visible via yiic:
Yii command runner (based on Yii v1.1.14)
Usage: yiic.php <command-name> [parameters...]
The following commands are available:
- message
- migrate
- shell
- test <<<
- webapp
To see individual command help, use the following:
yiic.php help <command-name>
If I am trying to get some help information about this console command:
php yiic.php help test
I see default text:
Usage: yiic.php test index
How can I write my TestCommand class which will show my help information? Is it a some public field or special method which return help text? I need something like that:
php yiic.php help webapp
Result like I need:
USAGE
yiic webapp <app-path> [<vcs>]
DESCRIPTION
This command generates an Yii Web Application at the specified location.
PARAMETERS
* app-path: required, the directory where the new application will be created.
If the directory does not exist, it will be created. After the application
is created, please make sure the directory can be accessed by Web users.
* vcs: optional, version control system you're going to use in the new project.
Application generator will create all needed files to the specified VCS
(such as .gitignore, .gitkeep, etc.). Possible values: git, hg. Do not
use this argument if you're going to create VCS files yourself.
You can override the default getHelp method to implements your help!
It must return a string that is the help text.
Provides the command description. This method may be overridden to return the actual command description.
Here is the default method:
public function getHelp()
{
$help='Usage: '.$this->getCommandRunner()->getScriptName().' '.$this->getName();
$options=$this->getOptionHelp();
if(empty($options))
return $help."\n";
if(count($options)===1)
return $help.' '.$options[0]."\n";
$help.=" <action>\nActions:\n";
foreach($options as $option)
$help.=' '.$option."\n";
return $help;
}
You can also override the default getOptionHelp method that is called in getHelp
Provides the command option help information. The default implementation will return all available actions together with their corresponding option information.
Source