Categories
rants

AT&T the SPAM NETWORK

You can’t get worse service than AT&T.

If you like SPAM THEN AT&T IS YOUR PROVIDER OF CHOICE.

AT&T the spam network

That could be another title for this article. This is the MOST GREEDY  OF THE MOST GREEDY POS COMPANIES ON EARTH.

Lets face it AT&T is the absolute worst cell carrier out there. Their coverage TOTALLY SUCKS ASS MAJORLY. I was down in Fort Myers Florida in the city and couldn’t get service. I get shit service where I live. Hell I can’t think of many places AT&T actually works.

That isn’t even the worst part. The worst part is I have to leave my phone on SILENT. WHY? WHY YOU ASK?  Because of constant spam texts and calls. This is a money making SCAM by AT&T and the government should stop it.

It shouldn’t be legal for a company to charge you over $100 a month for service and then say… Well you know if you don’t give us an extra $3.99 per month WE WILL OPEN THE FLOOD GATES TO HELL ON YOUR ASS AND SEND YOU 50 SHIT TEXT MESSAGES AND PHONE CALLS A DAY.

You can bet your ass AT&T when I get the money I will leave your shit sucking service in a split second. And I will continue to warn everyone I encounter about your shitty, spam filled money grabbing BS actions.

Your customers shouldn’t have to pay you extra just to not have YOU SEND SPAM YOU MORONS.

T-mobile offers spam blocking for FREE. IMAGINE THAT??? WOW NO SPAM FOR FREE!!!

See how many people you can lose AT&T you dishonest assholes

And isn’t this just fucking cute. They publish propaganda pieces like this telling people they are fighting the problem. GTFO You lieing fucking assholes. You expect people who pay for your shitty suck balls service to believe more of your bullshit lies? Sure you are helping stop spam to your customers WHO PAY YOU EXTRA FOR A FUCKING APP MORONIC ASSHOLES.

 

Categories
Web Development

Symfony Messenger vs EventDispatcher learn more

So maybe you are looking into how to use Symfony in a more Event Driven way. Or maybe you want to speed up your apps response time to users. Or maybe the article title sounds interesting so you are reading this.

Under the hood Symfony has something called an EventDispatcher system. You can see this at work by opening your Symfony profiler, the dark row at the bottom of your developer page.

Symfony profiler tab/row thingy

Every time a page is loaded Symfony executes many event listeners/subscribers. You can see a list of these inside your profiler by clicking pretty much any of the options in the profiler bar. Click the one that tells you the load time for example or even the 200 response. Click the “Events” tag to see the events.

You will see something like this :

Symfony EventDispatcher list of events.

You can listen to any of these events and perform an action based on them. The information is in the documentation link above.  This allows you to create an event or subscribe to one that happens on every request, whether ajax or page loading.

The messenger component on the other hand, allows you to create custom events or send messages to handlers. This is handy for when handling a request requires multiple steps, or reaching out to another website/service or heavy processing like image and video uploads.

The messenger component is useful for asynchronous activities and processing. This is really useful for uploads in your application. Do you really need the user to sit there and watch a spinny wheel for many seconds while you process their image? Or would it be better to take the upload and return to the user as quickly as possible while the system performs the operations in the background? You always want the experience to be the best for the user.

Summary

In the end it is up to you to review both methods and figure out which system woks best for you particular use case.

So a use case for the EventDispatcher is more like, store the page name the user is on before processing for analytics or redirecting. Or catch errors/exceptions and handle them in some specific way.  Or just do something when x happens. EventDispatcher is ONLY Synchronous so anything you do here adds to the time it takes to finish the request/response cycle for the user.

If you want an Excellent explanation and deep dive into how to use the EventDispatcher, I suggest reading this SymfonyCast on how the Http Request/Response cycle and events work.

Messenger on the other hand is good for things like handling an image upload since you may be using something like S3 object storage and that may have network issues. Messenger can be either synchronous or asynchronous.  The messenger component helps you build an asynchronous processing system so that your users experience is as quick as possible for slow processing events.

too fast meme
Don’t do this to your users.

In general the Messenger Component is good for building an Event driven system to make your app feel quicker to the user. User experience is #1.

If you want a deep dive into the Messenger Component, how it works and how to use it to the fullest. I suggest you read this SymfonyCast it goes deep into the subject and will answer all of the questions you have after reading this article and the documentation about the component.

Categories
Software Development Web Development

How to fix Composer GitHub API limit (0 calls/hr) is exhausted … errors

