Beginning Laravel 4 - Keeping the controller skinny - php

I'm trying to maintain a skinny controller, but I'm still getting used to what can go in my controller, since before I used to pile just about everything inside of it. In this example I'm inserting validated data into a database, which I assumed is correct. What I'm confused about is that I want to take one of the field inputs, manipulate its text formatting and then save it to another field in my database. What I have written works, but I don't know if its good to have this code in my controller, if not where should it go?
Controller
public function store()
{
$validation = new Services\Validators\Deal;
if($validation->passes())
{
$deals = Deals::create(Input::all());
// start code in question
$image = Input::get('company');
$image = strtolower($image);
$image = str_replace(" ", "-", $image);
$image .= ".png";
$deals->image = $image;
$deals->save();
// end code in question
return Redirect::to('deals/create')
->with('message', 'Deal Created');
}
return Redirect::back()
->withInput()
->withErrors($validation->errors);
}
To recap, I'm not sure if the code in question belongs in my controller, and if it doesn't, where would it be better placed? Thanks for any insights.

Any business logic should be placed in models, or repositories and your controller should look just like
<?php
class DealsController extends controller {
public function __construct(Deals $deals) //// <---- Dependency Injection
{
$this->deals = $deals;
}
public function store()
{
try
{
$this->deals->insertRow(Input::all());
}
catch (\Exceptions\ValidationException $e)
{
return Redirect::back()
->withInput()
->withErrors($this->deals->errors);
}
return Redirect::to('deals/create')
->with('message', 'Deal Created');
}
}
And in your Deals class, you do whatever you need to do with your data
class Deals extends Eloquent {
public function insertRow($input)
{
$validation = new Services\Validators\Deal;
if($validation->passes())
{
$deals = Deals::create($input);
// start code in question
$image = $input['company'];
$image = strtolower($image);
$image = str_replace(" ", "-", $image);
$image .= ".png";
$deals->image = $image;
$deals->save();
// end code in question
}
$this->errors = $validation->errors;
throw new \Exceptions\ValidationException("Error inserting Deals", $validation->errors);
}
}
This is untested and a not-really-refactored code, but I hope you can see a point in it.

You could actually remove all the code in question and use Laravel Mutator instead.
Basically, setup a function in your Deals class which will automatically process the text formatting whenever data is about to be set/save via the Model Eloquent ::create, or update.
Something like
public function setImageAttribute($value)
{
$image = strtolower($value);
$image = str_replace(" ", "-", $image);
$image .= ".png";
$this->attributes['image'] = $image;
}
Refer to http://laravel.com/docs/eloquent#accessors-and-mutators

Related

Laravel 8 not finding "tag" from package "\Conner\Tagging\Taggable;"

The code works perfectly when I want to create a new tag from scratch, but when $skillsQuery->count() > 0 and enters in the if statement. It prints...
Method Illuminate\Database\Eloquent\Collection::tag does not exist.
How can I update tags using this package?
Controller
<?php
public function storeSkills(Request $request)
{
$id = auth()->user()->id;
$skillsQuery = Skill::where('created_by', $id)->get();
// If skill exists
if ($skillsQuery->count() > 0) {
$input = $request->all();
$tags = explode(", ", $input['name']);
// $skill = Skill::create($input);
$skillsQuery->tag($tags);
$skillsQuery->created_by = $id;
if ($skillsQuery->save()) {
return redirect()->route('profile')->with('success', 'Skills updated successfully');
} else {
return redirect()->route('profile')->with('error', 'Error updated your Skills!');
}
} else {
$input = $request->all();
$tags = explode(", ", $input['name']);
$skill = Skill::create($input);
$skill->tag($tags);
$skill->created_by = $id;
if ($skill->save())
return redirect()->route('profile')->with('success', 'Skills stored successfully');
else {
return redirect()->route('profile')->with('error', 'Error storing your Skills!');
}
}
}
The result of calling ->get() on a Illuminate\Database\Query is that you will receive an instance of a Illuminate\Database\Collection, which does not contain a ->tag() method. Even if it was a query (by removing ->get()) this still would not work, as you can't call a relationship method off of a collection.
If instead you loop over the skillsQuery then you will receive an instance of a Model object which then allows you to access functions and/or relationships off of it:
$skillsQuery->each(function ($skill) use ($tags) {
$skill->tag($tags); // or perhaps ->retag($tags); here
});

