Tidy up controller in Laravel 5.1 - php

I'm setting up a Laravel 5.1 project and I've been making good progress with it but have run in to something that I'm struggling to figure out.
Basically, I have been creating objects to insert in to the database in my controller methods. This hasn't been too bad because they're usually one-liners.
However, I've run in to a more complex db entry and my controller has become a little muddied. Let me show you:
/**
* Store a new ticket for the specified project.
*
* #param int $id
* #param TicketsRequest $request
* #return Response
*/
public function store_ticket($id, TicketsRequest $request)
{
$user_id = Auth::user()->id;
$project_id = $id;
$project_tickets = Ticket::whereProjectId($id);
$project_ticket_id = $project_tickets->count() + 1;
$ticket = new Ticket;
$ticket->user_id = $user_id;
$ticket->project_id = $project_id;
$ticket->project_ticket_id = $project_ticket_id;
$ticket->title = $request->title;
$ticket->save();
$ticket_update = new TicketUpdate;
$ticket_update->user_id = $user_id;
$ticket_update->ticket_id = $ticket->id;
$ticket_update->status_id = $request->status_id;
$ticket_update->priority_id = $request->priority_id;
$ticket_update->assigned_to = $request->assigned_to;
$ticket_update->description = $request->description;
$ticket_update->save();
return redirect('/projects');
}
So as you can see, I'm creating a ticket which gets saved to the database, then also creating a ticket update which is also saved to the database.
What I'd like to do is extract this code in to 'something' to clean up my controller.
On my travels, I've found that maybe creating repositories might be the way forward. Otherwise I was thinking about some kind of service but I'm not really convinced that that is the way forward.
I have a subscription to Laracasts and found the following video but it's a little outdated and I was sure if this would still be the 'right' way to do this in Laravel 5.1 (I've found that things seem to have a natural home in 5.1 compared to older versions).
https://laracasts.com/lessons/repositories-simplified
Any suggestions/links etc would be great. Thanks!

If you instantiate your objects often/always using the same set of attributes, you could easily extract that code into models constructors, e.g:
//in your model
public function __construct($user_id, $ticket_id, $request) {
$this->user_id = $user_id;
$this->ticket_id = $ticket_id;
$this->status_id = $request->status_id;
$this->priority_id = $request->priority_id;
$this->assigned_to = $request->assigned_to;
$this->description = $request->description;
}
// in your controller
$ticket_update = new TicketUpdate($user_id, $ticket->id, $request);
$ticket_update->save();

Nothing wrong with your implementation. I suggest you use try catch when calling the save() method in case something goes wrong and send a nice error to the user saying something like "something went wrong, contact your admin".

Related

How to keep hash in redirect url Laravel 8 and Inertiajs

I am using Laravel 8 with Inertiajs and React. I'm trying to redirect to a url with a hash on the end of the url, and for some reason no matter how I construct the url, Laravel is stripping the fragment at the end. Even when I explicitly use withFragment, it doesn't work. Here's my controller:
/**
* Handle request to store a new answer
*/
public function store(Request $request)
{
$userId = Auth::id();
$question = Question::find($request->input('question')['id']);
$questionAuthor = User::find($question->user_id);
$answer = $request->input('answer');
$newAnswer = new Answer;
$newAnswer->body = $answer['body'];
$newAnswer->user_id = $userId;
$newAnswer->question_id = $question->id;
$newAnswer->save();
Mail::to($questionAuthor)->send(new NewAnswer(
$questionAuthor,
$newAnswer,
$question
));
$url = url("/questions/{$question->slug}");
return redirect($url)->withFragment($newAnswer->id);
}
If someone could help me understand why the hash is being stripped from the end and how I might fix it, I would be very grateful. Thanks.

Issue with Mockery when injecting multiple mocked objects into controller