Programming Joke.

Modern web development keeps getting easier by the day

How does a mechanic start their day? They go to work. They get their tools and start working.

How does a carpenter start their day? They go to work. They pick up their tools and begin where they left off the previous day.

How does a web developer start their day? They go to work. Turn on their computer.  Start up the needed programs. Discover updates are needed. Start to do the updates. Failures happen. Spend entire day finding every bug and quirk in every piece of software they use. Maybe their OS shit the bed. Maybe their frameworks need updating… So they spend 4 hours googling and fixing errors in the first tool. Get to the next tool, same thing, spend 2 hours fixing it. Get to the next tool and have to spend 4 hours googling, updating and fixing it. In the midst of all of that they wreck a few things and have to restart a few dozen times. Finally 10 hours later their tools are maybe ready for programming the next day.

These days as a web developer you spend more time fixing your tools than programming. Programming in Web languages is the absolute worst of the worst.

If web developers were mechanics we would be ordering new tools every day and waiting for them to arrive before we could start work because the previous days tools were either broken, stolen or worked entirely differently today than they did yesterday… and the manufacturers docs were not updated, so we have to just play with them until we figure out how they work now.

programmers tools are all like…

Now to fix the  Github issue with composer.

Side note: a long time ago I’d get these types of  errors when simply pushing my code to my own repository.

“gitHub API limit (0 calls/hr) is exhausted, could not fetch https://api.github.com/graphql. Create a GitHub OAuth token to go over the API rate limit. You can also wait until ? for the rate limit to reset.”

Aren’t we all just like..

When you get that error while trying to use composer…

Well the Composer docs say the following

Because of GitHub’s rate limits on their API it can happen that Composer prompts for authentication asking your username and password so it can go ahead with its work.

If you would prefer not to provide your GitHub credentials to Composer you can manually create a token using the procedure documented here.

Now Composer should install/update without asking for authentication.

I don’t know about you, but Composer has never once prompted me for any password. It sort of just started doing this.

Here is what I randomly got today while trying to update a Symfony project.

Calculating CHANGELOG…GitHub API limit (0 calls/hr) is exhausted, could not fetch https://api.github.com/search/issues?q=a1a70353f64f405cfbacfc4ce860af623442d6e5. Create a GitHub OAuth token to go over the API rate limit. You can also wait until ? for the rate limit to reset.

Composer never asked for any authentication password. I do notice this error it is finally prompting for a github token below this error in another message. I have no idea when this began, I used composer a few weeks ago it seems and this wasn’t an issue.

You can enter the token there and it says it saves it.  DON’T DO THAT. Stop now. Do not create and enter the token at this prompt or you will be dreadfully sorry, you will get nothing but errors about the malformed token if you do. DON’T DO IT. Hit ctrl+c to quit or close your terminal, but whatever you do, do not enter that token yet, until you have the latest version of Composer installed.

Find your version with

 
//globally installed
composer --verision

//or loally installed
php composer.phar --version

If your version composer is not the latest version then update it(2.2.9 as of this article) To do this, if you installed composer locally then you delete the composer.phar file in your project folder. If you installed it globally then you need to go to that directory probably in

/usr/local/bin/composer

where composer is the filename not a directory. Delete the composer file. You will see suggestions online that you can just run those commands and you don’t need to delete composer. That didn’t work for me. I only got permission denied errors instead. So I rage deleted the damn thing and issued the commands again and

El magico

wow magic meme
Wow like magic it works now

No more permission denied now you summonabich!!! LOL

Then rerun the commands to install composer either locally or globally. I prefer globally so I don’t have to screw around with updating each project.

Then you need to create this special token to use Composer.

You are special and need a special token!

How to create and use the token? That is explained in the Composer Docs here. You go to the github page seen below

create github token
Create github token

Don’t choose any of the Scopes, just choose to create this useless token, it doesn’t need them.

You will next see this screen.

Now you copy that token. Open your terminal and use the following command with your token where the word token is.

//locally installed composer
php composer.phar config github-oauth.github.com token
//globally installed like I suggested
composer config --global github-oauth.github.com token

And now composer should be working again.

boom how it is done baby meme
Trial and error until it works

 

Categories
Web Development

How does Symfony decide what content type to return in a response?

This isn’t a real article it is a link to an article that explains this process. This way I can find this information again later when I need it and also google can find this question for others in the future and they can find the Symfony cast with the info they need.  And…. this is now one of the most popular articles on the site. LOL

yoda questions meme
Lets find the answers