naming a file after submitting a form in Symfony

I am working on a form which accepts some user input and an image file, the submission part and the data getting entered into the database is working fine but I am stuck at how to name a file once it is uploaded, right now this is what i see as an image name in database C:\wamp2.5\tmp\phpF360.tmp which obviously is not correct.
This is what my controller looks like DefaultController.php
public function createBlogAction(Request $request)
{
$post = new Post();
$form = $this->createForm(new PostCreate(), $post);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$post->upload();
$post->setDate(date_create(date('Y-m-d H:i:s')));
$post->setAuthor('ClickTeck');
$em->persist($post);
$em->flush();
$this->get('session')->getFlashBag()->add(
'notice',
'Success'
);
}
return $this->render('BlogBundle:Default:blog-create.html.twig', array(
'form' => $form->createView()
)
);
}
This is what my upload() looks like inside Entity/Post.php which is uploading the file and moving it into the folder, the file name that I see in a folder is correct however now the one that goes into the database
public function upload()
{
if (null === $this->getImage()) {
return;
}
// I might be wrong, but I feel it is here that i need to name the file
$this->getImage()->move(
$this->getUploadRootDir(),
$this->getImage()->getClientOriginalName()
);
$this->path = $this->getUploadDir();
$this->file = null;
}
I will really appreciate if someone can push me in right direction, I just need to name the file, a name which gets assigned to the image in database and the file should get uploaded with the same name as well.
UPDATE
I managed to get it to work using the following function, not sure if this is the best practice but it did work, i would love to hear from others on this. please do not provide any links, if you can refine what has already been done that would be great.
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getImage()) {
return;
}
$dirpath = $this->getUploadRootDir();
$image = $this->getImage()->getClientOriginalName();
$ext = $this->getImage()->guessExtension();
$name = substr($image, 0, - strlen($ext));
$i = 1;
while(file_exists($dirpath . '/' . $image)) {
$image = $name . '-' . $i .'.'. $ext;
$i++;
}
$this->getImage()->move($dirpath,$image);
$this->image = $image;
$this->path = $this->getUploadDir();
$this->file = null;
}
This topic from documentation may help you : http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
In addition, you should not put your upload function in the controller but rather use Doctrine events (Lifecycle callbacks) to call your function automatically.
as per suggestion of #theofabry you can check symfony2 documentation How to handle File Uploads with Doctrine, Controller must be thin as much as possible and try to do upload with Doctrine Events.
If you want to continue with your logic you may try following code, I have not tested yet...so please be careful.
// set the path property to the filename where you'ved saved the file
$this->path = $this->file->getClientOriginalName();
instead of
$this->path = $this->getUploadDir();

Many to Many Relationship Laravel 4