I'm using Mockery in my Laravel based PHP project to help test a Laravel MVC controller. Below is the relevant part of my controller class I'm trying to test.
class DevicesController extends Controller
{
private $deviceModel;
private $rfDeviceModel;
private $userModel;
private $userDeviceModel;
public function __construct(Device $deviceModel, RFDevice $rfDeviceModel, User $userModel, UserDevice $userDeviceModel)
{
$this->middleware('guest');
$this->deviceModel = $deviceModel;
$this->rfDeviceModel = $rfDeviceModel;
$this->userModel = $userModel;
$this->userDeviceModel = $userDeviceModel;
}
...
public function add(Request $request)
{
$name = $request->input('name');
$description = $request->input('description');
$onCode = $request->input('onCode');
$offCode = $request->input('offCode');
$pulseLength = $request->input('pulseLength');
$type = 1;
$currentUserId = $this->currentUser()->id;
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
$this->rfDeviceModel->add($onCode, $offCode, $pulseLength, $newDeviceId);
$this->userDeviceModel->add($currentUserId, $newDeviceId);
return redirect()->route('devices');
}
}
In particular, I'm writing several unit tests around the controller's add(Request $request) function to make sure that each of the three model add(...) functions are called. My test case to handle this looks like the following:
public function testAdd_CallsAddForModels()
{
$mockDeviceModel = Mockery::mock(Device::class);
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(Device::class, $mockDeviceModel);
$mockRFDeviceModel = Mockery::mock(RFDevice::class);
$mockRFDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(RFDevice::class, $mockRFDeviceModel);
$mockUserDeviceModel = Mockery::mock(UserDevice::class);
$mockUserDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(UserDevice::class, $mockUserDeviceModel);
$user = $this->givenSingleUserExists();
$this->addDeviceForUser($user->user_id);
}
private function givenSingleUserExists()
{
$user = new User;
$name = self::$faker->name();
$email = self::$faker->email();
$userId = self::$faker->uuid();
$user = $user->add($name, $email, $userId);
return $user;
}
private function addDeviceForUser($userId)
{
$this->withSession([env('SESSION_USER_ID') => $userId])
->call('POST', '/devices/add', [
'name' => 'Taylor',
'description' => 'abcd',
'onCode' => 1,
'offCode' => 2,
'pulseLength' => 3
]);
}
When I run this test, I get the following output in the console:
There was 1 error:
1) Tests\Unit\Controller\DeviceControllerTest::testAdd_CallsAddForModels
Mockery\Exception\InvalidCountException: Method add() from Mockery_1_App_RFDevice should be called
exactly 1 times but called 0 times.
But the funny and perplexing thing is that if I comment out and combination of 2 of the 3 mockery sections, my test pass. This means to mean, that my code is actually working correctly, but for some reason in this case, I can't inject multiple mocked model objects into my controller and test them all at once. I guess I could split this up into three separate tests that make sure each model's add(...) function is called, but I want to do it all in one test case if possible. I also know I could use a repository pattern to wrap all the business logic in the controller's add(...) function into a single call, but then I would run into the same problem while testing the repository class.
You're not mocking the return values of the methods so this line attempts to access an attribute (id) on a null.
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
You can fix this by adding a return value to your Device model mock like so:
$mockDeviceModel = Mockery::mock(Device::class);
$device = new Device;
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once()->andReturn($device);
To make such problems easier to debug in the future, change your error handler to re-throw the exceptions in a testing environment instead of rendering a valid HTML response.

Symfony2 entity manager get not working all the time