I found this bit of interesting information in this SymfonyCast here. I had wondered about this when I was creating AJAX calls to Symfony routes.

I was playing with the new Javascript Fetch API and with sending accepted response types. In the end I decided to leave all that off and just get what is returned.

Read the linked SymfonyCast section to get an understanding of the steps/process Symfony is using to decide what content type to return in the headers.

I’d suggest reading that entire SymfonyCast it contains a hell of a lot of useful information.

Categories
Web Development

Why can only Symfony controller methods be auto-wired and not other services methods?

So when I first encountered this issue with Symfony I was baffled.  Why can you auto-wire Symfony Controller methods but with Services (aka your other classes under src) why do you have to auto-wire only the constructor. I couldn’t figure out why I couldn’t auto-wire to the methods instead.

Well, while working on deciding how I wanted to handle errors and exceptions, I googled something that lead me to the explanation. I Found in this Symfonycast deep dive right here.

I won’t repeat it all, go read it. LOL. I am sure this is stated somewhere in the docs or something, but read this explanation.

I’d suggest reading the entire Symfonycast or purchase the subscription to watch the animated videos. That cast is one of the best if you want to understand how the Request/Response life cycle actually works in Symfony.I’ve been reading it and playing around and it is all finally making sense now.

At first I was lost, then I saw the light.

Basically it is how the argument resolver system works. Now I finally understand and it makes sense.

Categories
Software Development Web Development

Symfony 5+ error logging handlers explained

This will be a super long article, this subject is way more complex than it originally sounds. As always, I’ll be updating this soon with more info and possibly more examples as I figure it out better.

First off, you are not limited to just one logger in Symfony. However, the most popular is Monolog and Symfony has built in support making it easier to implement. So that is what I cover here.

One thing you might want to use a logger for is logging specific exceptions or errors to specific locations. You may also just want to log some value in your code for debugging purposes to see what is happening or being sent to a particular method/function. These are good use cases for logging.

While most errors/exceptions already display with lots of info in the browser, you may be like me and want certain things logged so you can review the entire pattern over a period of time.

The steps required to log sound sort of simple:

  1. Install the logger
  2. Configure the logger, decide how you want it to actually work. You don’t have to configure it with yaml. Doing so makes it so that you can autowire loggers instead of having to create a new object passing values in each time which will usually be the same values.
  3. Get the logger into your class some how( this can vary depending on whether it is a controller or a service/class)
  4. Use the logger to log something like an exception.

Installing

Your project might already have monolog logger installed since Symfony uses it by default to log everything. It doesn’t matter. Running the line below won’t hurt even if it is installed already.

To install the Monolog Logger you simply open your terminal, navigate to the root of your project and type the following :

composer require symfony/monolog-bundle

That is all. Now the monolog bundle is installed and you can use it after you configure it. And that is where the fun and confusion begin. Symfony uses Monolog so it may already be installed  depending on how you created your project.

Handlers

First decide what types of loggers you want to use. You will probably see people using the stream type logger most often. This is really generic and is the default. I don’t like the stream handler because it just keeps growing infinitely.

I prefer storing different logs to different places. You can even store the logs in a database if you want. The good news is you can use more than one type of logger, and configure them however you want.

First off, here is a long list of handler types you can use with Monolog. This is going to be SUPER LONG. I’d rather just list it here than link to a file. This list is taken from the code on github

