How in feature testing use data from factory? - laravel-5

In laravel 5.8 app with tests I make posting data with some dummy data, like:
$newVoteCategoryRow= [
'id' => null,
'name' => $new_vote_category_row_name,
'meta_description' => 'vote category meta_description on ' . now(),
'meta_keywords' => [ 'vote category meta_description on ' . now(), 'meta_keywords' ],
'active' => true,
'in_subscriptions' => true,
];
$response = $this->actingAs($loggedUser)->post('/admin/vote-categories', $newVoteCategoryRow);
$this->assertCount( $vote_categories_count+1, VoteCategory::all() );
it works ok, but actually I have factory for VoteCategory table in /database/factories/VoteCategoryFactory.php, defined :
<?php
use Faker\Generator as Faker;
use \Cviebrock\EloquentSluggable\Services\SlugService;
use App\VoteCategory;
$factory->define(App\VoteCategory::class, function (Faker $faker) {
$name= 'Vote category ' . $faker->word;
$slug = SlugService::createSlug(VoteCategory::class, 'slug', $name);
return [
'name' => $name,
'slug' => $slug,
'active' => true,
'in_subscriptions' => false,
'meta_description' => $faker->text,
'meta_keywords' => $faker->words(4),
];
});
and my question is if there is a way in post request instead of $newVoteCategoryRow array use my factory, not adding row in database but
reading data from factory for post request ?

to achieve that you just need to use your factory within the test case method:
to create VoteCategory u have to methods, the first one is make and this one will create an instance of VoteCategory without persisting it within the database, and the create method will persist the new VoteCategory within the database.
in your case, you want to create a new instance without adding it to the database, for that you just need to use make:
$newVoteCategoryRow = factory('App\VoteCategory')->make(); // add this line to your test case method.
$response = $this->actingAs($loggedUser)->post('/admin/vote-categories', $newVoteCategoryRow->toArray());
$this->assertCount( $vote_categories_count+1, VoteCategory::all());
for more information, you can check the doc Laravel 5.8: using-factories

Related

laravel endpoint hide field

How can i hide some fields ?
i want to hide the file field
Eloquent :
$reports = Report::select('id', 'file','company_id', 'title', 'year', 'created_at')
->orderBy('id', 'desc')
->paginate(10);
return ReportResource::collection($reports);
Model :
...
public function getFileSizeAttribute()
{
return Storage::disk('files')->size($this->attributes['file']);
}
....
ReportResource:
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file' => $this->whenNotNull($this->file), <-- i want to hide the file field
'file_size' => $this->fileSize, <-- but always show file_size
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
to get file_size field i must select the file field , because it's depends on it to calculate the file size.
but i want to hide the file field before send the response.
i know i can use the protected $hidden = [] method in the model , but i don't want that, because file field it's required on others place. i just want to hide it on this endpoint only.
Since you are using API resources the best and clean way to do this is by using a Resource class for your collection.
Said that, you will have 3 Resources:
The first one, as it is, just for retrieving a single Model with file and file_size attributes. The one you already have ReportResource.php
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file' => $this->whenNotNull($this->file),
'file_size' => $this->fileSize,
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
A new second resource to be used in your endpoint, without the file attribute. IE: ReportIndexResource.php
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file_size' => $this->fileSize,
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
Now you need to create a Resource collection which explicitly defines the Model Resource to use. IE: ReportCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ReportCollection extends ResourceCollection
{
/**
* The resource that this resource collects.
*
* #var string
*/
public $collects = ReportIndexResource::class;
}
Finally, use this new resource collection in your endpoint
$reports = Report::select('id', 'file','company_id', 'title', 'year', 'created_at')
->orderBy('id', 'desc')
->paginate(10);
return new ReportCollection($reports);
Of course, you can make use of makeHidden() method, but IMO is better to write a little more code and avoid a non desired attribute in your response because you forgot to make it hidden.
Also, in case you make use of makeHidden() method and you want to show the attribute in a future, you will have to update all your queries instead of a silgle resource file.
If you want to make it Hide From All Returns , you can Do this in model
protected $hidden = ['file'];
and if you want to do it temporirly with this query , you can Use MakeHidden method
$users = $reports->makeHidden(['file']);
It's clear in laravel docs , take a look
https://laravel.com/docs/9.x/eloquent-collections#method-makeHidden

Stripe & Laravel how to upgrade or downgrade session subscription?

