Laravel Livewire database notification doesn't exist yet when broadcast notification received - laravel-livewire

I've worked on getting database and broadcast notifications to play nice for quite a while, and I've finally got it working and identified an issue I don't know how to solve.
The root cause seems to be that the notification from pusher is getting back to the app prior to when the database notification is available to select.
This results in my notification icon showing up on the front end, but the notifications array is empty:
public function getListeners()
{
return [
"echo-private:users.{$this->user->id},StatementCompleted" => 'notifyUser',
];
}
public function notifyUser(mixed $notification)
{
if (!empty($notification)) {
$this->showNotificationsBadge = true;
ray('notification!');
$this->refreshNotifications();
ray($this->notifications); <-- THIS IS EMPTY
}
}
public function refreshNotifications()
{
$this->notifications = $this->user->unreadNotifications()->get();
$this->notificationCount = $this->user->unreadNotifications()->count();
}
HOWEVER, if I add sleep(5) into the notifyUser() method like so:
public function notifyUser(mixed $notification)
{
if (!empty($notification)) {
$this->showNotificationsBadge = true;
ray('notification!');
sleep(5); <-- ADDED THIS
$this->refreshNotifications();
}
}
it results in the notifications array containing the notification and everything works fine. So my theory is that it's a timing issue.
How do I ensure my database notification exists before the pusher notification triggers my livewire front-end refresh?
EVENT/LISTENER/NOTIFICATION CLASSES:
Here's my event:
class StatementCompleted implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $statement;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Statement $statement)
{
$this->statement = $statement;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('users.' . $this->statement->uploadedBy->id);
}
}
and here's the event listener class:
class HandleStatementCompletedEvent implements ShouldQueue
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle($event)
{
Notification::send($event->statement->uploadedBy, new SendStatementCompletedNotification($event->statement));
}
}
and here's the notification class:
class SendStatementCompletedNotification extends Notification implements ShouldQueue, ShouldBroadcast
{
use Queueable;
public $statement;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Statement $statement)
{
$this->statement = $statement;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database', 'broadcast'];
}
/**
* Get the broadcastable representation of the notification.
*
* #param mixed $notifiable
* #return BroadcastMessage
*/
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'title' => 'Statement Processed',
'message' => "Your statement {$this->statement->original_file_name} has been processed.",
'user_id' => $this->statement->uploadedBy->id
]);
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toDatabase($notifiable)
{
return [
'title' => 'Statement Processed',
'message' => "Your statement {$this->statement->original_file_name} has been processed.",
'user_id' => $this->statement->uploadedBy->id
];
}
}

Related

Symfony 4 > filter a list of results, the best way