I found a strange issue in symfony2 (maybe is not strange and i did a mistake somewhere):
i'm trying to call an entity manager method that i defined in the entity class:
//Entity/organisation.php
/**
* #return string
*/
public function getApiUrl(){
return $this->api_url;
}
and i don't always get a return value for the same object
the call is made from a controller method:
private function addApiLog($organisationId, $callType, $eventInfo){
$em = $this->getEntityManager();
$organisation = $em->find('\WebAgenda\Entity\Organisation', $organisationId);
if (null === $organisation) {
die();
}
$apiUrl = $organisation->getApiUrl();
$apiKey = $organisation->getApiKey();
$fc = fopen("debug_api_log.txt", "a");
fwrite($fc, date("Y-m-d H:i:s")." - ".$callType." - ".$organisationId." - ".$organisation->getName()." - ".$apiUrl."\n");
fclose($fc);
if(trim($apiUrl)!='' && $apiUrl!='-'){
the 'addApiLog()' methos is called from different methods depending on the action and even though the organisationId that is passed to it is the same and i get the organisation object, sometimes the $organisation->getApiUrl() method doesn't return anything and the $organisation->getName(), always return the correct value: http://screencast.com/t/HQ2NuNfWSG9
What am I missing? Why i don't get values?
Thank you!
Replace $em = $this->getEntityManager();
With this $em = $this->getDoctrine()->getManager();
And then use your repository
$myRepo = $em->getRepository('NamespaceMyBundle:Organisation');
$organisation = $myRepo->find($organisationId);
You said the addApiLog() method is called from different methods depending on the action.
Maybe not all your methods have access to the Context ?

Laravel 5.1 - Generate a unique 10 alphanumeric character code upon save to database

I'm working in Laravel 5.1 and saving a gecko to a database. My code for my store method is below:
public function store(GeckoRequest $request)
{
$user_id = Auth::user()->id;
$input = $request->all();
$input['genetics'] = json_encode($input['genetics'], JSON_FORCE_OBJECT);
$input['user_id'] = $user_id;
Gecko::create($input);
$name = str_replace(' ', '-', $request['name']);
flash()->success('Success!', 'Your gecko has been added to the system');
return redirect()->action('GeckoController#show', [$name]);
}
I know I could do $input['uid'] = str_random(10); - But how do I ensure it is in fact unique and won't redirect back to my form if it isn't unique?
Is there a proper practice into achieving something like this?
Create a function that generates the 10 digit random key then passes it through a validator with a unique rule set. If the validator gives you an error re-run the same function to generate a new one
public function randomId(){
$id = \Str::random(10);
$validator = \Validator::make(['id'=>$id],['id'=>'unique:table,col']);
if($validator->fails()){
return $this->randomId();
}
return $id;
}
From what I understand of your question, you could achieve this using a created event on the model. This will allow you to add anything to the model before it is persisted in the database without any further interaction required.
In a service provider, or your App\Providers\AppServiceProvider add the event to the boot method.
public function boot()
{
User::creating(function ($user) {
// When ever a User model is created
// this event callback will be fired.
$user->setAttribute('randomString', str_random(10));
// Returning 'false' here will stop the
// creation of the model.
});
}
Full documentation for eloquent model events.
Here is something I've used before:
do
{
$code = // generate your code
$gecko_code = Gecko::where('code', $code)->first();
}
while(!empty($gecko_code));

Symfony2 - doctrine get tables creation order

I am using Symfony 2.7, doctrine 2, MySQL.
I'm trying to retrieve the tables creation order in a controller as when issuing
php app/console doctrine:generate:schema --dump-sql
but I only need the table names.
So, for instance, if I have two tables like
Product - Category I'd like to have an output which looks like this : array('Category', 'Product')
Using this documentation here's what I've done so far:
public function getTablesCreationOrderAction()
{
$conn = $this->get('database_connection');
$sm = $conn->getSchemaManager();
$sequences = $sm->listSequences($this->getParameter('database_name'));
die();
}
An exception is thrown at $sequences = $sm->listSequences.
Here's the exception I get:
Operation 'Doctrine\DBAL\Platforms\AbstractPlatform::getListSequencesSQL' is not supported by platform.
I don't know if this means that MySQL does not support the operation.
Thanks!
Yes, but it seems like the feature isn't implemented yet, since this is in the source code of AbstractPlatform you will see this:
public function getListSequencesSQL($database)
{
throw DBALException::notSupported(__METHOD__);
}
Now this means that platforms overriding AbstractPlatform must override these functions to provide the right implementation.
Now if you take a look at MySqlPlatform, you won't be able to find the getListSequencesSQL() anywhere, hence you can't use this feature with mysql or at least it's not implemented.
For those interested, here's how I did it. There may be better ways..
public function getTablesCreationOrderAction()
{
$app = New Application($this->get('kernel'));
$app->setAutoExit(false);
$input = New ArrayInput(['command' => 'doctrine:schema:create',
'--dump-sql'=>true]);
$output = New BufferedOutput();
$app->run($input, $output);
$rst = $output->fetch();
$schema = explode('CREATE TABLE ', $rst);
$tables = [];
foreach ($schema as $tableDef)
{
$tables[] = explode(' (', $tableDef)[0];
}
unset($tables[0]); // empty
var_dump($tables);
die();
}

Categories