I am working on a laravel-4 application. Currently It is coming together nicely and I've been getting my head around defining the relationships between the various table s of the database. However I've run into a problem that I'm having trouble solving.
In my db there is a resources table and tags table. There is a many to many relationship between them so I've also got a resource_tags table which has both tables id as the foreign keys.
Now, when I am creating a resource based on data provided by the user via a form I create the resource, check the type and decide on an action. Then I retrieve the tags of the resource and loop through them and create an entry into the Tags table.
My issue is placing information into the resource_tags table. Is there a method that can enable me to do this with relative ease?
This is my controller that is handling the form submission:
class SharedResourcesController extends BaseController {
//Add a shared Resource to the DB
//To do: Error checking and validation.
public function handleResource(){
//Create Object
$resource = new SharedResource;
$resource->title = Input::get('title'); //Title of resource
$resource->user_id = Input::get('user_id'); //User who uploads
$resource->book_id = Input::get('book_id'); //Book it is associated with
$resource->type_id = Input::get('type_id'); //Type of resource
//STORE LINKS
//if type is link... 1
if($resource->type_id == "1"){
$resource->web_link = Input::get('link');
}
//if type is video...2
if($resource->type_id == "2"){
$resource->vid_link = Input::get('link');
}
//UPLOADING
//If type is doc...3
if($resource->type_id == "3"){
if(Input::hasFile('file')){
$destinationPath = '';
$filename = '';
$file = Input::file('file');
$basename = Str::random(12);
$extension = $file->getClientOriginalExtension();
$destinationPath = public_path().'/file/';
$filename = Str::slug($basename, '_').".".$extension;//Create the filename
$file->move($destinationPath, $filename);
$resource->doc_link = $filename;
}
}
//if type is img...4
if($resource->type_id == "4"){
if(Input::hasFile('file')){
$destinationPath = '';
$filename = '';
$file = Input::file('file');
$basename = Str::random(12);
$extension = $file->getClientOriginalExtension();
$destinationPath = public_path().'/img/uploads/';
$filename = Str::slug($basename, '_').".".$extension;//Create the filename
$file->move($destinationPath, $filename);
$resource->img_link = $filename;
}
}
//TAGS
//Get the tags
$tags = Array();
$tags = explode(',', Input::get('tags'));
foreach($tags as $tag){
//Create a new Tag in DB - TO DO: Only Unique TAGS
$newTag = new Tag;
$newTag->name = $tag;
$newTag->save();
//Enter to resource tags
}
//Entry to resouce_tags
//Save Object
$resource->save();
return Redirect::action('User_BaseController#getSharedResources')->with('success', 'Resouce Created!');
//Any errors return to Form...
}
}
MODELS
class SharedResource extends Eloquent{
//set up many to many
public function tags(){
return $this->belongsToMany('Tag');
}
and
class Tag extends Eloquent{
//set up many to many
public function sharedResources(){
return $this->belongsToMany('SharedResource');
}
I know that there is lots missing in terms of validation and error handling, but I'm just trying to get the flow working and I can modify it at a later date. I'd appreciate any help.
All you have to do is build or grab the Resource and build or grab the Tags then call saveMany on the resource's tags relationship and pass an array of tag items into it, like this (pseudo-codey example):
$resource = Resource::create(['name' => 'Resource 1']);
$tag = [];
for ($i = 5; $i > 0; $i--) {
$tag = Tag::create(['name' => 'Tag '.$i]);
array_push($tags, $tag);
}
$resource->tags()->saveMany($tags);
The $tags have to be an array of Tag objects, and the saveMany called on the relationship will take care of the pivot table insertions for you. You should end up with a Resource 1 resource in the resources table, five Tags in the tag table, and 5 records in the resource_tag table with the relationships saved.
Can you add the code for both of your models as well? Do you have the relationship defined in them?
For example:
class Resource extends Eloquent {
public function tags()
{
return $this->belongsToMany('tag');
}
}
and
class Tag extends Eloquent {
public function resources()
{
return $this->belongsToMany('resource');
}
}

i got this error 500 | Internal Server Error | Doctrine_Connection_Mysql_Exception

when i am update my form, i got this error
500 | Internal Server Error | Doctrine_Connection_Mysql_Exception
executeEdit
public function executeEdit(sfWebRequest $request)
{
$this->form = new ContactForm();
$this->rs = Doctrine::getTable('login')-> find($request->getParameter('id'));
$id=$request->getParameter('id');
$unm=$this->rs['username'];
$msg=$this->rs['message'];
$em=$this->rs['email'];
$sub=$this->rs['subject'];
$this->form->setDefault('id', $id);
$this->form->setDefault('username', $unm);
$this->form->setDefault('message', $msg);
$this->form->setDefault('email', $em);
$this->form->setDefault('subject', $sub);
//$this->forward404Unless($this->rs);
if ($request->isMethod('post'))
{
$this->form->bind($request->getParameter('contact'), $request->getFiles('contact'));
if ($this->form->isValid())
{
$name="'".$this->form->getValue('username')."'";
$message="'".$this->form->getValue('message')."'";
$email="'".$this->form->getValue('email')."'";
$sub="'".$this->form->getValue('subject')."'";
$id=$this->form->getValue('id');
$file = $this->form->getValue('doc');
$filename = sha1($unm).'_'.sha1($file->getOriginalName());
$extension = $file->getExtension($file->getOriginalExtension());
$path=sfConfig::get('sf_upload_dir').'/'.$filename.$extension;
$qs= Doctrine_Query::create()
->update('login l')
->set('l.username', $name)
->set('l.message', $message)
->set('l.email', $email)
->set('l.subject', $sub)
->set('l.doc', $path)
->where('l.id=?',$id)
->execute();
$this->redirect('user/show?id=' . $id);
}
}
}
this is my edit action code. so whats the exaclty error. and what is my mistake
and what can i do to solve this error?
please help me
Why do you add quotes to the values you are saving? Doctrine will handle this for you. Try removing this part:
$name="'".$this->form->getValue('username')."'";
$message="'".$this->form->getValue('message')."'";
$email="'".$this->form->getValue('email')."'";
$sub="'".$this->form->getValue('subject')."'";
You're not utilising the power of Doctrine and forms fully.
E.g. instead of this:
$this->form = new ContactForm();
$this->rs = Doctrine::getTable('login')-> find($request->getParameter('id'));
$id=$request->getParameter('id');
$unm=$this->rs['username'];
$msg=$this->rs['message'];
$em=$this->rs['email'];
$sub=$this->rs['subject'];
$this->form->setDefault('id', $id);
$this->form->setDefault('username', $unm);
$this->form->setDefault('message', $msg);
$this->form->setDefault('email', $em);
$this->form->setDefault('subject', $sub);
You could use this:
$this->rs = Doctrine_Core::getTable('login')-> find($request->getParameter('id'));
$this->form = new ContactForm($this->rs);
You only have to "connect" your form to a proper model by adding this function:
public function getModelName()
{
return 'login';
}
You could then use the save method on the form so it automatically saves your object without having to create the update query.
Also have a look at the sfValidatorFile as it will automatically create a unique filename for you.

An example of an MVC controller

I have been reading a lot about how and why to use an MVC approach in an application. I have seen and understand examples of a Model, I have seen and understand examples of the View.... but I am STILL kind of fuzzy on the controller. I would really love to see a thorough enough example of a controller(s). (in PHP if possible, but any language will help)
Thank you.
PS: It would also be great if I could see an example of an index.php page, which decides which controller to use and how.
EDIT: I know what the job of the controller is, I just don't really understand how to accomplish this in OOP.
Request example
Put something like this in your index.php:
<?php
// Holds data like $baseUrl etc.
include 'config.php';
$requestUrl = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$requestString = substr($requestUrl, strlen($baseUrl));
$urlParams = explode('/', $requestString);
// TODO: Consider security (see comments)
$controllerName = ucfirst(array_shift($urlParams)).'Controller';
$actionName = strtolower(array_shift($urlParams)).'Action';
// Here you should probably gather the rest as params
// Call the action
$controller = new $controllerName;
$controller->$actionName();
Really basic, but you get the idea... (I also didn't take care of loading the controller class, but I guess that can be done either via autoloading or you know how to do it.)
Simple controller example (controllers/login.php):
<?php
class LoginController
{
function loginAction()
{
$username = $this->request->get('username');
$password = $this->request->get('password');
$this->loadModel('users');
if ($this->users->validate($username, $password))
{
$userData = $this->users->fetch($username);
AuthStorage::save($username, $userData);
$this->redirect('secret_area');
}
else
{
$this->view->message = 'Invalid login';
$this->view->render('error');
}
}
function logoutAction()
{
if (AuthStorage::logged())
{
AuthStorage::remove();
$this->redirect('index');
}
else
{
$this->view->message = 'You are not logged in.';
$this->view->render('error');
}
}
}
As you see, the controller takes care of the "flow" of the application - the so-called application logic. It does not take care about data storage and presentation. It rather gathers all the necessary data (depending on the current request) and assigns it to the view...
Note that this would not work with any framework I know, but I'm sure you know what the functions are supposed to do.
Imagine three screens in a UI, a screen where a user enters some search criteria, a screen where a list of summaries of matching records is displayed and a screen where, once a record is selected it is displayed for editing. There will be some logic relating to the initial search on the lines of
if search criteria are matched by no records
redisplay criteria screen, with message saying "none found"
else if search criteria are matched by exactly one record
display edit screen with chosen record
else (we have lots of records)
display list screen with matching records
Where should that logic go? Not in the view or model surely? Hence this is the job of the controller. The controller would also be responsible for taking the criteria and invoking the Model method for the search.
<?php
class App {
protected static $router;
public static function getRouter() {
return self::$router;
}
public static function run($uri) {
self::$router = new Router($uri);
//get controller class
$controller_class = ucfirst(self::$router->getController()) . 'Controller';
//get method
$controller_method = strtolower((self::$router->getMethodPrefix() != "" ? self::$router->getMethodPrefix() . '_' : '') . self::$router->getAction());
if(method_exists($controller_class, $controller_method)){
$controller_obj = new $controller_class();
$view_path = $controller_obj->$controller_method();
$view_obj = new View($controller_obj->getData(), $view_path);
$content = $view_obj->render();
}else{
throw new Exception("Called method does not exists!");
}
//layout
$route_path = self::getRouter()->getRoute();
$layout = ROOT . '/views/layout/' . $route_path . '.phtml';
$layout_view_obj = new View(compact('content'), $layout);
echo $layout_view_obj->render();
}
public static function redirect($uri){
print("<script>window.location.href='{$uri}'</script>");
exit();
}
}
<?php
class Router {
protected $uri;
protected $controller;
protected $action;
protected $params;
protected $route;
protected $method_prefix;
/**
*
* #return mixed
*/
function getUri() {
return $this->uri;
}
/**
*
* #return mixed
*/
function getController() {
return $this->controller;
}
/**
*
* #return mixed
*/
function getAction() {
return $this->action;
}
/**
*
* #return mixed
*/
function getParams() {
return $this->params;
}
function getRoute() {
return $this->route;
}
function getMethodPrefix() {
return $this->method_prefix;
}
public function __construct($uri) {
$this->uri = urldecode(trim($uri, "/"));
//defaults
$routes = Config::get("routes");
$this->route = Config::get("default_route");
$this->controller = Config::get("default_controller");
$this->action = Config::get("default_action");
$this->method_prefix= isset($routes[$this->route]) ? $routes[$this->route] : '';
//get uri params
$uri_parts = explode("?", $this->uri);
$path = $uri_parts[0];
$path_parts = explode("/", $path);
if(count($path_parts)){
//get route
if(in_array(strtolower(current($path_parts)), array_keys($routes))){
$this->route = strtolower(current($path_parts));
$this->method_prefix = isset($routes[$this->route]) ? $routes[$this->route] : '';
array_shift($path_parts);
}
//get controller
if(current($path_parts)){
$this->controller = strtolower(current($path_parts));
array_shift($path_parts);
}
//get action
if(current($path_parts)){
$this->action = strtolower(current($path_parts));
array_shift($path_parts);
}
//reset is for parameters
//$this->params = $path_parts;
//processing params from url to array
$aParams = array();
if(current($path_parts)){
for($i=0; $i<count($path_parts); $i++){
$aParams[$path_parts[$i]] = isset($path_parts[$i+1]) ? $path_parts[$i+1] : null;
$i++;
}
}
$this->params = (object)$aParams;
}
}
}
Create folder structure
Setup .htaccess & virtual hosts
Create config class to build config array
Controller
Create router class with protected non static, with getters
Create init.php with config include & autoload and include paths (lib, controlelrs,models)
Create config file with routes, default values (route, controllers, action)
Set values in router - defaults
Set uri paths, explode the uri and set route, controller, action, params ,process params.
Create app class to run the application by passing uri - (protected router obj, run func)
Create controller parent class to inherit all other controllers (protected data, model, params - non static)
set data, params in constructor.
Create controller and extend with above parent class and add default method.
Call the controller class and method in run function. method has to be with prefix.
Call the method if exisist
Views
Create a parent view class to generate views. (data, path) with default path, set controller, , render funcs to
return the full tempalte path (non static)
Create render function with ob_start(), ob_get_clean to return and send the content to browser.
Change app class to parse the data to view class. if path is returned, pass to view class too.
Layouts..layout is depend on router. re parse the layout html to view and render
Please check this:
<?php
global $conn;
require_once("../config/database.php");
require_once("../config/model.php");
$conn= new Db;
$event = isset($_GET['event']) ? $_GET['event'] : '';
if ($event == 'save') {
if($conn->insert("employee", $_POST)){
$data = array(
'success' => true,
'message' => 'Saving Successful!',
);
}
echo json_encode($data);
}
if ($event == 'update') {
if($conn->update("employee", $_POST, "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Update Successful!',
);
}
echo json_encode($data);
}
if ($event == 'delete') {
if($conn->delete("employee", "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Delete Successful!',
);
}
echo json_encode($data);
}
if ($event == 'edit') {
$data = $conn->get("select * from employee where id={$_POST['id']};")[0];
echo json_encode($data);
}
?>

Categories