I'm working on a new Symfony 4 project.
I have a list of Speakers, and a list of Themes, and I have a ManyToMany relations between.
class Speakers
{
// ...
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Themes", inversedBy="speakers")
* #ORM\JoinTable(name="speakers_themes")
*/
private $themes;
/**
* Speakers constructor.
*/
public function __construct()
{
$this->themes = new ArrayCollection();
}
/**
* #return Collection|Themes[]
*/
public function getThemes(): Collection
{
return $this->themes;
}
/**
* #param Themes $theme
* #return Speakers
*/
public function addTheme(Themes $theme): self
{
if (!$this->themes->contains($theme)) {
$this->themes[] = $theme;
}
return $this;
}
/**
* #param Themes $theme
* #return Speakers
*/
public function removeTheme(Themes $theme): self
{
if ($this->themes->contains($theme)) {
$this->themes->removeElement($theme);
}
return $this;
}
}
class Themes
{
// ...
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Speakers", mappedBy="themes")
*/
private $speakers;
/**
* Themes constructor.
*/
public function __construct()
{
$this->speakers = new ArrayCollection();
}
/**
* #return Collection|Speakers[]
*/
public function getSpeakers(): Collection
{
return $this->speakers;
}
/**
* #param Speakers $speaker
* #return Themes
*/
public function addSpeaker(Speakers $speaker): self
{
if (!$this->speakers->contains($speaker)) {
$this->speakers[] = $speaker;
$speaker->addTheme($this);
}
return $this;
}
/**
* #param Speakers $speaker
* #return Themes
*/
public function removeSpeaker(Speakers $speaker): self
{
if ($this->speakers->contains($speaker)) {
$this->speakers->removeElement($speaker);
$speaker->removeTheme($this);
}
return $this;
}
I want to be able to filter the list of speakers, depending of the themes.
Since the list of themes is short (3 items), I thought of a select box, with the possibility of choosing a theme to filter.
But I don't know what's the best way to do this:
Build a specific form, to filter ?
class SpeakersFilterType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('themes', EntityType::class,
[
'class' => Themes::class,
'multiple' => true,
'expanded' => true
])
->add('save', SubmitType::class,
[
'label' => 'Filter'
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'method' => Request::METHOD_GET,
// 'data_class' => null,
'data_class' => Speakers::class,
]
);
}
}
I tried to build a form like you can see below.
In the configureOptions method, I specify the method to GET, and I was hoping that I can pass some get params to filter but it's not working.
I have an error:
The form's view data is expected to be an instance of class App\Entity\Speakers, but is a(n) array. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) array to an instance of App\Entity\Speakers.
If I follow the advice and put the option 'data_class' to null, I dont have an error but I'm not sure It's working, regarding the strange params it sends to the URL:
http://127.0.0.1:8000/speakers/?speakers_filter%5Bthemes%5D%5B%5D=2&speakers_filter%5Bsave%5D=&speakers_filter%5B_token%5D=ClBi9xzMrkM72niXNnkbMi1i-3gWMQ2sgfIxY0M1Pi8
Can somebody can help ? Thank you so much from a Symfony newbie.

Doctrine 2 ZF3 self referencing entity is not updating all fields of parent

I have a self referencing entity with parent and children. The strange thing is, when I add the form elements (DoctrineModule ObjectSelect) for parent and children to the form, some other fields of the parent entity doesn't update, when I persist the entity. The child entities are updating fine.
In the update query of the parent aren't these fields which I want to update. It's like doctrine doesn't recognize the changes for the parent (owning side) anymore. Before the persist I get the right entity with the actual changes from the form, but than doctrine doesn't update the changed fields in the query.
When I delete the form elements for parent and children in the form, everything works fine and the parent entity update/persist all fields.
/**
* #var string
*
* #Gedmo\Translatable
* #ORM\Column(type="text")
*/
private $teaser;
/**
* #var string
*
* #Gedmo\Translatable
* #ORM\Column(type="text")
*/
private $description;
/**
* One Category has Many Categories.
* #var Collection
* #ORM\OneToMany(targetEntity="Rental\Entity\Rental", mappedBy="parent")
*/
private $children;
/**
* Many Categories have One Category.
* #ORM\ManyToOne(targetEntity="Rental\Entity\Rental", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="houses_id", nullable=true, onDelete="SET NULL")
*/
private $parent;
public function __construct()
{
$this->children = new ArrayCollection();
}
/**
* Get teaser
*
* #return string
*/
public function getTeaser()
{
return $this->teaser;
}
/**
* Set teaser
*
* #param string $teaser
*/
public function setTeaser($teaser)
{
$this->teaser = $teaser;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set description
*
* #param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Add $child
* #param Collection $children
*/
public function addChildren(Collection $children)
{
foreach ($children as $child) {
$this->addChild($child);
}
}
/**
* #param Rental $child
* #return void
*/
public function addChild(Rental $child)
{
if ($this->children->contains($child)) {
return;
}
$child->setParent($this);
$this->children[] = $child;
}
/**
* Remove children
* #param Rental $children
*/
public function removeChildren(Collection $children)
{
foreach ($children as $child) {
$this->removeChild($child);
}
}
/**
* Remove child.
*
* #param \Rental\Entity\Rental $child
*
* #return boolean TRUE if this collection contained the specified element, FALSE otherwise.
*/
public function removeChild(Rental $child)
{
return $this->children->removeElement($child);
}
/**
* Get children.
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChildren()
{
return $this->children;
}
/**
* Set parent.
*
* #param \Rental\Entity\Rental|null $parent
*/
public function setParent(Rental $parent = null)
{
$this->parent = $parent;
}
/**
* Get parent.
*
* #return \Rental\Entity\Rental|null
*/
public function getParent()
{
return $this->parent;
}
Answer based (and might still change) based on discussion in comments with OP under question.
Simple use case: self-referencing Doctrine Entity does not correctly update parent/child related Entity object properties on on-save action.
OP provided Entity.
I'm assuming you have a Form setup for this Entity. I'm also assuming it's correctly setup, as you did not provide it after being asked (because, rightly so, it would mean a lot of code). However, assuming stuff with code makes for a lot of mistakes, which is why I mention it here.
As such, I'm assuming the handling of your Form in the Controller might be faulty. To check, please use the following simplified addAction function and give it a shot (Factory code below).
/**
* #var RentalForm
*/
protected $form;
/**
* #var ObjectManager
*/
protected $objectManager;
public function __construct(ObjectManager $objectManager, RentalForm $form)
{
$this->form = $form;
$this->objectManager = $objectManager;
}
public function addAction()
{
/** #var RentalForm $form */
$form = $this->getForm();
/** #var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$entity = $form->getObject();
$this->getObjectManager()->persist($entity);
try {
$this->getObjectManager()->flush();
} catch (\Exception $e) {
$message = sprintf(
'Was unable to save the data. Saving threw an error. <br />Code: %s. <br />Message: %s',
$e->getCode(),
$e->getMessage()
);
$this->flashMessenger()->addErrorMessage($message);
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
$this->flashMessenger()->addSuccessMessage(
$this->getTranslator()->translate('Successfully created object.')
);
// TODO replace vars with your own: return $this->redirect()->route($route, $routeParams);
}
$this->flashMessenger()->addWarningMessage(
'Your form contains errors. Please correct them and try again.'
);
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
Factory for class with the above, modify as needed for you situation
class RentalControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var ObjectManager $objectManager */
$objectManager = $container->get(EntityManager::class);
/** #var FormElementManagerV3Polyfill $formElementManager */
$formElementManager = $container->get('FormElementManager');
/** #var RentalForm $form */
$form = $formElementManager->get(RentalForm::class);
return new RentalController($objectManager, $form);
}
}

