Angular, Laravel 4 and MySQL database best practices - javascript

I am pretty new to Angular and have a bit of experience with Laravel 4.
I am building an app where the user can edit "on-the-fly" but also save to a MySQL.
The initial plan was to use Angular to manage the live editing, and to store and retrieve data to the MySQL DB using Eloquent. I am aware you can connect to DB via Angular, but I'd like to know what the best solution would be.
Is it best to keep it separated or to use Angular for everything?
Is there some performance issues if using Angular for everything?
Is Angular as easy to use as Eloquent for DB interactions ?
If I use Eloquent for DB, is it straight-forward to pass data to Angular?
I have already started building the live-editing part of the app with Angular and I have to say I found it very easy to learn and extremely powerful. I now have to make a decision as to how I will handle storage.
Thank you

Check out this tutorial by the great Dave Mosher, I think it might be exactly what you're looking for, he uses Laravel, Angular, and MySQL:
Youtube Screencast: http://www.youtube.com/watch?v=hqAyiqUs93c
Source code: https://github.com/davemo/end-to-end-with-angularjs

The best way to use angular.js and laravel is, using a REST API.
For example, if you have an admin panel to manage users, the method will be,
In your route,
Route::resource('users', 'UsersController');
The controller looks like this,
<?php
/**
*
* Users Controller
*
*/
class UsersController extends AdminController {
/**
* Display all users.
*
* #return Response
*/
public function index() {
$users = User::where('id', '!=', Auth::user()->id)->get();
return Response::json(array(
'status' => 'success',
'users' => $users->toArray()),
200
);
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create() {
//
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store() {
// add some validation also
$input = Input::all();
$user = new User;
if ( $input['name'] ) {
$user->name = $input['name'];
}
if ( $input['username'] ) {
$user->username = $input['username'];
$user->password = Hash::make($input['username']);
}
if ( $input['email'] ) {
$user->email = $input['email'];
}
$user->save();
return Response::json(array(
'status' => 'success',
'users' => $user->toArray()),
200
);
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id) {
$user = User::where('id', $id)
->take(1)
->get();
return Response::json(array(
'error' => false,
'users' => $user->toArray()),
200
);
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id) {
//
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
*/
public function update($id) {
// add some validation also
$input = Input::all();
$user = User::find($id);
if ( $input['name'] ) {
$user->name = $input['name'];
}
if ( $input['username'] ) {
$user->username = $input['username'];
}
if ( $input['email'] ) {
$user->email = $input['email'];
}
$user->save();
return Response::json(array(
'status' => 'success',
'message' => 'User Updated'),
200
);
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id) {
$user = User::find($id);
$user->delete();
return Response::json(array(
'status' => 'success',
'message' => 'User Deleted'),
200
);
}
}
Then the script,
var app = angular.module('myApp', []);
// include this in php page to define root path
app.factory('Data', function(){
return {
root_path: "<?php echo Request::root(); ?>/"
};
});
GET - Get all users
$http({method: 'GET', url: Data.root_path + 'users'}).
success(function(data, status, headers, config) {
$scope.users = data.users;
}).
error(function(data, status, headers, config) {
$scope.users = [];
});
GET - Get single user for edit
$http({method: 'GET', url: Data.root_path + 'users/'+id}).
success(function(data, status, headers, config) {
$scope.entry = data.users[0];
}).
error(function(data, status, headers, config) {
$scope.entry = [];
});
PUT - Update single user
$http.put(Data.root_path + 'users/'+entry.id, entry).
success(function(data, status, headers, config) {
//
}).
error(function(data, status, headers, config) {
//
});
POST - Save new user
$http.post(Data.root_path + 'users', entry).
success(function(data, status, headers, config) {
//
}).
error(function(data, status, headers, config) {
//
});
DELETE - Delete a user
$http.delete(Data.root_path +'users/'+id)
.success(function(response) {
//
})
.error(function(response) {
//
});

Create RESTful endpoints to manage resources using Laravel. This is dead simple with the resource controller as already pointed out:
Route::resource('user', 'UsersController');
Then you use an AngularJS resource to interact with that. Example (from docs) of fetching a user, editing the model and then saving (calling via the $ in the callback).
var User = $resource('/user/:userId', {userId:'#id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
http://docs.angularjs.org/api/ngResource.$resource
Even better, create an Angular JS Service for the resource so you can just inject that into multiple controllers and get all the methods defined.
Here's a great post on how to set this up:
http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource

Related

Pusher: Callback function not executing with standalone Laravel (API) Vue.js(client) apps

Please I need help with pusher integration in my Laravel & Vue js project. NOT SPA (i.e separate Apps (Laravel - API & Vuejs- frontend)
The goal is to establish a real-time chat between two users.
The whole cycle is working perfectly well but the pusher callback is not executing, therefore making the chat function limited to the app API level only. It is not real-time which is why I'm integrating pusher to handle that.
Please see the code snippets below, ready to provide more on request. I've spent days on this, still can't figure out what I'm doing wrong.
Thanks in anticipation.
CommentController
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$user = Auth::user();
$validator = Validator::make($request->all(), [
'bid_id' => 'required',
'message' => ['required', 'string'],
]);
if ($validator->fails()) {
return response()->json(["error" => $validator->errors()], 400);
}
try {
$comment = $user->comments()->create([
'bid_id' => $request->bid_id,
'message' => $request->message,
]);
// Fire the comment broadcast event
// event(new CommentEvent($comment));
broadcast(new CommentEvent($user, $comment->load('user')))->toOthers();
} catch (Exception $exception) {
Log::error("Error while creating Comment" . $exception->getMessage());
} finally {
return response()->json(['comment' => $comment], 201);
}
}
CommentEvent.php
<?php
namespace App\Events;
use App\Models\Comment;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CommentEvent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #param $comment
*
* #return void
*/
public $user;
public $comment;
public function __construct(User $user, Comment $comment)
{
$this->user = $user;
$this->comment = $comment;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel('comment-channel');
}
public function broadcastAs()
{
return 'CommentEvent';
}
}
Main.js
import Pusher from "pusher-js";
/* -------------------------------------------------------------------------- */
/* PUSHER CONFIG */
/* -------------------------------------------------------------------------- */
Pusher.logToConsole = true;
let pusher = new Pusher(process.env.VUE_APP_PUSHER_APP_KEY,{
cluster: process.env.VUE_APP_PUSHER_APP_CLUSTER,
encrypted: false,
});
Vue.prototype.$pusher = pusher;
App.vue
<script>
export default {
name: "App",
components: {},
created() {
let channel = this.$pusher.subscribe("comment-channel");
channel.bind("pusher:subscription_succeeded", function(members) {
console.log(members);
console.log("succesfully subscribed!");
});
channel.bind("CommentEvent", function(data) {
console.log(data);
this.$store.commit("ADD_COMMENT", data.comment);
});
},
methods: {
},
};
</script>
I've been able to resolve this using.
but I had to switch tech. The real-time chat system of my app is now driven by socket.io, Redis and a simple node js server wrapped within the API
I'll be willing to help with code snippets if you need me to.

Vue js parameter can't get in route

I use VueJS and laravel but now I can't get parameter value, please help me to solve this problem.
my VueJS code:
getTestData:function () {
let config = {
params: {
id: 1
}
};
axios.post('{{ route('get_user_data') }}', config)
.then(function (response) {
console.log(response);
// app.posts=response.data;
})
.catch(error => {
})
},
My Controller code:
public function testData(Request $request)
{
// how to get this value?
}
My route
Route::post('get-user-data','TestController#testData')->name('get_user_data');
You don't actually need a post request to get some values out of database. A get request would be more suitable.
You need to have the param in the route definition
Route::get('get-user-data/{id}','TestController#testData')->name('get_user_data');
//If you want to use post route
Route::post('get-user-data/{id}','TestController#testData')->name('get_user_data');
Then in controller method you can get the value out of the request
public function testData(Request $request)
{
$id = $request->route('id');
}
params are the URL parameters to be sent with the request
You could retrieve input from query string in your controller as:
public function testData(Request $request)
{
$id = $request->query('id');
}

Angular http request with custom data

I am trying to do a http request to a php file on my server. My code i am using at the moment is as follows:
App.controller('GetSales', ['$scope', '$http', function ($scope, $http) {
$http({
method: 'POST',
url: '/app/controller/apis/_sales.php?period_start=2015-07-01&period_end=2015-07-31&join=leads&status=0&category=0user=1'
})
.success(function (data) {
$scope.sales = data;
});
}]);
Isnt there a better way to do this?
When i add these var as data it doesnt get posted to my page?
data: {
period_start: '2015-07-01',
period_end: '2015-07-31',
join: 'leads',
status: '',
category: '',
user: '1'
};
In php i get the data like this, its also sanitized for security reason:
$user = filter_var($_REQUEST['user'], FILTER_SANITIZE_NUMBER_INT);
$period_start = $_REQUEST['period_start'].' 00:00:00';
At first sight you are tryng to call an HTTP POST service, but you send parameter like it was a GET service, try something like that:
App.controller('GetSales', ['$scope', '$http', function ($scope, $http) {
$http.post('/app/controller/apis/_sales.php',
{
period_start: '2015-07-01',
period_end: '2015-07-31',
join: 'leads',
status: '',
category: '',
user: '1'
})
.success(function (data) {
$scope.sales = data;
})
.error(function (data, status) {
console.log(status);
});
I would use json_decode( file_get_contents('php://input') ) on your serverside. Also, please don't forget to sanitize your user sent data!
var dataParams = {
period_start: '2015-07-01',
period_end: '2015-07-31',
join: 'leads',
status: '',
category: '',
user: '1'
};
App.controller('GetSales', ['$scope', '$http', function ($scope, $http) {
$http.post('/app/controller/apis/_sales.php', dataParams)
.success(function (data) {
$scope.sales = data;
});
}]);
You will want to watch ever using the variable data as it will most likely collide with another variable, such as in your demonstration where you have named your post params as data while the return response is also aliased as data in the $.post success. This may not cause an issue in this case - but it usually will, so I renamed it for you out of habit.
Your server side could look something like this depending on what your usernames strings consist of:
public static function sanatize_client_string($dirtyString){
$cleanString = htmlspecialchars(strtolower(preg_replace("/[^a-z]+/i", "[FORBIDDEN_CHAR]", $dirtyString)));
return $cleanString;
}
$client_data = sanatize_client_string(json_decode( file_get_contents('php://input')));
Now you can access the username like:
echo $client_data['user']; // Will echo 1 based on the post data you are sending
This is what a simple serverside data-router could look like, as using normal $_POST has never worked for Angular data for me either:
/**
* Collect all Angular HTTP Request data
*/
$client_data = json_decode( file_get_contents('php://input') );
$app_state = utils::sanatizeClientString($client_data->appState); // <- name of prop must match client angularPostObj.x = serverModel.x
/**
* Cache the view path to the model
*/
$module_exists_in_model = isset($app['view_data']['views'][$app_state]);
/**
* If the angular post request matches data in the model, return the requested dataset, while if no object is found
* under that address, the error object will be returned which will send and error data to the view.
*
* This error condition will never happen aside from an attack because the clientside AngularJS router would prevent any
* unregistered paths from being even sent to the server. This would happen using a post mocking service or by
* forcing the string change in the code inspector while using the site.
*/
$module_exists_in_model ?
$view_model = $app['view_data']['views'][$app_state] :
$view_model = $app['view_data']['views']['error'];
// Call the view from Angular post data, passing it to a Class that sends a response as valid JSON
Render_app::echo_json($view_model);
I was informed of this by: http://www.cleverweb.nl/javascript/a-simple-search-with-angularjs-and-php/ and How to post a data in Angular?.
The point is... use $client_data = json_decode( file_get_contents('php://input') ); instead of $client_data = $_POST['username'];

FOSUserBundle AJAX Login with Symfony2 (routing)

I'm trying to make the AJAX authentication work with FOSUserBundle.
I have created an Handler directory with a AuthenticationHandler class :
<?php
namespace BirdOffice\UserBundle\Handler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
private $router;
private $session;
/**
* Constructor
*
* #param RouterInterface $router
* #param Session $session
*/
public function __construct( RouterInterface $router, Session $session )
{
$this->router = $router;
$this->session = $session;
}
/**
* onAuthenticationSuccess
*
* #param Request $request
* #param TokenInterface $token
* #return Response
*/
public function onAuthenticationSuccess( Request $request, TokenInterface $token )
{
// if AJAX login
if ( $request->isXmlHttpRequest() ) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
// if form login
} else {
if ( $this->session->get('_security.main.target_path' ) ) {
$url = $this->session->get( '_security.main.target_path' );
} else {
$url = $this->router->generate( 'home_page' );
} // end if
return new RedirectResponse( $url );
}
}
/**
* onAuthenticationFailure
*
* #param Request $request
* #param AuthenticationException $exception
* #return Response
*/
public function onAuthenticationFailure( Request $request, AuthenticationException $exception )
{
// if AJAX login
if ( $request->isXmlHttpRequest() ) {
$array = array( 'success' => false, 'message' => $exception->getMessage() ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
// if form login
} else {
// set authentication exception to session
$request->getSession()->set(SecurityContextInterface::AUTHENTICATION_ERROR, $exception);
return new RedirectResponse( $this->router->generate( 'login_route' ) );
}
}
}
I have created a login Javascript function :
function login() {
$.ajax({
type: "POST",
url: Routing.generate('check_login_ajax'),
dataType: 'json',
data: {
_username: $('#username').val(),
_password: $('#password').val(),
_remember_me: false,
_csrf_token: $('#_csrf_token').val()
}
}).done(function(data) {
console.log(data);
}).fail(function(data) {
console.log(data);
});
}
In my routingAjax.yml, I have added the following lines to override the FOSUserBundle security route :
check_login_ajax:
pattern: /check_login_ajax
defaults: { _controller: FOSUserBundle:Security:check }
requirements:
_method: POST
options:
expose: true
In my global security.yml file, I have added the check_path, success_handler and failure_handler parts :
firewalls:
main:
pattern: ^/
form_login:
login_path: fos_user_registration_register
check_path: check_login_ajax
success_handler: user.security.authentication_handler
failure_handler: user.security.authentication_handler
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout:
path: fos_user_security_logout
target: /
anonymous: true
My first issue is : the AJAX return this message: "Invalid CSRF token." (but I send a good one generated in PHP, maybe I missed something doing it). Here is my PHP code for it :
<?php
$csrfProvider = $this->container->get('form.csrf_provider');
$csrfToken = $csrfProvider->generateCsrfToken('popUpUser');
?>
Second issue : my login page (not the AJAX one) is not working anymore because the orignal route of FOSUserBundle login has been overwritten.
PS : I have posted a message yesterday : FOSUserBundle (login / register) + AJAX + Symfony2 but I have badly explained my problem. Sorry by advance.
First Issue: You are sending an invalid CSRF token. In Symfony 2.3 you could generate it using {{ csrf_token('authenticate') }} inside the template's input's value.
Second issue: Do not overwrite the route, simply use the original route: fos_user_security_check.
In general: if you use an AuthenticationSuccessHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler your method could look something like this:
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
return new JsonResponse(array('success' => true));
}
return parent::onAuthenticationSuccess($request, $token);
}
Do something similar for an AuthenticationFailureHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler.

How to invoke "{Model.Foo} updated" after model update?

I have a javascriptMVC Model
/**
* #class Hzfrontend.Models.Warmwasser
* #parent index
* #inherits jQuery.Model
* Wraps backend warmwasser services.
*/
$.Model('Hzfrontend.Models.Warmwasser',
/* #Static */
{
findAll: "/api/warmwasser",
findOne : "/api/warmwasser/{id}",
update : "/api/warmwasser/{id}"
},
/* #Prototype */
{
update : function(attrs, success, error){
$.ajax({
type: 'PUT',
url:"/api/warmwasser/"+this.id,
data: $.toJSON(this),
success: success,
error: error,
dataType: "json"});
}
});
and a Controller:
$.Controller('Hzfrontend.Warmwasser.List',
/** #Static */
{
defaults : {}
},
/** #Prototype */
{
init : function(){
this.element.append(this.view('init',Hzfrontend.Models.Warmwasser.findAll()) )
},
'.waterTemperature change': function( el ){
var mod = el.closest('.warmwasser').model();
mod.attr('waterTemperature', el.val());
mod.update();
steal.dev.log("update waterTemperature");
},
"{Hzfrontend.Models.Warmwasser} updated" : function(Warmwasser, ev, warmwasser){
warmwasser.elements(this.element)
.html(this.view('warmwasser', warmwasser) );
steal.dev.log("updated");
}
});
after the update completed I want to invoke the updated callback in the controller. How to do this?
If I use the ./fixtures/fixtures.js' to test the app without a server backend, it works without any problems.
Thanks.
That code should work. What do you return as a response from your server? You should return an JSON response with the object that you updated.
You shouldn't have a prototype update method.

Categories

Resources