I have some issues using the Laravel Cashier for creating subscriptions.
First, from my backend I am creating a Package, which calls the following two Strip functions:
public function createStripeProduct(array $data)
{
$product = $this->stripe->products->create([
'name' => $data['title']." ".appName(),
]);
return $product->id;
}
public function createStripePrice(array $data)
{
$price = $this->stripe->prices->create([
'unit_amount' => $data['price'] * $this->multiple,
'currency' => $this->currency,
'recurring' => ['interval' => 'month'],
'product' => $data['stripe_prod_id'],
]);
return $price->id;
}
Then in my Controller, I am creating the session:
public function create(Request $request)
{
$key = config('services.stripe.secret');
$stripe = new Stripe\StripeClient($key);
$stripeCustomer = $user->createOrGetStripeCustomer();
$checkout_session = $stripe->checkout->sessions->create([
'customer' => $stripeCustomer['id'],
'success_url' => route('frontend.user.account'),
'cancel_url' => route('frontend.user.account'),
'payment_method_types' => ['card'],
'line_items' => [
[
'price' => $request->stripe_price_id,
'quantity' => 1,
],
],
'mode' => 'subscription',
'allow_promotion_codes' => true,
]);
return $checkout_session['id'];
}
Everything is working so far, but with the implementation, I can subscribe one use multiple times to the same or to a different Package.
How can I prevent this from happening and also how to implement a future upgrade/downgrade of the Package?
To answer your two questions:
1) I can subscribe one use multiple times to the same or to a different Package. How can I prevent this from happening
Your code is fetching a Stripe Customer object in createOrGetStripeCustomer(). You can list all Subscriptions on the Customer with https://stripe.com/docs/api/subscriptions/list#list_subscriptions-customer and then check if you want to create an additional CheckoutSession Subscription on that Customer.
2) how to implement a future upgrade/downgrade of the Package?
You would use the code snippets here: https://stripe.com/docs/billing/subscriptions/upgrade-downgrade#changing where you update the Subscription's SubscriptionItem with a new Price ID.
$sub = \Stripe\Subscription::update('sub_123', [
'cancel_at_period_end' => false,
'proration_behavior' => 'create_prorations',
'items' => [
[
'id' => $subscription->items->data[0]->id,
'price' => 'price_456', // the new Price to update to
],
],
]);

How to convert object return by laravel model factory create method into array containing model fields?

For example, I have a UserFactory.php
<?php
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'role' => 'USER',
'password' => 'sasdcsdf34', // password
'remember_token' => Str::random(10),
];
});
Now, I can create a user as following
$user = factory(User::class)->create();
Now, How can I convert this $user object into array containing user info like name,email etc without initializing new array and manually assigning every $user object property. ??
I DON'T want to manually assign like following as it is tedious if there are many properties in $user object
$userArray=[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email
]
I have tried this but it creates array containing various other properties and actual values needed are nested inside properties
$userArray=array($user)
You should try using the raw method of factory instead of create.
$user = factory(User::class)->raw();
This should give you an array you can work with.
Try to add something like this to your model class:
public function getArr(){
foreach($this->attributes as $key => $val){
$array[$key] = $val;
}
return $array;
}
If you wish to have this function in every model you could create trait with this function and then just attach it in model class or any class extending it.
You can use json_decode.
// Laravel 7
$userArray = json_decode(factory(User::class)->create(), true);
// Laravel 8
$userArray = json_decode(User::factory()->create(), true);
For Laravel 8, instead of make or create method, use:
User::factory()->raw();
This will return an array

Relations in resources?

So I'm planning to start using resources for my "API" (vue endpoint). So I started to search for some tutorials about the subject, and found a youtuber that describes the process. And I started making my own API resource. The youtuber shows briefly how to use the relations, but the thing is that I receive Property [description] does not exist on this collection instance. when trying to use the relation in the resource.
The current setup is:
$stack = Stack::select(['id', 'name', 'subject_id', 'description', 'image'])->where('id', '=', $requestId)->first();
$questions = $stack->load('question.choiceInRandomOrder');
return $questions;
And with resource it would be something like (notice choiceInRandomOrde, I would need that relation also):
return [
'subject' => $this->subject->name,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'image' => $this->image,
'questions' => [
'description' => $this->question->description,
'is_info' => $this->question->is_info,
'source' => $this->question->source,
'image' => $this->question->image,
]
];
}
And for testing, I have setup the following in my routes web.php
use App\Stack;
use App\Http\Resources\StackResource;
Route::get('/json', function(){
$stack = Stack::find(2);
return new StackResource($stack);
});
You try to access the name of subject in 'subject' => $this->subject->name, but you do not load the relation.
i don't know if i'm right but i thnk it has to be with the fact that ure not doing an Eloquent call but a Query Builder call (when doing $stack = Stack::select ... ). Why select just some fields in the call if you can choose the parameters to show directly in the model class? (see this).
Try to doing an Eloquent call instead (something like Stack::find(1)) and test it. It should work.