How to create simple okhttp3 websocket connection?

Can someone please show me an example on how to establish the connection to the wss:// address with specific Authorization header, using okhttp3 okhttp-ws library?
All I have is the url of WS server and Authorization string token.
Later, I must be able to send request to that connection, listen to upcoming data from WS server and than close connection. I have a difficulties with this new to me WS world, always been working only with REST (with okhttp3 too)
So generally this sample is most of what you need
https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/WebSocketEcho.java
But you will have two changes
Use wss instead of ws in your URL
Call request.addHeader to add your token
request.addHeader("Authorization", "Bearer " + token)
I know this is an old question, but when I try to use websocket with okhttp3 there are a lot of options that I want and it was not in the library. So I create a class that handle WS connection with extra functionalities. I hope it will help some body. Gist link
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.ProtocolException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okhttp3.internal.ws.RealWebSocket;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.ByteString;
/**
* Websocket class based on OkHttp3 with {event->data} message format to make your life easier.
*
* #author Ali Yusuf
* #since 3/13/17
*/
public class Socket {
private final static String TAG = Socket.class.getSimpleName();
private final static String CLOSE_REASON = "End of session";
private final static int MAX_COLLISION = 7;
public final static String EVENT_OPEN = "open";
public final static String EVENT_RECONNECT_ATTEMPT = "reconnecting";
public final static String EVENT_CLOSED = "closed";
/**
* Main socket states
*/
public enum State {
CLOSED, CLOSING, CONNECT_ERROR, RECONNECT_ATTEMPT, RECONNECTING, OPENING, OPEN
}
private static HttpLoggingInterceptor logging =
new HttpLoggingInterceptor()
.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.HEADERS : HttpLoggingInterceptor.Level.NONE);
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder()
.addInterceptor(logging);
public static class Builder {
private Request.Builder request;
private Builder(Request.Builder request) {
this.request = request;
}
public static Builder with(#NonNull String url) {
// Silently replace web socket URLs with HTTP URLs.
if (!url.regionMatches(true, 0, "ws:", 0, 3) && !url.regionMatches(true, 0, "wss:", 0, 4))
throw new IllegalArgumentException("web socket url must start with ws or wss, passed url is " + url);
return new Builder(new Request.Builder().url(url));
}
public Builder setPingInterval(long interval, #NonNull TimeUnit unit){
httpClient.pingInterval(interval, unit);
return this;
}
public Builder addHeader(#NonNull String name, #NonNull String value) {
request.addHeader(name, value);
return this;
}
public Socket build() {
return new Socket(request.build());
}
}
/**
* Websocket state
*/
private static State state;
/**
* Websocket main request
*/
private static Request request;
/**
* Websocket connection
*/
private static RealWebSocket realWebSocket;
/**
* Reconnection post delayed handler
*/
private static Handler delayedReconnection;
/**
* Websocket events listeners
*/
private static Map<String,OnEventListener> eventListener;
/**
* Websocket events new message listeners
*/
private static Map<String,OnEventResponseListener> eventResponseListener;
/**
* Message list tobe send onEvent open {#link State#OPEN} connection state
*/
private static Map<String,String> onOpenMessageQueue = new HashMap<>();
/**
* Websocket state change listener
*/
private static OnStateChangeListener onChangeStateListener;
/**
* Websocket new message listener
*/
private static OnMessageListener messageListener;
/**
* Number of reconnection attempts
*/
private static int reconnectionAttempts;
private static boolean skipOnFailure;
private Socket(Request request) {
Socket.request = request;
state = State.CLOSED;
eventListener = new HashMap<>();
eventResponseListener = new HashMap<>();
delayedReconnection = new Handler(Looper.getMainLooper());
skipOnFailure = false;
}
/**
* Start socket connection if i's not already started
*/
public Socket connect() {
if (httpClient == null) {
throw new IllegalStateException("Make sure to use Socket.Builder before using Socket#connect.");
}
if (realWebSocket == null) {
realWebSocket = (RealWebSocket) httpClient.build().newWebSocket(request, webSocketListener);
changeState(State.OPENING);
} else if (state == State.CLOSED) {
realWebSocket.connect(httpClient.build());
changeState(State.OPENING);
}
return this;
}
/**
* Set listener which fired every time message received with contained data.
*
* #param listener message on arrive listener
*/
public Socket onEvent(#NonNull String event, #NonNull OnEventListener listener){
eventListener.put(event,listener);
return this;
}
/**
* Set listener which fired every time message received with contained data.
*
* #param listener message on arrive listener
*/
public Socket onEventResponse(#NonNull String event, #NonNull OnEventResponseListener listener){
eventResponseListener.put(event,listener);
return this;
}
/**
* Send message in {event->data} format
*
* #param event event name that you want sent message to
* #param data message data in JSON format
* #return true if the message send/on socket send quest; false otherwise
*/
public boolean send(#NonNull String event, #NonNull String data){
try {
JSONObject text = new JSONObject();
text.put("event", event);
text.put("data", new JSONObject(data));
Log.v(TAG,"Try to send data "+text.toString());
return realWebSocket.send(text.toString());
} catch (JSONException e) {
Log.e(TAG,"Try to send data with wrong JSON format, data: "+data);
}
return false;
}
/**
* Set state listener which fired every time {#link Socket#state} changed.
*
* #param listener state change listener
*/
public Socket setOnChangeStateListener(#NonNull OnStateChangeListener listener) {
onChangeStateListener = listener;
return this;
}
/**
* Message listener will be called in any message received even if it's not
* in a {event -> data} format.
*
* #param listener message listener
*/
public Socket setMessageListener(#NonNull OnMessageListener listener) {
messageListener = listener;
return this;
}
public void removeEventListener(#NonNull String event) {
eventListener.remove(event);
onOpenMessageQueue.remove(event);
}
/**
* Clear all socket listeners in one line
*/
public void clearListeners() {
eventListener.clear();
messageListener = null;
onChangeStateListener = null;
}
/**
* Send normal close request to the host
*/
public void close() {
if (realWebSocket != null) {
realWebSocket.close(1000, CLOSE_REASON);
}
}
/**
* Send close request to the host
*/
public void close(int code, #NonNull String reason) {
if (realWebSocket != null) {
realWebSocket.close(code, reason);
}
}
/**
* Terminate the socket connection permanently
*/
public void terminate() {
skipOnFailure = true; // skip onFailure callback
if (realWebSocket != null) {
realWebSocket.cancel(); // close connection
realWebSocket = null; // clear socket object
}
}
/**
* Add message in a queue if the socket not open and send them
* if the socket opened
*
* #param event event name that you want sent message to
* #param data message data in JSON format
*/
public void sendOnOpen(#NonNull String event, #NonNull String data) {
if (state != State.OPEN)
onOpenMessageQueue.put(event,data);
else
send(event,data);
}
/**
* Retrieve current socket connection state {#link State}
*/
public State getState() {
return state;
}
/**
* Change current state and call listener method with new state
* {#link OnStateChangeListener#onChange(Socket, State)}
* #param newState new state
*/
private void changeState(State newState) {
state = newState;
if (onChangeStateListener != null) {
onChangeStateListener.onChange(Socket.this, state);
}
}
/**
* Try to reconnect to the websocket after delay time using <i>Exponential backoff</i> method.
* #see
*/
private void reconnect() {
if (state != State.CONNECT_ERROR) // connection not closed !!
return;
changeState(State.RECONNECT_ATTEMPT);
if (realWebSocket != null) {
// Cancel websocket connection
realWebSocket.cancel();
// Clear websocket object
realWebSocket = null;
}
if (eventListener.get(EVENT_RECONNECT_ATTEMPT) != null) {
eventListener.get(EVENT_RECONNECT_ATTEMPT).onMessage(Socket.this, EVENT_RECONNECT_ATTEMPT);
}
// Calculate delay time
int collision = reconnectionAttempts > MAX_COLLISION ? MAX_COLLISION : reconnectionAttempts;
long delayTime = Math.round((Math.pow(2, collision)-1)/2) * 1000;
// Remove any pending posts of callbacks
delayedReconnection.removeCallbacksAndMessages(null);
// Start new post delay
delayedReconnection.postDelayed(new Runnable() {
#Override
public void run() {
changeState(State.RECONNECTING);
reconnectionAttempts++; // Increment connections attempts
connect(); // Establish new connection
}
}, delayTime);
}
private WebSocketListener webSocketListener = new WebSocketListener() {
#Override
public void onOpen(WebSocket webSocket, Response response) {
Log.v(TAG,"Socket has been opened successfully.");
// reset connections attempts counter
reconnectionAttempts = 0;
// fire open event listener
if (eventListener.get(EVENT_OPEN) != null) {
eventListener.get(EVENT_OPEN).onMessage(Socket.this, EVENT_OPEN);
}
// Send data in queue
for (String event : onOpenMessageQueue.keySet()) {
send(event, onOpenMessageQueue.get(event));
}
// clear queue
onOpenMessageQueue.clear();
changeState(State.OPEN);
}
/**
* Accept only Json data with format:
* <b> {"event":"event name","data":{some data ...}} </b>
*/
#Override
public void onMessage(WebSocket webSocket, String text) {
// print received message in log
Log.v(TAG, "New Message received "+text);
// call message listener
if (messageListener != null)
messageListener.onMessage(Socket.this, text);
try {
// Parse message text
JSONObject response = new JSONObject(text);
String event = response.getString("event");
JSONObject data = response.getJSONObject("data");
// call event listener with received data
if (eventResponseListener.get(event) != null) {
eventResponseListener.get(event).onMessage(Socket.this, event, data);
}
// call event listener
if (eventListener.get(event) != null) {
eventListener.get(event).onMessage(Socket.this, event);
}
} catch (JSONException e) {
// Message text not in JSON format or don't have {event}|{data} object
Log.e(TAG, "Unknown message format.");
}
}
#Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
// TODO: some action
}
#Override
public void onClosing(WebSocket webSocket, int code, String reason) {
Log.v(TAG,"Close request from server with reason '"+reason+"'");
changeState(State.CLOSING);
webSocket.close(1000,reason);
}
#Override
public void onClosed(WebSocket webSocket, int code, String reason) {
Log.v(TAG,"Socket connection closed with reason '"+reason+"'");
changeState(State.CLOSED);
if (eventListener.get(EVENT_CLOSED) != null) {
eventListener.get(EVENT_CLOSED).onMessage(Socket.this, EVENT_CLOSED);
}
}
/**
* This method call if:
* - Fail to verify websocket GET request => Throwable {#link ProtocolException}
* - Can't establish websocket connection after upgrade GET request => response null, Throwable {#link Exception}
* - First GET request had been failed => response null, Throwable {#link java.io.IOException}
* - Fail to send Ping => response null, Throwable {#link java.io.IOException}
* - Fail to send data frame => response null, Throwable {#link java.io.IOException}
* - Fail to read data frame => response null, Throwable {#link java.io.IOException}
*/
#Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
if (!skipOnFailure) {
skipOnFailure = false; // reset flag
Log.v(TAG, "Socket connection fail, try to reconnect. (" + reconnectionAttempts + ")");
changeState(State.CONNECT_ERROR);
reconnect();
}
}
};
public abstract static class OnMessageListener {
public abstract void onMessage (String data);
/**
* Method called from socket to execute listener implemented in
* {#link #onMessage(String)} on main thread
*
* #param socket Socket that receive the message
* #param data Data string received
*/
private void onMessage (Socket socket, final String data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onMessage(data);
}
});
}
}
public abstract static class OnEventListener {
public abstract void onMessage (String event);
private void onMessage (Socket socket, final String event) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onMessage(event);
}
});
}
}
public abstract static class OnEventResponseListener extends OnEventListener {
/**
* Method need to override in listener usage
*/
public abstract void onMessage (String event, String data);
/**
* Just override the inherited method
*/
#Override
public void onMessage(String event) {}
/**
* Method called from socket to execute listener implemented in
* {#link #onMessage(String, String)} on main thread
*
* #param socket Socket that receive the message
* #param event Message received event
* #param data Data received in the message
*/
private void onMessage (Socket socket, final String event, final JSONObject data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onMessage(event, data.toString());
onMessage(event);
}
});
}
}
public abstract static class OnStateChangeListener {
/**
* Method need to override in listener usage
*/
public abstract void onChange (State status);
/**
* Method called from socket to execute listener implemented in
* {#link #onChange(State)} on main thread
*
* #param socket Socket that receive the message
* #param status new status
*/
private void onChange (Socket socket, final State status){
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
onChange(status);
}
});
}
}
}
Usage example:
Socket socket = Socket.Builder.with(WEBSOCKET_BASE_URL).build().connect();
socket.onEvent(Socket.EVENT_OPEN, socketOpenListener);
socket.onEvent(Socket.EVENT_RECONNECT_ATTEMPT, .....);
socket.onEvent(Socket.EVENT_CLOSED, .....);
socket.onEventResponse("Some event", socketPairListener);
socket.send("Some event", "{"some data":"in JSON format"}");
socket.sendOnOpen("Some event", "{"some data":"in JSON format"}");