* Possible handler types and related configurations (brackets indicate optional params):
 *
 * - service:
 *   - id
 *
 * - stream:
 *   - path: string
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [file_permission]: int|null, defaults to null (0644)
 *   - [use_locking]: bool, defaults to false
 *
 * - console:
 *   - [verbosity_levels]: level => verbosity configuration
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [console_formatter_options]: array
 *
 * - firephp:
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - browser_console:
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - gelf:
 *   - publisher: {id: ...} or {hostname: ..., port: ..., chunk_size: ...}
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - chromephp:
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - rotating_file:
 *   - path: string
 *   - [max_files]: files to keep, defaults to zero (infinite)
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [file_permission]: string|null, defaults to null
 *   - [use_locking]: bool, defaults to false
 *   - [filename_format]: string, defaults to '{filename}-{date}'
 *   - [date_format]: string, defaults to 'Y-m-d'
 *
 * - mongo:
 *   - mongo:
 *      - id: optional if host is given
 *      - host: database host name, optional if id is given
 *      - [port]: defaults to 27017
 *      - [user]: database user name
 *      - pass: mandatory only if user is present
 *      - [database]: defaults to monolog
 *      - [collection]: defaults to logs
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - elasticsearch:
 *   - elasticsearch:
 *      - id: optional if host is given
 *      - host: elastic search host name. Do not prepend with http(s)://
 *      - [port]: defaults to 9200
 *      - [transport]: transport protocol (http by default)
 *      - [user]: elastic search user name
 *      - [password]: elastic search user password
 *   - [index]: index name, defaults to monolog
 *   - [document_type]: document_type, defaults to logs
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - redis:
 *   - redis:
 *      - id: optional if host is given
 *      - host: 127.0.0.1
 *      - password: null
 *      - port: 6379
 *      - database: 0
 *      - key_name: monolog_redis
 *
 * - predis:
 *   - redis:
 *      - id: optional if host is given
 *      - host: tcp://10.0.0.1:6379
 *      - key_name: monolog_redis
 *
 * - fingers_crossed:
 *   - handler: the wrapped handler's name
 *   - [action_level|activation_strategy]: minimum level or service id to activate the handler, defaults to WARNING
 *   - [excluded_404s]: if set, the strategy will be changed to one that excludes 404s coming from URLs matching any of those patterns
 *   - [excluded_http_codes]: if set, the strategy will be changed to one that excludes specific HTTP codes (requires Symfony Monolog bridge 4.1+)
 *   - [buffer_size]: defaults to 0 (unlimited)
 *   - [stop_buffering]: bool to disable buffering once the handler has been activated, defaults to true
 *   - [passthru_level]: level name or int value for messages to always flush, disabled by default
 *   - [bubble]: bool, defaults to true
 *
 * - filter:
 *   - handler: the wrapped handler's name
 *   - [accepted_levels]: list of levels to accept
 *   - [min_level]: minimum level to accept (only used if accepted_levels not specified)
 *   - [max_level]: maximum level to accept (only used if accepted_levels not specified)
 *   - [bubble]: bool, defaults to true
 *
 * - buffer:
 *   - handler: the wrapped handler's name
 *   - [buffer_size]: defaults to 0 (unlimited)
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [flush_on_overflow]: bool, defaults to false
 *
 * - deduplication:
 *   - handler: the wrapped handler's name
 *   - [store]: The file/path where the deduplication log should be kept, defaults to %kernel.cache_dir%/monolog_dedup_*
 *   - [deduplication_level]: The minimum logging level for log records to be looked at for deduplication purposes, defaults to ERROR
 *   - [time]: The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through, defaults to 60
 *   - [bubble]: bool, defaults to true
 *
 * - group:
 *   - members: the wrapped handlers by name
 *   - [bubble]: bool, defaults to true
 *
 * - whatfailuregroup:
 *   - members: the wrapped handlers by name
 *   - [bubble]: bool, defaults to true
 *
 * - syslog:
 *   - ident: string
 *   - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER
 *   - [logopts]: defaults to LOG_PID
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - syslogudp:
 *   - host: syslogd host name
 *   - [port]: defaults to 514
 *   - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER
 *   - [logopts]: defaults to LOG_PID
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [ident]: string, defaults to
 *
 * - swift_mailer:
 *   - from_email: optional if email_prototype is given
 *   - to_email: optional if email_prototype is given
 *   - subject: optional if email_prototype is given
 *   - [email_prototype]: service id of a message, defaults to a default message with the three fields above
 *   - [content_type]: optional if email_prototype is given, defaults to text/plain
 *   - [mailer]: mailer service, defaults to mailer
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [lazy]: use service lazy loading, bool, defaults to true
 *
 * - native_mailer:
 *   - from_email: string
 *   - to_email: string
 *   - subject: string
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [headers]: optional array containing additional headers: ['Foo: Bar', '...']
 *
 * - symfony_mailer:
 *   - from_email: optional if email_prototype is given
 *   - to_email: optional if email_prototype is given
 *   - subject: optional if email_prototype is given
 *   - [email_prototype]: service id of a message, defaults to a default message with the three fields above
 *   - [mailer]: mailer service id, defaults to mailer.mailer
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - socket:
 *   - connection_string: string
 *   - [timeout]: float
 *   - [connection_timeout]: float
 *   - [persistent]: bool
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - pushover:
 *   - token: pushover api token
 *   - user: user id or array of ids
 *   - [title]: optional title for messages, defaults to the server hostname
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [timeout]: float
 *   - [connection_timeout]: float
 *
 * - raven / sentry:
 *   - dsn: connection string
 *   - client_id: Raven client custom service id (optional)
 *   - [release]: release number of the application that will be attached to logs, defaults to null
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [auto_log_stacks]: bool, defaults to false
 *   - [environment]: string, default to null (no env specified)
 *
 * - sentry:
 *   - hub_id: Sentry hub custom service id (optional)
 *
 * - newrelic:
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [app_name]: new relic app name, default null
 *
 * - hipchat:
 *   - token: hipchat api token
 *   - room: room id or name
 *   - [notify]: defaults to false
 *   - [nickname]: defaults to Monolog
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [use_ssl]: bool, defaults to true
 *   - [message_format]: text or html, defaults to text
 *   - [host]: defaults to "api.hipchat.com"
 *   - [api_version]: defaults to "v1"
 *   - [timeout]: float
 *   - [connection_timeout]: float
 *
 * - slack:
 *   - token: slack api token
 *   - channel: channel name (with starting #)
 *   - [bot_name]: defaults to Monolog
 *   - [icon_emoji]: defaults to null
 *   - [use_attachment]: bool, defaults to true
 *   - [use_short_attachment]: bool, defaults to false
 *   - [include_extra]: bool, defaults to false
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [timeout]: float
 *   - [connection_timeout]: float
 *
 * - slackwebhook:
 *   - webhook_url: slack webhook URL
 *   - channel: channel name (with starting #)
 *   - [bot_name]: defaults to Monolog
 *   - [icon_emoji]: defaults to null
 *   - [use_attachment]: bool, defaults to true
 *   - [use_short_attachment]: bool, defaults to false
 *   - [include_extra]: bool, defaults to false
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - slackbot:
 *   - team: slack team slug
 *   - token: slackbot token
 *   - channel: channel name (with starting #)
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - cube:
 *   - url: http/udp url to the cube server
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - amqp:
 *   - exchange: service id of an AMQPExchange
 *   - [exchange_name]: string, defaults to log
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - error_log:
 *   - [message_type]: int 0 or 4, defaults to 0
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - null:
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - test:
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - debug:
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - loggly:
 *   - token: loggly api token
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [tags]: tag names
 *
 * - logentries:
 *   - token: logentries api token
 *   - [use_ssl]: whether or not SSL encryption should be used, defaults to true
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *   - [timeout]: float
 *   - [connection_timeout]: float
 *
 * - insightops:
 *   - token: Log token supplied by InsightOps
 *   - region: Region where InsightOps account is hosted. Could be 'us' or 'eu'. Defaults to 'us'
 *   - [use_ssl]: whether or not SSL encryption should be used, defaults to true
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - flowdock:
 *   - token: flowdock api token
 *   - source: human readable identifier of the application
 *   - from_email: email address of the message sender
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - rollbar:
 *   - id: RollbarNotifier service (mandatory if token is not provided)
 *   - token: rollbar api token (skip if you provide a RollbarNotifier service id)
 *   - [config]: config values from https://github.com/rollbar/rollbar-php#configuration-reference
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * - server_log:
 *   - host: server log host. ex: 127.0.0.1:9911
 *   - [level]: level name or int value, defaults to DEBUG
 *   - [bubble]: bool, defaults to true
 *
 * All handlers can also be marked with `nested: true` to make sure they are never added explicitly to the stack
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Christophe Coevoet <stof@notk.org>

That is a lot. Some are obvious, others you will need to google and figure out.

You can get as wild and creative as you want though.

Config location is everything

Where you place your configuration is very important. A little more hint here.

If you want to log just during Development you put the configuration inside the dev folder. /app/config/packages/dev/monolog.yaml

If you want to log only during production you put the configuration in the prod folder. /app/config/packages/prod/monolog.yaml

If you want to log in all environments you place the configuration in the main configuration folder. /app/config/packages/monolog.yaml

So if you want a specific logger that logs to a specific file to be available in all environments define it in the “packages” folder.

It isn’t too bad once you figure it out.

Here is the official documentation on how configuration works. Read it if you need more information.

An example configuration

Before getting too much further lets look at the default file I have for the configuration of monolog in the development environment.

#file name -> /config/packages/dev/monolog.yaml
monolog:
    handlers:
        main:
            type: rotating_file
            max_files: 3
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            #path: php://stderr
            level: debug
            channels: ["!event"]
        # uncomment to get logging in your browser
        # you may have to allow bigger header sizes in your Web server configuration
        #firephp:
        #    type: firephp
        #    level: info
        #chromephp:
        #    type: chromephp
        #    level: info
        console:
            type: console
            process_psr_3_messages: false
            channels: ["!event", "!doctrine", "!console"]

Yours may differ, I have probably made changes to this and forgotten at this point or some changes in the Symfony code base may have happened.

After the word handlers you list each handler one tab level in.  Each handler gets it’s own configuration settings. Basically you pick a handler from the long list above, then configure it properly for your needs.

For local development I prefer the rotating_file option. Since it seems like it is the only one that you can put a limit on the number of files.

The stream option just infinitely fills a single file forever and ever. Unless you have some sort of log rotator as mentioned in the documentation.

If you have the time and urge you could even store the messages in a database then create a user interface in your admin panel to view the messages then delete them. Otherwise you will need some way of limiting the logs and log sizes.

The documentation refers to this list as a stack of handlers. You can have as many as you want, like I mentioned above.

Here is the default production configuration taken from here in the Symfony docs.

# file name -> /config/packages/prod/monolog.yaml
monolog:
    handlers:
        filter_for_errors:
            type: fingers_crossed
            # if *one* log is error or higher, pass *all* to file_log
            action_level: error
            handler: file_log

        # now passed *all* logs, but only if one log is error or higher
        file_log:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"

        # still passed *all* logs, and still only logs error or higher
        syslog_handler:
            type: syslog
            level: error

The Symfony docs say the following about production logs.

In the prod environment, logs are written to STDERR PHP stream, which works best in modern containerized applications deployed to servers without disk write permissions.

I’ll probably remove the line that logs locally to a file the file_log line. No need to infinitely fill a log I don’t read. You will need to decide what handlers you want in production and configure them.

Handler names

As you can see in the development configuration file above, there is a handler named “main”. You can give your handlers any name you want, but you need to tell Symfony what type it is with the type directive like you see in main.

main: 
  type: rotating_file 
  max_files: 3 
  path: "%kernel.logs_dir%/%kernel.environment%.log" 
  level: debug 
  channels: ["!event"]

The word “main” can be anything you desire, but notice how you must use type, then use one of the above handler names, then provide the options. Read more about handler names here in the Symfony docs. Basically you can call the handler anything even “asdfg” as long as you use the word “type” and list the type and other required information. More on this below.

Bubble?

That is the question!

One property that you will see on all of the types above is “bubble” Further digging into the github monolog documentation reveals the following about it.

Handlers also have a $bubble property which defines whether they block the record or not if they handled it. In this example, setting the MailHandler‘s $bubble argument to false means that records handled by the MailHandler will not propagate to the StreamHandler anymore.

Basically this means you need to define your loggers in the proper order if you want to have the handling stop at a particular logger for a particular situation. Otherwise each logger in your list does something each time and you might not want that. Like do you want errors to go to a log and also to the console during development?

One thing you may want to think about is where do you want your errors, exceptions and other logged data to be logged? This answer will be different for production vs development for sure. You may want to log just exceptions to a database in production, so that you can query it and show them in an admin user interface for example.

This part of the documentation speaks about logging to different locations.

Channels aka logging to multiple places

What are these channels?

The documentation speaks of channels. What are these channels? In the code above for the monolog.yaml development configuration you will see this line

 channels: ["!event", "!doctrine", "!console"]

Here in the documentation it says the following about channels

The Symfony Framework organizes log messages into channels. By default, there are several channels, including doctrine, event, security, request and more. The channel is printed in the log message and can also be used to direct different channels to different places/files.

Aha. So that is what those are. I wonder what the ! means? Does that mean it ignores those or does it mean it accepts those? Yes it does. Basically the above line would log everything except (event, doctrine and console.) Below you can see a list of all the channels.

So to clarify if you want a handler to log a message for a specific channel you can use the following syntax in the handler configuration.

channels: [security, doctrine]

If you want the handler to ignore a channel then you use this syntax in the handler configuration

channels: ["!event", "!doctrine", "!console"]

You can really get as insanely complex as you want with this logging stuff.

By the way how do you know what logging channels are available?

I prefer the following command, because these can be auto-wired :

php bin/console debug:autowiring

Scroll down and look for monolog listings. You will see something like this.(These can all be auto-wired)

console bin output

These are the channels you have.Basically whenever one of these experiences an issue Symfony sends out an event and the handlers you list decide if they want to do something about it. This way using the channels and multiple handlers you can log things exactly where you want them.  You can even log the same event/message multiple times in multiple locations.

As the docs mention, by default Symfony logs all events to the same location. It is up to you to change that to your projects needs.

Another useful command is the following which allows you to get detailed info about each, it also lists more, but not all are auto-wireable.

php bin/console debug:container monolog

That is interactive and you can choose one of the listings to get more information about it. I find the first method useful to find what I actually can access.

Creating Custom Channels

Sometimes you need to log a specific issue to a specific file or location. For this you need to create your own Channel. The documentation is really weak on this subject so lets dig deep into it.

I get tired of the foo bar BS so here I create a custom channel named “chicken” just so it sticks out for this example.

To create the channel you simple list it at the top of the monolog.yaml file like this.

monolog:
  channels: ['chicken']
  handlers:
    main:
      type: rotating_file
      max_files: 3
      path: "%kernel.logs_dir%/%kernel.environment%.log"
      #path: php://stderr
      level: debug
      channels: [ "!event" ]

A little confusing how you use that syntax in more than one location. In one it listens for channels in another it creates channels.

Wait what?

Maybe channel_list or something else to differentiate would have been better?

Placing the directive above the rest of the handler code creates the new channel or channels.  I don’t know if this is a requirement, but I know it works. To create more than one channel create strings separated by commas.

Now to check if anything happened I will use this command


php bin/console debug:autowiring

Now we have a chicken logger.

Now you can see “monolog.logger.chicken” logger that was created with that one line in the monolog.yaml configuration file.

That is how it is done.

How to use the logger?

There are two ways to use the logger, one you didn’t have to do any of this to use, the other is autowiring, which is why we did all of this for. The other way is to just initiate it and pass the values to the constructor like the documentation for Monolog shows.

Even though all that configuration seems like a pain, in the end it makes things easier by allowing us to auto-wire the logger we want. This saves a lot of having to create the same boilerplate code over and over all throughout our app.

Taking another look at the output of the bin/console debug:container command from above we can see the following

Lets now use the chickenLogger

Each line after the first monolog.logger contains what looks like a PHP variable. It is in fact a PHP variable, one you can use for type hinting. How this works is you type hint LoggerInterface, then use which variable you want and Symfony automagically loads it for you.

farth too easy meme
It feels too easy to be true

Back to the chickenLoggerExample. What I want to do with the chickenLogger is use it to log exceptions to their own exceptions log for easier viewing. This is useful in a situation like using a library to create and convert images.

class UserImageProcessor
{

    private LoggerInterface $chickenLogger;

    public function __construct(LoggerInterface $chickenLogger){
        $this->chickenLogger = $chickenLogger;
        $this->chickenLogger->debug('lets log some chickens');
    }

See in the constructor how I passed the variable $chickenLogger and now Symfony passes me the chickenLogger Channel and I can now log chickens. Logging chickens is a very handy feature in the real world.

And below is the whole monolog.yaml file I am using to log my chickens.

monolog:
  channels: ['chicken']
  handlers:
    main:
      type: rotating_file
      max_files: 3
      path: "%kernel.logs_dir%/%kernel.environment%.log"
      #path: php://stderr
      level: debug
      channels: [ "!event", "!chicken" ]

    exceptions:
      type: rotating_file
      max_files: 2
      path: "%kernel.logs_dir%/exceptions.log"
      file_permission: '765'
      level: debug
      channels: ['chicken']


    # uncomment to get logging in your browser
    # you may have to allow bigger header sizes in your Web server configuration
    #firephp:
    #    type: firephp
    #    level: info
    #chromephp:
    #    type: chromephp
    #    level: info
    console:
      type: console
      process_psr_3_messages: false
      channels: [ "!event", "!doctrine", "!console" ]

Notice I used the file permissions option. That is a handy one. See the exceptions handler is using the chicken channel. Again I could have named the “exceptions:” section anything even “chirpy-birds” symfony doesn’t care, only the directives matter.

Now when I log my chickens, the main handler section will ignore the chickens, but my exceptions section will log my chickens and so will the console. I’d have to add “!chicken” to the consoles “channels” section or I can add “bubbles: false” to the exceptions section to stop it.

Now you know!!!

Links about Loggers

Logging to Rollbar -> Helps you organize and view logs and much more.

Logging with Monolog -> A short article

Logging -> Documentation link ->overall general information

How to log messages to different files -> more from the documentations

Using the logger -> more from the docs on how to use it.

Categories
Software Development Web Development

PHP Enumeration classes vs fake enumerations what to use.

I am growing to HATE PHP ENUMS. Why? Because if you forget to call ->value you get BS errors about cannot convert object to string. It is just another un-needed step. Enums sounded cool at first but fuq the BS with calling ->value, it is entirely too easy to forget.

Enumeration classes in PHP are a new feature as of version 8.1 Fake enumerations were what we did before this new feature. A fake enumeration was just a class with constants, the syntax to refer to them was the same minus ->value. See my other article if you don’t know what fake PHP enumerations are Php Backed Enums don’t forget to call value

TLDR

Use plain classes with constants if any of the values will repeat. Use Enumerations when each value is always unique. Keep reading to find out WTF I am talking about.

WTF are you talking about?

The story

The new Enumeration classes have a limitation on them that each “case” must have an unique value, it is just how it works.
This is only a problem when you are using Enumerations more like the old fake constant enumerations, because you can’t give two cases the same value. If you are using ints you run into this issue more quickly.

Let me show an example.

I have often used this pattern to help eliminate bugs from typing, it also helps you remember what values are possible for something. Below is the values allowed for an image for some sort of upload.

class ImageDataEnum
{
    const HEIGHT = 'height';
    const SIZE_STRING = 'size';
    const IMAGE_URL = 'image_url';
    const WIDTH = 'width';
}

This way I know what values are allowed in the upload and I don’t get errors from typing.This would convert to an Enumeration perfectly.

I have dysgraphia and dyslexia so this is something I fight constantly, it is also why I hate Linux command line. Basically my brain gets the letters out of order sometimes when I type or write or leaves entire letters out or adds them and my eyes don’t see it for some odd reason sometimes. I couldn’t take notes in school because it turned into jibberish word soup.

But I also like to use them to store info that won’t change, like maximum image size. This is a use case that won’t work with PHP Enumerations, probably one of the only ones I can think of.

Bad code

You made a real mess of that code fella

This kind of code won’t work.

enum ImageSizeEnum: int
{

    case BLOG_IMAGE_HEIGHT = 600;
    case BLOG_IMAGE_WIDTH = 600;
    case TWITTER_LARGE_IMAGE_HEIGHT = 600;
    case TWITTER_LARGE_IMAGE_WIDTH = 1200;
    case PROFILE_PICTURE_HEIGHT = 180;
    case PROFILE_PICTURE_WIDTH = 180;
    case PROFILE_THUMB_HEIGHT = 55;
    case PROFILE_THUMB_WIDTH = 55;
    case USER_THUMB_HEIGHT = 150;
    case USER_THUMB_WIDTH = 150;
    case USER_IMAGE_HEIGHT = 600;
    case USER_IMAGE_WIDTH = 600;

}

Notice that while each case has a unique name, they do not have unique values. The code above will give you an error because the values for the cases is repeated. If you are using PHPStorm then it is screaming at you right now with red underlines and error warnings etc. LOLOL

PHP Storm is all like…

The docs say the following

In PHP, Enums are a special kind of object. The Enum itself is a class, and its possible cases are all single-instance objects of that class. That means Enum cases are valid objects and may be used anywhere an object may be used, including type checks

In order to do what is intended in the code above (a list of constants to be used in code later) this has to be converted back to a class with constants. It looks so similar it is crazy.

Just convert really quickly

Ok code

This code will work.

class ImageSizeEnum
{

    const BLOG_IMAGE_HEIGHT = 600;
    const BLOG_IMAGE_WIDTH = 600;
    const TWITTER_LARGE_IMAGE_HEIGHT = 600;
    const TWITTER_LARGE_IMAGE_WIDTH = 1200;
    const PROFILE_PICTURE_HEIGHT = 180;
    const PROFILE_PICTURE_WIDTH = 180;
    const PROFILE_THUMB_HEIGHT = 55;
    const PROFILE_THUMB_WIDTH = 55;
    const USER_THUMB_HEIGHT = 150;
    const USER_THUMB_WIDTH = 150;
    const USER_IMAGE_HEIGHT = 600;
    const USER_IMAGE_WIDTH = 600;

}

With the fake enum you leave off the ->value part when you need the value as noted in this article. Php Backed Enums don’t forget to call value

Now I can have constants that have the same value and PHP won’t barf errors on me.

kitten frew up meme
I make PHP barf.

You can also do something very similar in Javascript if you are interested check out this article. Faking Enumerations with Vanilla javascript

Summary

So basically if you need to use the same value with a different name, then you need the old fashioned PHP fake enumerations. If your use case has it so that every named case has a unique value then use Enumerations.