How to properly hydrate and extract Doctrine Entities from Zend Forms

I'm just starting out with Doctrine and was rewriting some code to use Doctrine entities in some Forms.
I have an Entity Business which has some 1:n relations with addresses, employees, emails etc. the Setup is really basic and working fine.
To add new Businesses i created a BusinessForm and Fieldsets for each of my entities. Here the constructor of the form:
public function __construct($scenario='create', $entityManager = null) {
parent::__construct('business_form');
$this->scenario = $scenario;
$this->entityManager = $entityManager;
$this->setAttribute('method', 'post');
$businessFieldset = new BusinessFieldset($this->entityManager);
$businessFieldset->setUseAsBaseFieldset(true);
$this->add($businessFieldset);
$hydrator = new DoctrineHydrator($this->entityManager, new Business());
$this->setHydrator($hydrator);
$this->addElements();
$this->addInputFilter();
}
addElements just adds a Submit and CSRF input.
And here the Controller action:
public function addAction(){
$form = new BusinessForm('create', $this->entityManager);
if ($this->getRequest()->isPost()) {
$data = $this->params()->fromPost();
$form->setData($data);
if($form->isValid()) {
// save Object
return $this->redirect()->toRoute('subcontractor', ['action'=>'index']);
}
}
return new ViewModel([
'form' => $form
]);
}
The form validates and i can get the Data from the form with $form->getData(). But i cant figure out how to get a populated Object from the form using the form's hydrator. When I use setObject(new Business()) at the start of the controller i get an error while $form->isValid() is running :
Zend\Hydrator\ArraySerializable::extract expects the provided object to implement getArrayCopy()
Isnt that the wrong hydrator being called ?
If i dont setObject() but instead use $form->bind(new Business()) after the validation i get an empty Object from $form->getObject(). If i get the data and hydrate a new Object with the form's hydrator like so : $form->getHydrator()->hydrate($data['business], new Business()) i do get the populated Object i was expecting. (Business being the name of the base fieldset)
So my question is, how to i get the result of the last call from just using $form->getObject() after the validation?
[EDIT]
The Problem seems to be with the Collections of Fieldsets used as sub-fieldsets in the businessfieldset. If i validate the form without using the collections i do get the expected Business Object from $form->getData()
Here an example how i add the collection (in the business fieldset):
$this->add([
'name' => 'emails',
'type' => 'Zend\Form\Element\Collection',
'attributes' => [
'id' => 'business_emails',
],
'options' => [
'label' => 'Emails',
'count' => 1,
'should_create_template' => true,
'template_placeholder' => '__index__',
'allow_add' => true,
'target_element' => [
'type' => 'LwsSubcontractor\Form\EmailFieldset',
],
'target_class' => 'LwsSubcontractor\Entity\Email'
],
]);
and here the EmailFieldset:
public function __construct() {
parent::__construct('email');
$this->setObject(new Email());
$this->addElements();
}
protected function addElements() {
$this->add([
'name' => 'email',
'type' => 'Zend\Form\Element\Email',
'attributes' => [
'placeholder' => 'E-Mail (z.B. email#muster-email.de)',
'class' => 'form-control',
'required' => true,
'size' => 50,
],
'options' => [
'label' => 'Email',
],
]);
}
}
If using the Collections i get the Error message from above. So after adding a hydrator to each Fieldset i was fine.
Although i was under the impression that setting the hydrator for the form would result in the used fieldsets to inherit that hydrator.Or was this because of using the fieldsets as collections and not directly ?
You have to add the hydrator to all your fieldsets, personally I use DoctrineModule\Stdlib\Hydrator\DoctrineObject for doctrine entities.
I would also look at using the init() method to initialize your forms and add elements then register and retrieve your form and fieldsets through the FormElementManager, $serviceLocator->get('FormElementManager')->get(yourFieldsetorForm::class). The form can than be injected into your controller.
I hope this helps.

Resources