Laravel user_id forign key not working

I am new to Laravel and I have multiple registration types I have tried to add a foreign key following the aravel 5.2 documentation and it keeps giving me errors any help would be greatly appreciated. I need to connect the registration of each type of user into the different registration types. Below I will post one of the three registration types that I have. If there is a way to only add the user information to the user table after there email has been verified that would be great. So in other words each registration (3x) I need them to fill out registration and only the user table info goes to the user table the rest would go to the other table but I want to two to be connected.
This is the migration file I am trying to get to work following the laravel 5.2 documents.
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->boolean('active')->default(false);
$table->rememberToken();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('artists');
$table->foreign('user_id')
->references('id')->on('artists')
//->onUpdate('cascade')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('users');
//$table->dropForeign('artists_user_id_foreign');
}
}
I also tried doing the same thing on the artists table and it did not work either keep getting a foreign constraint issue.
Ill need to do that process for each type of user account.
The controller is set up like this for artist
<?php
namespace App\Http\Controllers\Artist;
use App\User;
use App\Artist;
use App\Mailers\AppArtMailer;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class RegistrationController extends Controller
{
//protected $redirectTo = 'art';
/** Create a new registration instance.
*/
public function __construct()
{
//$this->middleware('artist');
}
/** show the Register page/
*
* #return \Response
*/
public function register()
{
return view('art.register');
}
/**
* Perform the registration.
*
* #param Request $request
* #param AppMailer $mailer
* #return \Redirect
*/
public function postRegister(Request $request, AppArtMailer $mailer)
{
//Validate
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:artists',
'password' => 'required'
]);
//create artist
$artist = Artist::create($request->all());
$user = User::create($request->all());
//email them
$mailer->sendEmailConfirmationTo($artist);
//$mailer->sendEmailConfirmationTo($user);
//flash
flash('Please confirm your email address.');
// redirect
return redirect()->back();
}
/**
* Confirm a user's email address.
*
* #param string $token
* #return mixed
*/
public function confirmEmail($token)
{
Artist::whereToken($token)->firstOrFail()->confirmEmail();
flash('You are now confirmed. Please login');
return redirect('artist');
}
}
I also followed the laracast ACL Roles and Permissions but am not sure how to integrate that into each of the user registrations so that each type automatically has the set roles.
Here are the different models I have each is a bit different since I am learning and playing.

Artist Model This will actually change since they can only be an artist if they are a viewer.
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use App\Http\Middleware\RedirectIfAuthenticatedArtist;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class Artist extends Model implements AuthenticatableContract, CanResetPasswordContract
{
protected $table = 'artists';
use Authenticatable, CanResetPassword;
/**
* The database table used by the model.
*
* #var string
*/
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* Boot the model.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::creating(function($artist) {
$artist->token = str_random(30);
});
}
/**
* Set the password attribute.
*
* #param string $password
*/
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}
/**
* Confirm the user.
*
* #return void
*/
public function confirmEmail()
{
$this->verified = true;
$this->token = null;
$this->save();
}
}
This is my users model
<?php
namespace App;
use DB;
use Illuminate\Http\Response;
use App\Http\Controllers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Cmgmyr\Messenger\Traits\Messagable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasRoles, Messagable;
//* The attributes that are mass assignable.
//*
// * #var array
// */
protected $fillable = [
'name', 'email', 'password',
];
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
// $user->roles
}
Sponsors Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
//use App\Http\Middleware\RedirectIfAuthenticatedSponsor;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class Sponsor extends Model implements AuthenticatableContract, CanResetPasswordContract
{
protected $table = 'sponsors';
use Authenticatable, CanResetPassword;
/**
* The database table used by the model.
*
* #var string
*/
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* Boot the model.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::creating(function($sponsor) {
$sponsor->token = str_random(30);
});
}
/**
* Set the password attribute.
*
* #param string $password
*/
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}
/**
* Confirm the user.
*
* #return void
*/
public function confirmEmail()
{
$this->verified = true;
$this->token = null;
$this->save();
}
}
Viewer Model
<?php
namespace App;
use Illuminate\Auth\Authenticatable;
use Cmgmyr\Messenger\Traits\Messagable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use App\Http\Middleware\RedirectIfAuthenticatedViewer;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class Viewer extends Model implements AuthenticatableContract, CanResetPasswordContract
{
protected $redirectTo = 'viewer';
use Authenticatable, CanResetPassword, Messagable;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'viewers';
//protected $table = 'experience';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function experience()
{
return $this->hasOne('App\Experience');
}
public function awardExperience($points)
{
return $this->experience->award($points);
}
/**
* Boot the model.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::creating(func

Unit test Laravel middleware

I am trying to write unit tests for my middleware in Laravel. Does anyone know a tutorial, or have an example of this ?
I have been writing a lot of code, but there must be a better way to test the handle method.
Using Laravel 5.2, I am unit testing my middleware by passing it a request with input and a closure with assertions.
So I have a middleware class GetCommandFromSlack that parses the first word of the text field in my Post (the text from a Slack slash command) into a new field called command, then modifies the text field to not have that first word any more. It has one method with the following signature: public function handle(\Illuminate\Http\Request $request, Closure $next).
My Test case then looks like this:
use App\Http\Middleware\GetCommandFromSlack;
use Illuminate\Http\Request;
class CommandsFromSlackTest extends TestCase
{
public function testShouldKnowLiftCommand()
{
$request = new Illuminate\Http\Request();
$request->replace([
'text' => 'lift foo bar baz',
]);
$mw = new \App\Http\Middleware\GetCommandFromSlack;
$mw->handle($request,function($r) use ($after){
$this->assertEquals('lift', $r->input('command'));
$this->assertEquals('foo bar baz',$r->input('text'));
});
}
}
I hope that helps! I'll try to update this if I get more complicated middleware working.
To actually test the middleware class itself you can do:
public function testHandle()
{
$user = new User(['email'=>'...','name'=>'...']);
/**
* setting is_admin to 1 which means the is Admin middleware should
* let him pass, but oc depends on your handle() method
*/
$user->is_admin = 1;
$model = $this->app['config']['auth.model'];
/**
* assuming you use Eloquent for your User model
*/
$userProvider = new \Illuminate\Auth\EloquentUserProvider($this->app['hash'], $model);
$guard = new \Illuminate\Auth\Guard($userProvider, $this->app['session.store']);
$guard->setUser($user);
$request = new \Illuminate\Http\Request();
$middleware = new \YourApp\Http\Middleware\AuthenticateAdmin($guard);
$result = $middleware->handle($request, function(){ return 'can access';});
$this->assertEquals('can access',$result);
}
I thinking the best solution is just checking what happened after middleware. For example, the authentication middleware:
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Authenticate {
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest())
{
if ($request->ajax())
{
return response('Unauthorized.', 401);
}
else
{
return redirect()->guest('auth/login');
}
}
return $next($request);
}
}
And my test unit:
<?php
class AuthenticationTest extends TestCase {
public function testIAmLoggedIn()
{
// Login as someone
$user = new User(['name' => 'Admin']);
$this->be($user);
// Call as AJAX request.
$this->client->setServerParameter('HTTP_X-Requested-With', 'XMLHttpRequest');
$this->call('get', '/authpage');
$this->assertEquals(200, $response->getStatusCode());
}
}
I would do it in that way.
I was working on a localization Middleware that sets the app locale based on a URI segment, e.g. http://example.com/ar/foo should set the app local to Arabic. I basically mocked the Request object and tested as normal. Here is my test class:
use Illuminate\Http\Request;
use App\Http\Middleware\Localize;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class LocalizeMiddlewareTest extends TestCase
{
protected $request;
protected $localize;
public function setUp()
{
parent::setUp();
config(['locale' => 'en']);
config(['app.supported_locales' => ['en', 'ar']]);
$this->request = Mockery::mock(Request::class);
$this->localize = new Localize;
}
/** #test */
public function it_sets_the_app_locale_from_the_current_uri()
{
$this->request->shouldReceive('segment')->once()->andReturn('ar');
$this->localize->handle($this->request, function () {});
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_allows_designating_the_locale_uri_segment()
{
$this->request->shouldReceive('segment')->with(2)->once()->andReturn('ar');
$this->localize->handle($this->request, function () {}, 2);
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_throws_an_exception_if_locale_is_unsupported()
{
$this->request->shouldReceive('segment')->once()->andReturn('it');
$this->request->shouldReceive('url')->once()->andReturn('http://example.com/it/foo');
$this->setExpectedException(
Exception::class,
"Locale `it` in URL `http://example.com/it/foo` is not supported."
);
$this->localize->handle($this->request, function () {});
}
}
And here is my Middleware class:
namespace App\Http\Middleware;
use Closure;
class Localize
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param integer $localeUriSegment
* #return mixed
*/
public function handle($request, Closure $next, $localeUriSegment = 1)
{
$locale = $request->segment($localeUriSegment);
if (in_array($locale, config('app.supported_locales')))
{
app()->setLocale($locale);
}
else
{
abort(500, "Locale `{$locale}` in URL `".$request->url().'` is not supported.');
}
return $next($request);
}
}
Hope that helps :)